Authorization
AuthorizeComponent wires up enforcer-based route authorization - RBAC through Casbin (with an optional multi-tenant domain-scoped model), voters, and role shortcuts - evaluated by the authorize() middleware after authentication.
In one example
The recommended setup: scoped Casbin RBAC backed by one PolicyDefinition edge table, then a protected route.
import {
AuthorizeBindingKeys, AuthorizeComponent, AuthorizationDecisions, AuthorizationEnforcerRegistry,
AuthorizationEnforcerTypes, CasbinAuthorizationEnforcer, CasbinEnforcerModelDrivers,
ScopedCasbinAdapter, CASBIN_RBAC_DOMAIN_SCOPED_MODEL, BaseApplication, IAuthorizeOptions,
} from '@venizia/ignis';
export class Application extends BaseApplication {
preConfigure() {
// 1. Global options
this.bind<IAuthorizeOptions>({ key: AuthorizeBindingKeys.OPTIONS }).toValue({
defaultDecision: AuthorizationDecisions.DENY,
alwaysAllowRoles: ['999_super-admin'],
});
// 2. Component (validates options, binds alwaysAllowRoles)
this.component(AuthorizeComponent);
// 3. Enforcer, registered by class + name + type + co-located options
const adapter = new ScopedCasbinAdapter({
dataSource, // a BasePostgresDataSource instance
entities: {
policyDefinition: { tableName: 'PolicyDefinition', schemaName: 'identity' },
permission: { tableName: 'Permission', schemaName: 'identity' },
principals: { user: 'User', role: 'Role' },
domainTypes: ['Merchant'],
softDelete: { use: true, columnName: 'deleted_at' },
},
});
AuthorizationEnforcerRegistry.getInstance().register({
container: this,
enforcers: [{
enforcer: CasbinAuthorizationEnforcer,
name: 'casbin',
type: AuthorizationEnforcerTypes.CASBIN,
options: {
model: { driver: CasbinEnforcerModelDrivers.TEXT, definition: CASBIN_RBAC_DOMAIN_SCOPED_MODEL },
isScoped: true,
adapter,
cached: { use: false },
},
}],
});
}
}// Inside a controller's binding() - a route protected by authentication + authorization
const DELETE_ARTICLE_CONFIG = {
path: '/articles/{id}',
method: 'delete',
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
authorize: { action: AuthorizationActions.DELETE, resource: 'Article' },
responses: jsonResponse({ description: 'Deleted article', schema: ArticleSchema }),
} as const;How it works
- Enforcer-based and pluggable.
authorize({ spec })returns Hono middleware built byAuthorizationProvider. It resolves anIAuthorizationEnforcerfromAuthorizationEnforcerRegistryby name (default: the first registered) - swapCasbinAuthorizationEnforcerfor a custom class without touching route configs. - Runs after authentication. The middleware reads
Authentication.CURRENT_USERfrom the Hono context, soAuthenticateComponentmust run first and the route needs anauthenticateconfig alongsideauthorize. - No enforcers registered = no-op. If
AuthorizationEnforcerRegistry.hasEnforcers()isfalse, the middleware callsnext()and skips authorization entirely - useful during incremental rollout, dangerous if you forget to register an enforcer in production. - Casbin's scoped RBAC model is the recommended engine.
CASBIN_RBAC_DOMAIN_SCOPED_MODEL+isScoped: true+ScopedCasbinAdapterreads one principal's role/permission/domain edges (plus the shared role/resource/action/domain hierarchy) from a singlePolicyDefinitiontable, and supports multi-tenant grants scoped toSYSTEM_WIDE,ANY_MEMBER, or a specific<Type>_<id>domain. - Per-request enforcers, cached lines. Each Casbin evaluation borrows an isolated enforcer from an internal pool, loads that user's policy lines into it, and evaluates - the datasource query only runs on cache miss (or always, if
cached.use: false).
Pipeline (7 steps, short-circuits marked)
| # | Step | Short-circuits when |
|---|---|---|
| 1 | Authorization.SKIP_AUTHORIZATION check | Set -> next() |
| 2 | Read Authentication.CURRENT_USER | Missing -> 401 |
| 3 | Role shortcuts (alwaysAllowRoles + allowedRoles) | Match -> next() |
| 4 | Voters (per-route) | ALLOW/DENY -> next() / 403 |
| 5 | Resolve enforcer | None registered -> next() |
| 6 | Build/cache rules (+ resolve domain, if any) | - |
| 7 | enforcer.evaluate() | DENY/ABSTAIN-as-deny -> 403 |
Common tasks
Secure a route. Add authorize: { action, resource } next to authenticate in a route config or @get/@post decorator.
authorize: { action: AuthorizationActions.READ, resource: 'Article' }Bypass the enforcer for trusted roles. alwaysAllowRoles (global, in IAuthorizeOptions) or allowedRoles (per-route spec) skip straight to next() once a matching role is found.
authorize: { action: AuthorizationActions.DELETE, resource: 'Article', allowedRoles: [AuthorizationRoles.ADMIN.identifier] }Add custom logic before the enforcer. A voter returns ALLOW/DENY/ABSTAIN; the first non-ABSTAIN decision wins.
const ownerVoter: TAuthorizationVoter = async ({ user, context }) =>
(await articleService.findById({ id: context.req.param('id') }))?.authorId === user.userId
? AuthorizationDecisions.ALLOW
: AuthorizationDecisions.ABSTAIN;Scope a grant to a tenant. Declare where the domain comes from per route (or globally via IAuthorizeOptions.domainResolver).
authorize: { action: 'read', resource: 'Order', domain: { from: 'param', key: 'merchantId', type: 'Merchant' } }Reference a model instead of a hardcoded string. @model({ settings: { authorize: { principal } } } }) auto-populates Model.AUTHORIZATION_SUBJECT.
authorize: { action: AuthorizationActions.READ, resource: Article.AUTHORIZATION_SUBJECT }See also
- Usage & Examples - securing routes, voters, CRUD factory integration, domain scoping in depth
- API Reference - architecture, enforcer internals, provider pipeline, registry, adapters
- Error Reference - every error message and how to fix it
- Authentication - runs before authorization, populates
Authentication.CURRENT_USER - Components Overview - component system basics
- Persistent Models - declaring
AUTHORIZATION_SUBJECTon a model - Security Guidelines - authorization best practices
Files:
packages/core/src/components/auth/authorize/component.ts-AuthorizeComponentpackages/core/src/components/auth/authorize/common/- constants, binding keys, types, policy/permission builderspackages/core/src/components/auth/authorize/providers/authorization.provider.ts-AuthorizationProvider(the 7-step pipeline)packages/core/src/components/auth/authorize/enforcers/-CasbinAuthorizationEnforcer,AuthorizationEnforcerRegistry,CASBIN_RBAC_DOMAIN_SCOPED_MODELpackages/core/src/components/auth/authorize/adapters/-BaseFilteredAdapter,ScopedCasbinAdapterpackages/core/src/components/auth/authorize/models/authorization-role.model.ts-AuthorizationRolepackages/core/src/base/metadata/persistents.ts-@modelauto-populatingAUTHORIZATION_SUBJECTfromsettings.authorize.principal