Insecure Direct Object Reference in Adonisjs with Api Keys
Insecure Direct Object Reference in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
In AdonisJS, an Insecure Direct Object Reference (IDOR) occurs when an API endpoint uses a user-supplied identifier (such as a numeric resource ID or a string key) to access a database record or file without verifying that the requesting user is authorized to access that specific object. When API keys are used for authentication but not paired with proper ownership or scope checks, the combination creates a classic IDOR scenario.
Consider an endpoint like GET /api/v1/projects/:id that relies on an API key for authentication. The key identifies the consumer, but if the handler directly queries Project.findOrFail(request.param('id')) using the ID from the URL, it omits a check that the project belongs to the authenticated API key’s scope. An attacker who knows or guesses another project’s ID can retrieve or modify it simply by changing the URL parameter, even with a valid API key. This is IDOR: the reference to the object is direct and insecure because authorization is tied to the key’s general validity, not to the specific object being accessed.
In AdonisJS applications that use API keys stored in a separate table (e.g., ApiKey), the risk is compounded if relationships and ownership are not enforced. For example, a many-to-many relationship between ApiKey and Project should be validated on each request. If the code only checks that the key exists, but does not verify that the key is associated with the project identified by :id, an attacker can pivot across resources. Common triggers include missing scoping in query statements, overly permissive route parameters, and assumptions that a key’s presence equals full access to all records.
Real-world attack patterns mirror OWASP API Top 10 A01:2023 broken object level authorization. In practice, IDOR in AdonisJS with API keys can lead to unauthorized data exposure (e.g., customer PII), tampering with other users’ records, or privilege escalation if the objects contain administrative flags. Because the vulnerability resides in the lack of object-level authorization, rate limiting and input validation alone do not prevent it; the authorization logic must explicitly bind the API key to the resource being accessed.
Using OpenAPI/Swagger analysis, such misconfigurations can be detected when spec definitions expose numeric or UUID identifiers without required security schemes that enforce scope checks at the path level. Runtime findings then map these paths to the IDOR category, highlighting endpoints where the parameter is not correlated with the authenticated key’s allowed set. This is why continuous scanning and spec-to-runtime correlation are valuable: they surface routes where authorization is implied by key presence rather than enforced by relationship checks.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
Remediation centers on enforcing that every data access includes a check that the authenticated API key has a relationship to the target object. Below are concrete, syntactically correct examples for AdonisJS that demonstrate this approach.
Example 1: Scoped query using relationships
Assume you have models ApiKey and Project with a many-to-many relationship defined. The API key model has a projects() relationship, and the route uses an API key for authentication via header or query parameter.
// app/Controllers/Http/ProjectController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Project from 'App/Models/Project'
export default class ProjectsController {
public async show({ params, auth }: HttpContextContract) {
// Authenticate via API key middleware that sets auth.user
const project = await auth.user
.related('projects')
.query()
.where('id', params.id)
.firstOrFail()
return project
}
}
This ensures that the project must be in the authenticated key’s associated projects; otherwise, firstOrFail throws a 404, effectively preventing IDOR.
Example 2: Explicit ownership check with a custom scope
If you prefer not to rely on relationships at query time, you can perform an explicit check before operating on the resource.
// app/Controllers/Http/ProjectController.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Project from 'App/Models/Project'
import ApiKey from 'App/Models/ApiKey'
export default class ProjectsController {
public async update({ params, request, response }: HttpContextContract) {
const apiKey = await ApiKey.findByOrFail('key', request.header('X-API-Key'))
const project = await Project.findOrFail(params.id)
// Ensure the key is allowed to act on this project
const allowed = await apiKey.related('projects').query().where('project_id', project.id).first()
if (!allowed) {
return response.unauthorized('Access to this resource is denied')
}
// Proceed with update
project.merge(request.only(['name', 'description']))
await project.save()
return project
}
}
This pattern makes the authorization relationship explicit and avoids relying on route-level assumptions.
Example 3: Middleware-based scope binding
You can create an implicit binding in middleware so that controllers always receive a pre-scoped query builder.
// start/hooks.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import ApiKey from 'App/Models/ApiKey'
export const bindProjectScope = async (ctx: HttpContextContract, next: () => Promise) => {
const key = await ApiKey.findByOrFail('key', ctx.request.header('X-API-Key'))
ctx.binder('project', () =>
key.related('projects').query()
)
await next()
}
// routes.ts
Route.get('/projects/:id', 'ProjectController.show').bind('project', (query, _params, { binder }) => {
// The binder ensures the query is scoped to the key’s projects
return query.where('id', params.id)
})
These examples illustrate how to integrate API key authentication with object-level authorization in AdonisJS, mitigating IDOR by ensuring that references to objects are checked against the authenticated key’s allowed set.
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 |