Broken Access Control in Adonisjs with Jwt Tokens
Broken Access Control in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when authorization checks are missing or bypassed, allowing authenticated users to access or modify resources they should not. In AdonisJS, using JWT tokens for authentication increases the risk if authorization is not explicitly enforced for every sensitive route or resource. JWTs typically carry claims such as roles or permissions, but the framework does not automatically enforce these claims; developers must implement and apply authorization logic consistently. If route-level guards rely only on the presence of a valid token and do not validate scopes or roles, an attacker who obtains a low-privilege token can escalate access by manipulating or omitting role claims.
Common misconfigurations include failing to validate the token’s payload before allowing access to controllers, or assuming middleware like authentication guards also handle authorization. In AdonisJS, the authentication guard verifies the token signature and establishes an authenticated user, but it does not restrict what that user can do. Without explicit role- or permission-based checks, endpoints that return user data, perform administrative actions, or modify records become vulnerable. For example, an endpoint like GET /api/users/:id might use authentication to confirm identity but fail to ensure the requesting user can only access their own data unless the resolver enforces ownership rules.
Another scenario involves using JWTs with groups or roles stored in the payload. If the application decodes the token on the client or middleware but does not revalidate critical claims on the server for each request, tampered tokens can elevate privileges. Additionally, when APIs expose nested resources (e.g., /organizations/:orgId/projects/:projectId), missing checks that bind the authenticated user’s organization membership to the requested resource enable Insecure Direct Object References (IDOR), a subset of Broken Access Control. AdonisJS does not inherently bind JWT claims to route parameters; developers must write logic that cross-references token claims with database records to ensure subject- and tenant-level authorization.
SSRF and other server-side risks can compound these issues if token handling code introduces unsafe external calls, but the core issue remains missing or inconsistent authorization checks. Because JWTs are self-contained, developers might overlook the need to verify permissions on each request, especially for high-risk methods like DELETE or PATCH. The framework provides hooks to attach user information to the request context, but if those hooks are not paired with per-action authorization policies, the API surface remains over-permissive.
To detect this using a black-box scan, middleBrick runs checks such as BOLA/IDOR and Property Authorization against endpoints that authenticate via JWTs but lack explicit authorization tests. The scanner validates whether responses differ based on role or ownership constraints, and whether sensitive operations require more than a valid token. These checks highlight where access controls are missing, enabling teams to align implementation with frameworks like OWASP API Top 10 A01:2023.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on enforcing role- and ownership-based checks after authentication, using JWT claims explicitly, and avoiding implicit trust in the token payload. Always validate and sanitize input parameters, and ensure that every data access respects the authenticated user’s scope.
Example 1: Role-based access with JWT claims
Store roles in the JWT payload and enforce them in routes or controllers. Do not rely on client-side decoding.
// config/jwt.ts snippet
export const jwt = {
secret: 'your-super-secret-key',
token: {
expiresIn: '7d',
},
}
// In a controller
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
export default class ProjectsController {
public async show({ request, auth, response }: HttpContextContract) {
const user = await auth.authenticate() // AdonisJS JWT auth
const projectId = request.param('id')
const project = await Project.find(projectId)
if (!project) {
return response.notFound({ message: 'Project not found' })
}
// Role-based check using JWT-derived role
if (user.role !== 'admin' && user.id !== project.userId) {
return response.forbidden({ message: 'Access denied' })
}
return response.ok(project)
}
}
Example 2: Ownership check with route parameters
Bind JWT user identity to resource ownership explicitly.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Organization from 'App/Models/Organization'
export default class OrganizationsController {
public async update(
{ request, auth, response, params }: HttpContextContract,
orgId: number
) {
const user = await auth.authenticate()
const org = await Organization.find(orgId)
if (!org) {
return response.notFound({ message: 'Organization not found' })
}
// Ensure the authenticated user owns the organization
if (org.ownerId !== user.id) {
return response.forbidden({ message: 'Not authorized to update this organization' })
}
// Proceed with update
const data = request.only(['name', 'description'])
org.merge(data)
await org.save()
return response.ok(org)
}
}
Example 3: Middleware for tenant-aware authorization
Use route-level middleware to validate tenant membership before allowing access.
// start/hooks.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
export const validateTenantAccess = async (ctx: HttpContextContract) => {
const user = await ctx.auth.authenticate()
const orgId = ctx.request.param('orgId')
const membership = await UserMembership.findBy({ userId: user.id, organizationId: orgId })
if (!membership) {
ctx.response.unauthorized({ message: 'Tenant access denied' })
}
}
// routes.ts
Route.resource('projects', 'ProjectsController')
.apiOnly()
.middleware({ api: ['validateTenantAccess'] })
Best practices summary
- Always re-authorize on the server using data from your database; do not trust JWT claims alone for sensitive operations.
- Use middleware to centralize tenant and role checks, keeping controllers focused on business logic.
- Apply the principle of least privilege: scope queries by user ID or organization ID to prevent IDOR.
- Combine authentication guards with explicit authorization logic; avoid assuming a valid token equals permitted actions.
middleBrick can validate these patterns by scanning endpoints that use JWT authentication and checking for missing ownership or role-based checks. Its findings map to relevant compliance controls and provide prioritized remediation guidance.