Agora

Getting started

Install @adonis-agora/authz, run the migration, grant a permission and check it through Bouncer.

Install & configure

npm i @adonis-agora/authz
node ace configure @adonis-agora/authz

configure registers the provider and commands, and publishes:

  • config/authz.ts — the store configuration;
  • app/abilities/authz.ts — the can / hasRole Bouncer abilities;
  • a Lucid migration for the RBAC tables.

Decide who creates the tables

The lucid store manages its own schema: with autoCreateSchema left at its default it creates authz_roles, authz_permissions, authz_role_permission, authz_user_role and authz_user_permission on first use. No users table is touched — users are referenced polymorphically. You can skip straight to the next step and it will work.

For production, take the tables under your own control instead — schema changes then ship with your deploy rather than on first request. Turn the automatic creation off and run the migration configure published:

config/authz.ts
stores: {
  lucid: stores.lucid({ autoCreateSchema: false }),
}
node ace migration:run

Both paths create the same tables, so switching later is safe — see Configuration.

Grant a permission

From the CLI:

node ace authz:grant editor posts.edit   # permission → role
node ace authz:assign editor 42          # role → user (id 42)

…or programmatically:

import { AuthzService } from '@adonis-agora/authz'
import app from '@adonisjs/core/services/app'

const service = await app.container.make(AuthzService)
await service.store.givePermissionToRole('editor', 'posts.edit')
await service.store.assignRole({ type: 'user', id: '42' }, 'editor')

Writing to the store needs the service instance. For reading a decision anywhere in the app, import the singleton instead — no container call:

import authz from '@adonis-agora/authz/services/main'

await authz.can(user, 'posts.edit')
await authz.hasRole(user, 'editor')

Check it through Bouncer

import { can, hasRole } from '#abilities/authz'

router.put('/posts/:id', async (ctx) => {
  const post = await Post.findOrFail(ctx.params.id)

  // boolean check (wildcards apply: posts.* ⊇ posts.edit)
  if (await ctx.bouncer.allows('can', 'posts.edit', post)) {
    // ...
  }

  // throwing check (HTTP 403 on deny)
  await ctx.bouncer.authorize('hasRole', 'admin')
})

In Edge templates:

@can('can', 'posts.edit')
  <a href="/posts/{{ post.id }}/edit">Edit</a>
@end

The resource argument (post above) is accepted for ergonomic call sites, but RBAC grants are model-less, so the default can ability does not inspect it. Combine @adonis-agora/authz with a regular Bouncer policy when you need per-record ownership checks.

On this page