Browse docs
Browse docs
Understanding the fundamental building blocks of Role-Based Access Control in Dashforge.
A subject represents the user or entity requesting access. Every subject has an ID, a list of assigned roles, and optional attributes for advanced permission logic.
import type { Subject } from '@dashforge/rbac';
const currentUser: Subject = {
id: 'user-123',
roles: ['editor', 'finance-reviewer'],
attributes: {
department: 'sales',
region: 'us-west',
employeeLevel: 3
}
};The roles array determines which permissions apply to the subject. The attributes object stores additional context used in conditional permissions (e.g., checking if a user owns a resource or belongs to a specific department).
A permission is a rule that defines an action that can be performed on a resource. Actions and resources are strings you define based on your domain model.
import type { Permission } from '@dashforge/rbac';
const readBookingPermission: Permission = {
action: 'read',
resource: 'booking'
};
const deleteBookingPermission: Permission = {
action: 'delete',
resource: 'booking'
};
const editCustomerPermission: Permission = {
action: 'edit',
resource: 'customer'
};Permissions use a simple action-resource pattern. Choose action names that match your business operations: read, edit, delete, approve, export, etc.
Permissions can have an effect of either allow (grant access) or deny (explicitly block access). If omitted, the effect defaults to allow.
const permissions: Permission[] = [
// Allow access (default effect)
{
action: 'read',
resource: 'booking'
},
// Explicitly deny access (blocks even if other roles allow)
{
action: 'delete',
resource: 'booking',
effect: 'deny'
}
];A role is a named collection of permissions. Roles let you assign multiple permissions to users as a group, rather than assigning permissions individually.
import type { Role } from '@dashforge/rbac';
const editorRole: Role = {
name: 'editor',
permissions: [
{ action: 'read', resource: 'booking' },
{ action: 'edit', resource: 'booking' },
{ action: 'read', resource: 'customer' },
{ action: 'edit', resource: 'customer' }
]
};
const viewerRole: Role = {
name: 'viewer',
permissions: [
{ action: 'read', resource: 'booking' },
{ action: 'read', resource: 'customer' }
]
};Roles can inherit permissions from other roles using the inherits property. This creates a hierarchy where higher-level roles automatically include all permissions from lower-level roles.
const roles: Role[] = [
// Base role with read-only access
{
name: 'viewer',
permissions: [
{ action: 'read', resource: 'booking' },
{ action: 'read', resource: 'customer' }
]
},
// Editor inherits viewer permissions + adds edit permissions
{
name: 'editor',
inherits: ['viewer'],
permissions: [
{ action: 'edit', resource: 'booking' },
{ action: 'edit', resource: 'customer' }
]
},
// Admin inherits editor permissions + adds delete permissions
{
name: 'admin',
inherits: ['editor'],
permissions: [
{ action: 'delete', resource: 'booking' },
{ action: 'delete', resource: 'customer' },
{ action: 'manage', resource: 'users' }
]
}
];
// Result: admin can read, edit, and delete (inherited from viewer + editor + own permissions)When a role inherits from another role, it gains all permissions from the parent role recursively. You can inherit from multiple roles by providing an array of role names.
A policy is the top-level RBAC configuration that contains all roles for your application. It's passed to RbacProvider to configure the access control system.
import type { RbacPolicy } from '@dashforge/rbac';
const policy: RbacPolicy = {
roles: [
{
name: 'viewer',
permissions: [
{ action: 'read', resource: 'booking' },
{ action: 'read', resource: 'customer' }
]
},
{
name: 'editor',
inherits: ['viewer'],
permissions: [
{ action: 'edit', resource: 'booking' },
{ action: 'edit', resource: 'customer' }
]
},
{
name: 'admin',
inherits: ['editor'],
permissions: [
{ action: 'delete', resource: 'booking' },
{ action: 'delete', resource: 'customer' },
{ action: 'manage', resource: 'users' }
]
}
]
};Policies are typically defined once at application startup and remain constant throughout the session. Dynamic policy changes require creating a new RBAC engine instance.
Permissions can include a condition function that evaluates whether the permission applies based on runtime context. Conditions enable dynamic access control like "users can only edit their own bookings" or "managers can approve within their department."
import type { Permission, ConditionContext } from '@dashforge/rbac';
const permissions: Permission[] = [
// Allow all editors to read any booking
{
action: 'read',
resource: 'booking'
},
// Allow editing only if user owns the booking
{
action: 'edit',
resource: 'booking',
condition: (context: ConditionContext) => {
const booking = context.resourceData as { ownerId: string };
return booking?.ownerId === context.subject.id;
}
},
// Allow deletion only for same department
{
action: 'delete',
resource: 'booking',
condition: (context: ConditionContext) => {
const booking = context.resourceData as { department: string };
const userDept = context.subject.attributes?.department as string;
return booking?.department === userDept;
}
}
];Condition functions receive a ConditionContext object containing:
subject — The current user (with id, roles, attributes)resourceData — Optional data about the specific resource being accessed (e.g., booking object)environment — Optional environment context (e.g., current time, request IP)// Example: Allow access only during business hours
{
action: 'approve',
resource: 'expense',
condition: (context: ConditionContext) => {
const now = context.environment?.currentTime as Date || new Date();
const hour = now.getHours();
return hour >= 9 && hour < 17; // 9 AM to 5 PM
}
}When a subject has multiple roles with conflicting permissions, the RBAC engine follows a clear precedence rule: deny always wins.
If any permission with effect: 'deny' matches the access request, access is denied — even if other permissions would allow it.
const policy: RbacPolicy = {
roles: [
{
name: 'editor',
permissions: [
{ action: 'edit', resource: 'booking' } // allow (default)
]
},
{
name: 'restricted',
permissions: [
{ action: 'edit', resource: 'booking', effect: 'deny' } // explicit deny
]
}
]
};
// Subject with both roles
const subject: Subject = {
id: 'user-123',
roles: ['editor', 'restricted']
};
// Result: DENIED
// deny from 'restricted' role takes precedence over allow from 'editor' roleThis precedence rule ensures that deny permissions act as absolute blocks, useful for temporarily revoking access or implementing blacklists.
The RBAC system supports * (asterisk) wildcards in both actions and resources to grant broad permissions without listing every individual action or resource.
const adminRole: Role = {
name: 'admin',
permissions: [
// Allow all actions on all resources
{ action: '*', resource: '*' },
// Allow all actions on bookings specifically
{ action: '*', resource: 'booking' },
// Allow reading all resources
{ action: 'read', resource: '*' },
// Wildcard works with deny too
{ action: 'delete', resource: '*', effect: 'deny' } // deny all deletes
]
};action: '*' matches any action (read, edit, delete, approve, etc.)resource: '*' matches any resource (booking, customer, user, etc.){ action: '*', resource: '*' } grants unlimited access (use with caution)* only, no partial matches like read-*Name actions and resources based on your business domain, not technical implementation. Use approve-expense not post-api-expense-approve.
Design roles around job functions (editor, approver, viewer) rather than individual permissions. Assign users to roles, not individual permissions.
Build role hierarchies with inherits. Start with minimal base roles (viewer) and extend upward (editor, admin). Reduces duplication.
Conditions add complexity. Use them only when permissions depend on resource ownership or runtime context. Prefer static permissions when possible.
The RBAC engine denies access by default. Only explicitly granted permissions are allowed. This "fail-secure" approach prevents accidental privilege escalation.
Write unit tests for critical permissions using createRbacEngine. Test that admins can delete, viewers cannot edit, etc. Catch policy bugs early.