Skip to content

Role-Based and Permission-Based Authorization Strategy Guide

Declarative Access Control: Roles and Permissions

Section titled “Declarative Access Control: Roles and Permissions”

Securing message execution branches across a decoupled application architecture requires mapping verified identities to discrete capability constraints. Xeno implements these boundaries using specialized pipeline strategies that evaluate role and permission matrices directly against message intents before invoking business logic.


Introduction to Role and Permission Authorization Policies

Section titled “Introduction to Role and Permission Authorization Policies”

Role-based and permission-based authorization in Xeno secures CQRS message handlers by evaluating explicit user identities against a centralized policy registry. These strategies intercept incoming command and query intents within the mediator execution stack, validating access privileges prior to triggering core business domain transformations.

By moving security boundaries away from HTTP delivery endpoints and embedding them within the mediator bus, Xeno enforces consistent security across any ingress channel (e.g., HTTP REST, WebSockets, or background workers). This declarative approach leverages an IPolicyRegistry to track application policies, validating inbound message parameters against claims extracted from the user’s verified identity.

graph TD
A[Inbound Message dispatched via Mediator] --> B{Is Route Public?}
B -->|Yes| C[Bypass Security & Execute Handler]
B -->|No| D[BaseAuthorizationStrategy Context Guard]
D -->|Context Absent| E[Return Result.fail - 401 Unauthorized]
D -->|Context Present| F[Query PolicyRegistry via request.intent]
subgraph Authorization Evaluation Loop [Pipeline Strategies]
F --> G[RoleAuthorizationStrategy]
G -->|Role Mismatch| H[Return Result.fail - 403 Forbidden]
G -->|Role Validated| I[PermissionAuthorizationStrategy]
I -->|Permission Mismatch| H
end
I -->|All Policies Cleared| J[Invoke Target Application Handler]
style E fill:#ffebee,stroke:#ef5350,stroke-width:1px;
style H fill:#ffebee,stroke:#ef5350,stroke-width:1px;
style C fill:#e8f5e9,stroke:#43a047,stroke-width:1px;
style J fill:#e8f5e9,stroke:#43a047,stroke-width:1px;

Understanding the Mechanics of Role-Based Authorization

Section titled “Understanding the Mechanics of Role-Based Authorization”

The RoleAuthorizationStrategy protects application handlers by verifying that an authenticated identity possesses at least one matching group designation required by the target operation. It queries the policy registry using the unique message intent, returning a forbidden result monad upon a validation failure.

The RoleAuthorizationStrategy evaluates broad, group-level user classifications (e.g., admin, manager, guest). When a message passes through the mediator bus, the strategy intercepts execution, reads the unique intent string token from the request, and fetches the associated policy from the IPolicyRegistry.

If the policy contains a non-empty roles array, the strategy checks if the user’s roles (auth.roles) satisfy the requirements using an array intersection:

const roles = auth.roles ?? []
const hasRequiredRole = policy.roles?.some((role) => roles.includes(role))

If the authenticated actor does not possess at least one of the required group roles, the strategy short-circuits the pipeline execution loop, returning a failed Result enclosing a 403 Forbidden (STATUS_CODES.FORBIDDEN) application exception.

Key-Value Specification of Role Authorization Properties

Section titled “Key-Value Specification of Role Authorization Properties”
  • Base Class Evaluation — Extends BaseAuthorizationStrategy, automatically executing pre-flight checks to ensure the RequestContext is present and active.

  • Intent Token Mapping — Extracts the policy criteria dynamically by passing the unique message token (request.intent) directly to the registry engine.

  • Case-Sensitive Array Matching — Executes an array evaluation (policy.roles.some) matching user role signatures, requiring exact casing configurations between identity claims and registry definitions.


Understanding the Mechanics of Permission-Based Authorization

Section titled “Understanding the Mechanics of Permission-Based Authorization”

The PermissionAuthorizationStrategy implements granular access control by matching an identity’s capability scopes against the fine-grained permissions declared for a message payload. It isolates execution streams, ensuring that authenticated actors cannot execute specialized write commands or query lookups without valid capability tokens.

While roles represent high-level organizational buckets, permissions define granular functional capabilities (e.g., write:users, read:reports). This granularity is essential for zero-trust architectures where access control must be applied at the single-action layer.

The PermissionAuthorizationStrategy functions identically to the role-based subsystem but evaluates fine-grained capabilities (auth.permissions) against the targeted message policy. If an identity lacks the explicit permission signature demanded by the handler’s policy, execution halts instantly with an AppError.forbidden validation failure, preventing unauthorized state mutations or data leaks.

Key-Value Specification of Permission Authorization Properties

Section titled “Key-Value Specification of Permission Authorization Properties”
  • Granular Action Targeting — Restricts resource manipulation by verifying precise application capabilities regardless of the user’s role groupings.

  • Monadic Short-Circuiting — Intercepts execution before handler processing begins, returning an explicit failure state to maintain predictable control flows.

  • Decoupled Architecture — Evaluates permission logic completely outside persistence mechanisms, keeping the core domain logic clean and portable.


Bootstrapping Policies using Command and Query Intent Tokens

Section titled “Bootstrapping Policies using Command and Query Intent Tokens”

Registering role and permission authorization strategies requires provisioning declarative policy objects tied strictly to unique command or query intent tokens during bootstrap. The fluent AppBuilder maps these intent contracts within the PolicyRegistry, enabling automated validation when routing messages across mediator pipelines.

To ensure seamless execution routing, your authorization configuration must map directly to the unique intent properties of your commands and queries. Misaligning these tokens will cause the registry to skip checks or return validation failures at runtime.

1. Declaring the Typed Messages with Unique Intent Strings

Section titled “1. Declaring the Typed Messages with Unique Intent Strings”

Every command and query must declare an explicit intent token string that acts as its unique injection key within the framework ecosystem:

src/application/user/commands/delete-user.command.ts
import { type ICommand, REQUEST_TYPE } from '@xeno-js/core'
export class DeleteUserCommand implements ICommand<void> {
// The unique string intent acting as the message identifier token
public readonly intent = 'DELETE_USER_COMMAND_HANDLER_TOKEN'
public readonly type = REQUEST_TYPE.COMMAND
constructor(public readonly userId: string) {}
}
src/application/user/queries/get-analytics.query.ts
import { type IQuery, type ICacheableOptions, REQUEST_TYPE } from '@xeno-js/core'
export class GetAnalyticsQuery implements IQuery<Record<string, unknown>> {
public readonly intent = 'GET_ANALYTICS_QUERY_HANDLER_TOKEN'
public readonly type = REQUEST_TYPE.QUERY
public readonly cacheOptions: ICacheableOptions
constructor() {
this.cacheOptions = { cacheKey: this.intent, ttl: 300 }
}
}

2. Wiring Intent Policies inside the AppBuilder Bootstrapper

Section titled “2. Wiring Intent Policies inside the AppBuilder Bootstrapper”

During host assembly, use the .addPipeline() configuration block to register policies. This process binds message intent string keys directly to their required roles and permissions:

src/infrastructure/bootstrap.ts
import { AppBuilder } from '@xeno-js/core'
import type { AppRegistry } from './infrastructure/app-registry'
export const bootstrapApplication = async () => {
const builder = new AppBuilder<AppRegistry>()
builder
// Enable context isolation layers
.addContext()
.addMiddleware()
// Configure and provision the security pipeline stack
.addPipeline((options) => {
// Map declarative security requirements using explicit intent keys
options.authorization.policies = {
// Enforces role-based checks specifically on DeleteUserCommand
DELETE_USER_COMMAND_HANDLER_TOKEN: {
roles: ['super_admin', 'security_officer'],
},
// Enforces both role and permission constraints on GetAnalyticsQuery
GET_ANALYTICS_QUERY_HANDLER_TOKEN: {
roles: ['admin', 'analyst'],
permissions: ['read:analytics', 'export:reports'],
},
}
})
return await builder.build()
}

Xeno is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section