HIGH insecure direct object referenceadonisjsjwt tokens

Insecure Direct Object Reference in Adonisjs with Jwt Tokens

Insecure Direct Object Reference in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Insecure Direct Object Reference (IDOR) occurs when an API exposes a reference to an internal object—such as a database row or file—and allows an authenticated subject to perform unauthorized actions on that object. In AdonisJS, IDOR commonly appears in REST routes that accept a user-supplied identifier (e.g., id) and directly load a model without verifying that the authenticated subject has the right to access or modify that specific record. When JWT tokens are used for authentication, the risk is compounded if authorization logic is incomplete or inconsistent with the token’s claims.

Consider an endpoint like GET /users/:id. If the route handler loads a user by id and returns the profile without confirming that the requesting user is the same user (or an admin), an attacker can tamper with the :id path parameter to enumerate or access other users’ data. Even when JWT tokens carry a subject identifier (e.g., sub), failing to compare the token’s subject with the requested id results in an IDOR. For example, a token for user A can be used to request /users/123 while user A is actually user 456, and the server may incorrectly serve user 123’s data because it only checks authentication (token validity) and not authorization (ownership or role-based access).

AdonisJS applications that rely on JWT packages such as @adonisjs/auth typically integrate guards and an Authenticator to manage tokens. The vulnerability emerges when developer code retrieves the authenticated user from the token but then uses an unvalidated client-supplied identifier to query other resources. Route-level middleware may enforce that a token exists, but if route logic does not enforce a relationship check—such as ensuring the requested resource’s user_id matches the token’s subject or the authenticated user’s role—IDOR persists. This is especially common in controllers that use implicit ownership assumptions, like assuming the requesting user is always the resource owner without explicit checks.

Real-world examples align with common attack patterns enumerated in OWASP API Top 10 (e.g., API1:2023 Broken Object Level Authorization). Consider a scenario where an endpoint allows users to update their profile: PUT /profile/:profileId. If the handler loads Profile.findOrFail(profileId) and updates it after confirming the user has a valid JWT, but does not verify that the profile belongs to the user identified in the token, an attacker can iterate over numeric IDs and modify arbitrary profiles. Similarly, in multi-tenant scenarios where data isolation should be enforced via tenant IDs, missing tenant checks on JWT-authenticated requests can lead to cross-tenant IDOR.

To detect this during a black-box scan, middleBrick runs checks such as BOLA/IDOR and Property Authorization in parallel with standard authentication and JWT validation probes. These checks attempt to access or modify resources using altered identifiers while holding a valid JWT, looking for unauthorized data exposure or modification. The scanner does not fix the logic, but highlights findings with severity and remediation guidance, helping developers align route and model logic with principles like least privilege and proper ownership verification.

Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes

Remediation centers on ensuring that every data access or mutation validates the relationship between the authenticated subject (as expressed in the JWT) and the target resource. Below are concrete code examples using AdonisJS v6+ conventions, including JWT-based authentication with @adonisjs/auth.

1. Validate ownership on user profile endpoints

Ensure that a user can only access or update their own profile by comparing the token subject with the requested identifier.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'

export default class ProfilesController {
  public async show({ params, auth }: HttpContextContract) {
    const user = auth.user! // Authenticated via JWT
    // Enforce ownership: only allow access when IDs match
    if (user.id !== params.id) {
      throw new Error('Unauthorized') // Or handle with a proper Forbidden response
    }
    const profile = await user.related('profile').query().first()
    return profile
  }

  public async update({ params, auth, request }: HttpContextContract) {
    const user = auth.user!
    if (user.id !== params.id) {
      throw new Error('Unauthorized')
    }
    const payload = request.only(['displayName', 'bio'])
    const profile = await user.related('profile').query().firstOrFail()
    profile.merge(payload)
    await profile.save()
    return profile
  }
}

2. Enforce tenant or scope checks for multi-tenant data

When records belong to a tenant, verify that the authenticated user’s tenant matches the record’s tenant, in addition to validating JWT claims.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Ticket from 'App/Models/Ticket'
import auth from '@ioc:Adonis/Addons/Auth'

export default class TicketsController {
  public async index({ auth, request }: HttpContextContract) {
    const user = auth.user!
    const tenantId = request.qs().tenantId
    // Ensure user can only list tickets for their tenant
    const tickets = await Ticket.query()
      .where('tenantId', tenantId)
      .andWhere('createdBy', user.id)
      .exec()
    return tickets
  }
}

3. Apply role-based checks when ownership is not sufficient

For admin or privileged operations, combine JWT claims (e.g., roles) with resource ownership or explicit allow-lists.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Post from 'App/Models/Post'

export default class PostsController {
  public async destroy({ params, auth }: HttpContextContract) {
    const user = auth.user!
    const post = await Post.findOrFail(params.id)
    // Only allow if user owns the post or has admin role in the JWT
    if (post.userId !== user.id && !user.roles.includes('admin')) {
      throw new Error('Forbidden')
    }
    await post.delete()
    return { message: 'deleted' }
  }
}

4. Centralize checks with route-level middleware

Define a reusable middleware that ensures the authenticated user matches the resource owner for sensitive routes, reducing repetitive conditionals.

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'

export const ensureOwnership = async (ctx: HttpContextContract, next: () => Promise) => {
  const user = ctx.auth.user!
  const resourceId = ctx.params.resourceId
  const resource = await User.find(resourceId)
  if (!resource || resource.id !== user.id) {
    ctx.response.status = 403
    ctx.response.body = { error: 'Forbidden: you do not own this resource' }
    return
  }
  await next()
}

// In routes.ts
Route.get('/resources/:resourceId', 'ResourceController.show').middleware(ensureOwnership)

5. Use schema-level guards and avoid exposing internal IDs

Consider using opaque identifiers (e.g., UUIDs or tokens) instead of sequential integers to make enumeration harder, and enforce strict scoping in models and policies.

import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
import { v4 as uuidv4 } from 'uuid'

export default class SecureResource extends BaseModel {
  @column({ isPrimary: true, serialize: false })
  public id: number

  @column()
  public publicId: string

  protected $prePersist: any[] = [
    function assignPublicId() {
      this.publicId = uuidv4()
    }
  ]

  // Use publicId in routes instead of numeric id
}

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 does middleBrick detect IDOR in JWT-authenticated endpoints?
middleBrick runs parallel checks including BOLA/IDOR and Property Authorization. It probes authenticated endpoints with altered resource identifiers while holding a valid JWT, looking for unauthorized data access or modification that indicates missing ownership or scope checks.
Can JWT tokens alone prevent IDOR in AdonisJS?
No. JWT tokens provide authentication (verifying identity) but do not enforce authorization. Without explicit checks that the authenticated subject is allowed to access the specific resource (e.g., comparing token subject to record IDs or tenant IDs), IDOR vulnerabilities can still exist.