The AdonisJS Starter Kit implements a robust authorization system using AdonisJS Bouncer for policy-based access control. The system includes role-based permissions, resource-level authorization, and fine-grained access control.
Authorize actions in your controllers using the bouncer service:
app/users/controllers/users_controller.ts
import type { HttpContext } from '@adonisjs/core/http'import UserPolicy from '#users/policies/user_policy'export default class UsersController { public async index({ bouncer, inertia, request }: HttpContext) { // Check if user can view list await bouncer.with(UserPolicy).authorize('viewList') // ... fetch and return users } public async update({ bouncer, params, request, response }: HttpContext) { const user = await User.findOrFail(params.id) // Check if current user can update this specific user await bouncer.with(UserPolicy).authorize('update', user) // ... update user } public async destroy({ bouncer, params, response }: HttpContext) { const user = await User.findOrFail(params.id) // Check if current user can delete this specific user await bouncer.with(UserPolicy).authorize('delete', user) await user.delete() return response.redirect().toRoute('users.index') }}
If authorization fails, Bouncer throws an E_AUTHORIZATION_FAILURE exception which results in a 403 Forbidden response.
import { belongsTo, column, computed } from '@adonisjs/lucid/orm'import type { BelongsTo } from '@adonisjs/lucid/types/relations'import Role from '#users/models/role'import Roles from '#users/enums/role'export default class User extends compose(BaseModel, AuthFinder) { @column({ isPrimary: true }) declare id: number @column() declare roleId: number @belongsTo(() => Role) declare role: BelongsTo<typeof Role> @computed() get isAdmin() { return this.roleId === Roles.ADMIN }}
import type { HttpContext } from '@adonisjs/core/http'import type { NextFn } from '@adonisjs/core/types/http'export default class InitializeBouncerMiddleware { async handle(ctx: HttpContext, next: NextFn) { // Bouncer is automatically available via ctx.bouncer return next() }}
public async updateProfile({ auth, request }: HttpContext) { // Users can always update their own profile const user = auth.user! user.merge(await request.validateUsing(updateProfileValidator)) await user.save()}