Broken Access Control in Adonisjs with Mongodb
Broken Access Control in Adonisjs with Mongodb — how this specific combination creates or exposes the vulnerability
Broken Access Control in an Adonisjs application using MongoDB typically arises when authorization checks are missing or incorrectly applied before data operations. Adonisjs does not enforce authorization at the model or database layer; it relies on developer-supplied guards and policies. When those guards are omitted or bypassed, a request can directly reference a MongoDB identifier and retrieve or modify records that should be restricted.
Consider a route that fetches a user profile by ID:
// routes.ts
Route.get('/profile/:id', async ({ params, auth }) => {
const profile = await Profile.find(params.id);
return profile;
});
If the developer does not verify that the authenticated user owns the requested params.id, an authenticated user can change the ID in the request and access any profile stored in the MongoDB collection. This is a classic Insecure Direct Object Reference (IDOR), which middleBrick classifies under BOLA/IDOR checks.
Adonisjs may use Lucid ORM models that wrap MongoDB via an ODM layer. If a developer queries directly through the ODM without applying tenant or ownership filters, the abstraction does not automatically restrict scope. For example, using Profile.query().where('id', id).first() still executes a MongoDB findOne without ownership context if the query does not include a user reference.
Role-based access control can also be misconfigured. A route intended for administrators might be protected by a role guard, but if the guard is not enforced at the data query level, a user with a basic role could still send a request that reaches MongoDB. MiddleBrick’s checks for BFLA/Privilege Escalation and Property Authorization are designed to detect such gaps by comparing intended permissions with actual query behavior.
Additionally, if the API exposes filters or sorting that map directly to MongoDB query operators, an attacker can manipulate URL parameters to exfiltrate other users’ data. For instance, allowing a filter[ownerId] parameter without validating it against the authenticated user enables horizontal privilege escalation. Because the scan tests unauthenticated attack surfaces, middleBrick can identify endpoints that accept user-controlled query inputs that directly shape MongoDB queries without authorization checks.
Mongodb-Specific Remediation in Adonisjs — concrete code fixes
To remediate Broken Access Control when using MongoDB in Adonisjs, always couple route parameters with the authenticated user’s identity and enforce checks before building queries. Do not rely on route-level guards alone; apply data-level constraints so that even if an ID is guessed, MongoDB returns no unauthorized documents.
1) Scope queries to the authenticated user. Instead of fetching by ID alone, include the user identifier in the filter:
// app/Controllers/Http/ProfileController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Profile from 'App/Models/Profile'
export default class ProfilesController {
public async show({ params, auth }: HttpContextContract) {
const user = auth.user!
const profile = await Profile.query()
.where('id', params.id)
.where('userId', user.id)
.preload('settings')
.firstOrFail()
return profile
}
}
In this example, the query includes both the requested ID and the authenticated user’s ID, ensuring that the MongoDB find operation only matches documents belonging to that user. This directly addresses IDOR by binding the data scope to the requester.
2) Use explicit policies to centralize authorization logic. Define a policy that decides whether a user can view or modify a given document, and call it before executing any database operation:
// start/kernel.ts
import Route from '@ioc:Adonis/Core/Route'
Route.resource('profiles', 'ProfileController').policy('profile')
// app/Policies/ProfilePolicy.ts
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import Profile from 'App/Models/Profile'
export default class ProfilePolicy {
constructor(protected app: ApplicationContract) {}
public async view(user: any, id: string) {
const profile = await Profile.query()
.where('id', id)
.where('userId', user.id)
.first()
return !!profile
}
}
With this setup, route handlers can invoke authorize before querying, and the policy ensures the same ownership check is applied consistently. This pattern scales well when endpoints proliferate and helps middleBrick’s authorization checks validate that controls are enforced across the surface.
3) Avoid passing raw user input directly into sort or filter parameters that reshape MongoDB queries. If filtering is required, map allowed fields to known safe values and reject anything that could modify the effective query scope:
// app/Validators/ProfileValidator.ts
import { schema } from '@ioc:Adonis/Core/Validator'
export const profileFilterSchema = schema.create({
sortBy: schema.optional.string([
schema.enum(['createdAt', 'updatedAt'])
]),
order: schema.optional.string([
schema.enum(['asc', 'desc'])
])
})
// Usage in controller
const { sortBy, order } = await params.validate({ schema: profileFilterSchema })
const query = Profile.query().where('userId', user.id)
if (sortBy) {
query.orderBy(sortBy, order || 'asc')
}
const results = await query.limit(50).exec()
This approach prevents attackers from injecting fields like userId or other sensitive keys into the query, mitigating manipulation of result sets. By combining strict validation with user-scoped queries, you align with the remediation guidance that middleBrick provides in its findings.