Insecure Direct Object Reference in Adonisjs with Mongodb
Insecure Direct Object Reference in Adonisjs with Mongodb — how this specific combination creates or exposes the vulnerability
Insecure Direct Object Reference (IDOR) occurs when an API exposes a reference to a resource—such as a MongoDB ObjectId in a URL or query parameter—and relies only on user-supplied input to locate and return that resource. In AdonisJS, this commonly arises when a route like /users/:id/profile uses the raw :id to fetch a document from MongoDB without verifying that the authenticated requesting user owns or is authorized to access that document.
AdonisJS does not inherently enforce resource-level ownership; it provides request handling, validation, and ORM-like features via Lucid ORM. When using MongoDB through the official driver or a Mongoose-like layer, an endpoint might do const profile = await Profile.find(req.params.id). If the id comes directly from the client and there is no check tying the found profile to the authenticated user’s ID (e.g., profile.userId.equals(auth.user.id)), the endpoint is vulnerable to IDOR.
The risk is compounded because MongoDB ObjectIds are predictable and globally unique. An attacker can iterate through likely ObjectIds and, if authorization is missing, retrieve or modify other users’ profiles, settings, or sensitive data. This is an unauthenticated or low-privilege attack surface when endpoints rely solely on the object identifier without contextual authorization checks.
Additionally, AdonisJS API resources and transformers can inadvertently expose references in JSON responses (e.g., embedding related resource IDs). If those references are used client-side without server-side authorization on each access, they enable horizontal privilege escalation across the application. The unauthenticated attack surface testing performed by middleBrick can surface these missing ownership checks by correlating spec definitions and runtime behavior across endpoints.
Mongodb-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on enforcing ownership or role-based access control every time you fetch a MongoDB document by user input. Below are concrete, idiomatic examples for AdonisJS using the official MongoDB driver.
1. Enforce userId ownership on profile fetch
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Database from '@ioc:Adonis/Lucid/Database'
export default class ProfilesController {
public async show({ params, auth }: HttpContextContract) {
const profile = await Database
.from('profiles')
.where('_id', params.id)
.where('user_id', auth.user?.id) // enforce ownership
.first()
if (!profile) {
return Response.badRequest({ message: 'Not found or unauthorized' })
}
return profile
}
}
2. Using MongoDB driver with ObjectId and userId checks
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { ObjectId } from 'mongodb'
import db from 'App/Providers/DbProvider'
export default class DocumentsController {
public async show({ params, auth }: HttpContextContract) {
const collection = db.client.db('app').collection('documents')
const userId = new ObjectId(auth.user?.id as string)
const doc = await collection.findOne({
_id: new ObjectId(params.id),
userId: userId,
})
if (!doc) {
return Response.unauthorized()
}
return doc
}
}
3. Scoped list with tenant or role checks
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import db from 'App/Providers/DbProvider'
export default class ReportsController {
public async index({ request, auth }: HttpContextContract) {
const tenantId = auth.user?.tenantId
const reports = await db.client
.db('app')
.collection('reports')
.find({ tenantId, visibility: 'internal' })
.toArray()
return reports
}
}
4. Validate and normalize IDs before querying
import { schema, rules } from '@ioc:Adonis/Core/Validator'
import { ObjectId } from 'mongodb'
const profileSchema = schema.create({
id: schema.string({}, [rules.format({ type: 'mongodb_id' })]),
})
export async function validateAndFetch(ctx: HttpContextContract) {
const payload = await ctx.validate({
schema: profileSchema,
})
const normalizedId = new ObjectId(payload.id)
// proceed with ownership-checked query as above
}
5. Middleware or policy-based authorization
Implement a lightweight policy check that runs before the route handler. For example:
import { Exception } from '@poppinss/utils'
const guardResourceAccess = async (resourceId: string, userId: string) => {
const hasAccess = await checkUserResourceRelationship(userId, resourceId)
if (!hasAccess) {
throw new Exception('Forbidden', 403, 'E_FORBIDDEN')
}
}
Use this guard inside handlers after fetching the minimal required metadata to avoid an extra round-trip when not needed.
These patterns ensure that every MongoDB query includes a user- or role-bound filter, eliminating the IDOR vector. When combined with input validation for ObjectId formats and consistent error handling, the attack surface is reduced to the authenticated boundary without changing the runtime behavior for legitimate clients.
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 |