Broken Access Control in Adonisjs
How Broken Access Control Manifests in Adonisjs
Broken Access Control in Adonisjs applications often stems from improper authorization checks that allow authenticated users to access resources they shouldn't have permission to view or modify. In Adonisjs, this typically manifests through several specific patterns.
One common vulnerability occurs when developers rely solely on route middleware for authorization. Consider a user management system where an admin route is protected by an auth middleware:
Route.get('/users/:id', 'UserController.show').middleware(['auth'])This only verifies the user is authenticated, not whether they have permission to view that specific user's data. An attacker can easily enumerate user IDs by incrementing the parameter value, accessing other users' profiles.
Another Adonisjs-specific pattern involves improper use of Lucid models without proper scoping. When fetching related models:
const user = await User.find(params.id)
const posts = await user.posts().fetch()If the posts relationship doesn't filter by user ownership or role, any authenticated user can access all posts in the system.
Middleware-based authorization in Adonisjs can also be bypassed when developers use broad patterns like:
async handle({ auth, params }, next) {
const user = await auth.getUser()
if (user) return await next()
return response.status(401).send('Unauthorized')
}This only checks authentication, not authorization. A more secure approach would verify the user's role or ownership of the resource being accessed.
Adonisjs's implicit model binding can introduce vulnerabilities when not properly secured. Consider:
async show({ params }) {
const user = await User.findOrFail(params.id)
return user
}Without checking that the authenticated user matches the requested user ID or has admin privileges, this endpoint is vulnerable to IDOR (Insecure Direct Object Reference) attacks.
Adonisjs-Specific Detection
Detecting Broken Access Control in Adonisjs applications requires examining both the codebase and runtime behavior. When scanning with middleBrick, several Adonisjs-specific patterns are tested.
middleBrick's BOLA (Broken Object Level Authorization) checks specifically target Adonisjs route patterns. The scanner analyzes routes that accept ID parameters and tests whether authenticated users can access resources belonging to other users. For example, it will attempt to access /users/2 while authenticated as user 1 to detect IDOR vulnerabilities.
The scanner examines middleware usage patterns in your Adonisjs application. It identifies routes protected only by auth middleware without additional authorization checks. middleBrick flags endpoints where the middleware chain includes 'auth' but lacks role-based or resource-based authorization middleware.
middleBrick tests property authorization by examining model relationships and access patterns. In Adonisjs, it specifically looks for:
const user = await User.find(params.id)
return user.toJSON() // Exposes all user propertiesThe scanner checks if sensitive properties like email, phone, or admin status are exposed to unauthorized users. It also tests whether model relationships return data from other users' records.
For LLM/AI security specific to Adonisjs, middleBrick tests for AI endpoint vulnerabilities that might be present in applications using AI integrations. This includes checking for unauthenticated access to AI features and testing for prompt injection vulnerabilities in any AI-related endpoints.
The scanner's runtime testing sends authenticated requests with varying user contexts to detect whether authorization checks properly restrict access. It maps findings to OWASP API Top 10 categories, providing a security score that reflects the severity of access control issues.
Adonisjs-Specific Remediation
Remediating Broken Access Control in Adonisjs requires implementing proper authorization patterns using the framework's built-in features. Start by replacing simple auth middleware with role-based authorization:
Route.get('/users/:id', 'UserController.show').middleware(['auth', 'requireRole:admin'])Create a custom middleware for resource ownership verification:
const { HttpContextContract } = require('@adonisjs/core/http')
class EnsureResourceOwnership {
async handle({ auth, params }, next, properties) {
const user = await auth.getUser()
const resource = await User.find(params.id)
if (resource.user_id !== user.id) {
return response.status(403).send('Forbidden')
}
return await next()
}
}
module.exports = EnsureResourceOwnershipApply this middleware to routes that should only be accessible by resource owners.
Use Adonisjs policies for centralized authorization logic:
const { HttpContextContract } = require('@adonisjs/core/http')
class UserPolicy {
async view(user, targetUser) {
return user.id === targetUser.id || user.isAdmin
}
}
module.exports = UserPolicyThen in your controller:
async show({ params, response }) {
const user = await User.find(params.id)
const policy = new UserPolicy()
if (!(await policy.view(auth.user, user))) {
return response.status(403).send('Forbidden')
}
return user
}Implement model scopes to automatically filter data by user:
class Post extends BaseModel {
static boot() {
super.boot()
this.addTrait('Serializable')
}
static get computed() {
return ['isVisible']
}
getIsVisible({ $query }) {
return $query.where('user_id', auth.user.id)
}
}For API endpoints, use Adonisjs's auth guards to verify permissions:
async show({ auth, params }) {
const user = await auth.use('api').authenticate()
const targetUser = await User.find(params.id)
if (user.id !== targetUser.id && !user.isAdmin) {
return response.status(403).send('Forbidden')
}
return targetUser
}Consider using Adonisjs's built-in authorization gates for complex permission systems:
const Gate = use('Gate')
Gate.define('viewUser', (user, targetUser) => {
return user.id === targetUser.id || user.isAdmin
})Then in controllers:
if (!(await Gate.allow('viewUser', auth.user, targetUser))) {
return response.status(403).send('Forbidden')
}