HIGH insecure direct object referenceadonisjsapi keys

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 IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

How can I test for IDOR in my AdonisJS API using API keys?
Use a valid API key to access a known resource ID that does not belong to the key’s scope. If the request succeeds instead of returning 403/404, an IDOR may exist. Combine this with automated scans that correlate API key relationships with route parameters to detect missing ownership checks.
Does input validation alone prevent IDOR in AdonisJS with API keys?
No. Input validation ensures the parameter is well-formed (e.g., a positive integer or valid UUID), but it does not confirm that the authenticated API key is authorized for that specific object. Authorization must explicitly verify the relationship between the key and the resource.