Bola Idor in Adonisjs with Mutual Tls
Bola Idor in Adonisjs with Mutual Tls — how this combination creates or exposes the vulnerability
BOLA (Broken Object Level Authorization) occurs when an API fails to enforce proper access controls at the object level, allowing one user to act on another user’s resources. AdonisJS, a Node.js framework, does not enforce authorization automatically; developers must implement policies and apply them consistently. When mutual TLS (mTLS) is used, the server authenticates the client by validating its certificate, but this does not imply identity mapping to application-level resources. Relying solely on mTLS for authentication can create a false sense of security: once the client is authenticated, the application may authorize requests based on connection attributes (e.g., certificate subject) instead of the actual resource ownership, enabling BOLA.
For example, an mTLS-authenticated endpoint like GET /documents/:id may verify the client certificate but then load a document by ID without checking whether the authenticated principal has permission for that specific document. If IDs are predictable, an attacker can iterate through numeric or UUID identifiers and access documents belonging to other users. This is a classic BOLA/IDOR pattern. The combination of mTLS and missing object-level checks means authentication succeeds, but authorization does not, because the framework does not enforce tenant or ownership scoping at the model layer.
OpenAPI/Swagger analysis with middleBrick can surface this risk when spec definitions expose endpoints with path parameters (e.g., /documents/{id}) but omit security schemes that enforce object ownership. Even when mTLS is declared as a security requirement, the spec may not indicate that each object must be scoped to the authenticated principal’s permissions. Runtime findings from unauthenticated scans (which middleBrick performs) can still detect ID-like parameter exposure and missing authorization checks, highlighting BOLA despite transport-layer mTLS.
Real-world patterns mirror CVE-like scenarios such as insecure direct object references (OWASP API Top 10:2023 API1:2023) where numeric IDs are iterated. With mTLS, attackers may use stolen or spoofed certificates if certificate validation is misconfigured, but the core BOLA issue remains insufficient model-level policy. The framework does not inherently bind the authenticated certificate to a user record and then to data rows; developers must implement this binding explicitly via policies that resolve the certificate to a user ID and scope queries accordingly.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
To remediate BOLA when using mTLS in AdonisJS, couple transport-layer authentication with robust object-level authorization. Map the client certificate to an application identity, enforce ownership checks on every resource access, and avoid trusting path parameters alone. Below are concrete, syntactically correct examples.
1. mTLS setup in AdonisJS (server-side)
Configure the AdonisJS HTTP server to require and validate client certificates. This example uses the built-in HTTPS server with requestCert and rejectUnauthorized.
// start/hooks.ts
import { defineConfig } from '@adonisjs/core/app'
import { join } from 'path'
export default defineConfig({
https: {
key: join(__dirname, '../cert/server.key'),
cert: join(__dirname, '../cert/server.crt'),
ca: join(__dirname, '../cert/ca.crt'),
requestCert: 'required',
rejectUnauthorized: true,
},
})
2. Certificate-to-user mapping via a custom middleware
Extract the certificate’s subject or serial and resolve it to a user in your application. Store the user ID in the auth context for downstream policies.
// middleware/map_mtls_user.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
export default async function mapMtlsUser({ request, auth, response, next }: HttpContextContract) {
const cert = request.sslCredentials
if (!cert || !cert.peerCertificate) {
return response.unauthorized('Client certificate required')
}
// Map certificate fingerprint or subject to a user
const subject = cert.peerCertificate.subject
const user = await User.query()
.where('cert_subject', subject)
.preload('roles')
.first()
if (!user) {
return response.unauthorized('Certificate not registered')
}
// Bind user to auth for policy checks
await auth.use('api').login(user)
await next()
}
3. Enforce object-level ownership in a policy
Create an AdonisJS policy that ensures the authenticated user owns the document before allowing access.
// policies/DocumentPolicy.ts
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Document from 'App/Models/Document'
export default class DocumentPolicy {
public async view({ params, auth }: HttpContextContract) {
const document = await Document.findOrFail(params.id)
// BOLA prevention: scope to authenticated user
if (document.userId !== auth.user?.id) {
throw new Error('Unauthorized access to document')
}
return document
}
public async update({ params, request, auth }: HttpContextContract) {
const document = await Document.findOrFail(params.id)
if (document.userId !== auth.user?.id) {
throw new Error('Unauthorized update')
}
// proceed with update
}
}
4. Apply the policy in a route
Use the policy to gate access to the resource, ensuring that even with mTLS, each operation checks ownership.
// routes.ts
import Route from '@ioc:Adonis/Core/Route'
import DocumentPolicy from 'Policies/DocumentPolicy'
Route.get('/documents/:id', async ({ params, auth }) => {
const document = await auth.use('api').policy(DocumentPolicy).view(params)
return document
})
Route.put('/documents/:id', async ({ params, request, auth }) => {
const document = await auth.use('api').policy(DocumentPolicy).update(params, request.only(['content']))
return document
})
5. Validate resource existence and scope in controllers
Alternatively or additionally, scope queries directly in controller methods to avoid missing policy coverage.
// controllers/DocumentsController.ts
import Document from 'App/Models/Document'
export default class DocumentsController {
public async show({ params, auth }: HttpContextContract) {
const document = await Document.query()
.where('id', params.id)
.where('userId', auth.user?.id)
.firstOrFail()
return document
}
}
These steps ensure that mTLS provides client authentication while explicit object-level checks prevent BOLA. middleBrick scans can verify that endpoints with path parameters include appropriate authorization checks and that mTLS is correctly configured in the spec.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |