RBAC & Fine-Grained Authorization
Built-in role-based access control and fine-grained authorization - define permissions once, enforce everywhere automatically
The .do platform provides comprehensive access control out of the box. Define roles and permissions using platform primitives, and authorization is automatically enforced across your entire application.
How It Works
Authorization is built into every platform operation:
// Define roles and permissions
const roles = {
admin: {
can: ['project.create', 'project.read', 'project.update', 'project.delete', 'user.manage'],
description: 'Full access to all resources'
},
editor: {
can: ['project.create', 'project.read', 'project.update'],
description: 'Can create and edit projects'
},
viewer: {
can: ['project.read'],
description: 'Read-only access'
}
}
// Assign roles to users
await $.User.assign.Role({ userId, roleId: 'editor' })
// Platform automatically checks permissions
const projects = await $.Project.list()
// Only returns projects user is authorized to see
await $.Project.update(projectId, { name: 'New Name' })
// Platform checks: Does user have 'project.update' permission?
// If not, throws UnauthorizedExceptionRole-Based Access Control (RBAC)
Define roles that group permissions together:
Built-in Roles
Every application gets standard roles:
// Organization-level roles
{
owner: ['*'], // Full access to everything
admin: ['user.manage', '*.read', '*.write'],
member: ['*.read'],
guest: ['public.read']
}
// Resource-specific roles
{
'project:owner': ['project.delete', 'project.admin'],
'project:contributor': ['project.write', 'project.read'],
'project:viewer': ['project.read']
}Custom Roles
Define roles specific to your application:
// Define custom role
const customRole = await $.Role.create({
name: 'finance-analyst',
description: 'Access to financial data and reports',
permissions: [
'report.financial.read',
'report.financial.export',
'dashboard.finance.view',
'data.revenue.read',
'data.expenses.read'
]
})
// Assign to users
await $.User.assign.Role({
userId: analyst.id,
roleId: customRole.id
})
// Platform enforces automatically
const report = await $.Report.financial.generate()
// Platform checks: Does user have 'report.financial.read'?Fine-Grained Authorization (FGA)
Go beyond roles with attribute-based and relationship-based access control:
Attribute-Based Access
Control access based on user attributes, resource properties, and context:
// Define attribute-based policy
await $.Policy.create({
name: 'sensitive-data-access',
effect: 'allow',
actions: ['data.pii.read'],
conditions: {
// Must be in specific department
'user.department': ['legal', 'compliance'],
// Must have completed training
'user.training.privacy': true,
// Only during business hours
'request.time': { between: ['09:00', '17:00'] },
// Only from corporate network
'request.ipRange': '10.0.0.0/8'
}
})
// Platform evaluates policy automatically
const sensitiveData = await $.Data.pii.read()
// Checks all conditions, allows/denies accordinglyRelationship-Based Access
Control access based on relationships between users and resources:
// Define relationships
await $.Project.assign.owner({ projectId, userId })
await $.Project.assign.contributor({ projectId, userId: editor.id })
await $.Project.assign.viewer({ projectId, userId: reader.id })
// Platform uses relationships for authorization
await $.Project.update(projectId, { status: 'archived' })
// Platform checks: Is user the owner? Or has 'project.admin' permission?
// Query based on relationships
const myProjects = await $.Project.list({
where: {
// Automatically scoped to projects user owns, contributes to, or can view
}
})Hierarchical Access
Model organizational hierarchies:
// Define hierarchy
const org = await $.Organization.create({
name: 'Acme Corp',
structure: {
departments: [
{
name: 'Engineering',
teams: [
{ name: 'Backend', manager: user1 },
{ name: 'Frontend', manager: user2 }
]
},
{
name: 'Sales',
teams: [
{ name: 'Enterprise', manager: user3 }
]
}
]
}
})
// Managers inherit permissions for their team
// Department heads inherit permissions for all teams
// Org admins inherit permissions for entire organization
// Platform resolves hierarchy automatically
const teamMetrics = await $.Metrics.team.get(teamId)
// Allowed if:
// - User is team member
// - User is team manager
// - User is department head
// - User is org adminPermission Model
Permissions follow a clear naming pattern:
// Format: resource.action
'project.create'
'project.read'
'project.update'
'project.delete'
// Namespaced permissions
'report.financial.read'
'report.financial.export'
'data.pii.read'
'data.pii.write'
// Wildcards
'project.*' // All project actions
'*.read' // Read on all resources
'*' // All permissionsPermission Checks
The platform checks permissions automatically:
// Explicit check
if (await $.User.can(userId, 'project.delete', projectId)) {
await $.Project.delete(projectId)
}
// Automatic enforcement
await $.Project.delete(projectId)
// Platform throws UnauthorizedException if not authorized
// Batch check
const permissions = await $.User.permissions(userId)
// Returns: ['project.read', 'project.update', ...]Access Control Lists (ACLs)
Define per-resource permissions:
// Set ACL on resource
await $.Project.acl.set(projectId, {
owner: userId,
readers: [user2.id, user3.id],
writers: [user4.id],
admins: [user5.id]
})
// Grant specific permission
await $.Project.acl.grant(projectId, {
userId: user6.id,
permission: 'project.comment'
})
// Revoke permission
await $.Project.acl.revoke(projectId, {
userId: user6.id,
permission: 'project.comment'
})
// Query ACL
const acl = await $.Project.acl.get(projectId)
// Returns: { owner: userId, readers: [...], writers: [...] }Multi-Tenancy Integration
Authorization is automatically scoped to organizations:
// All queries automatically scoped
const projects = await $.Project.list()
// Platform adds:
// - organizationId filter
// - user permission check
// - only returns authorized projects
// Cross-organization access requires explicit permission
await $.Policy.create({
name: 'cross-org-support',
effect: 'allow',
actions: ['*.read'],
conditions: {
'user.role': 'support-engineer',
'user.crossOrgAccess': true
}
})Delegation
Users can delegate permissions to others:
// Delegate permission temporarily
await $.User.delegate({
from: managerId,
to: teamLeadId,
permissions: ['team.approve.expense'],
expires: addDays(new Date(), 7)
})
// Team lead can now approve expenses
await $.Expense.approve(expenseId)
// Platform checks delegation, allows if valid
// Delegation automatically expires
// Platform revokes after expiry dateAudit & Compliance
All authorization decisions are logged:
// Every access check is logged
await $.Project.read(projectId)
// Logs: {
// userId,
// action: 'project.read',
// resourceId: projectId,
// decision: 'allow',
// reason: 'user has project.read via editor role',
// timestamp
// }
// Denied access is logged too
try {
await $.Project.delete(projectId)
} catch (error) {
// Logs: {
// userId,
// action: 'project.delete',
// resourceId: projectId,
// decision: 'deny',
// reason: 'user lacks project.delete permission',
// timestamp
// }
}
// Query authorization audit log
const log = await $.AuditLog.list({
type: 'authorization',
userId,
period: 'last_30_days'
})API Authorization
APIs automatically enforce authorization:
// REST API endpoints check permissions
on($.API.get('/projects/:id'), async ({ params, user }) => {
// Platform already checked 'project.read' permission
const project = await $.Project.find(params.id)
return { project }
})
on($.API.delete('/projects/:id'), async ({ params, user }) => {
// Platform already checked 'project.delete' permission
await $.Project.delete(params.id)
return { success: true }
})
// GraphQL queries check field-level permissions
{
project(id: "123") {
name # Requires 'project.read'
description # Requires 'project.read'
revenue # Requires 'project.revenue.read'
}
}Performance
Authorization checks are optimized for production:
- Cached roles: User roles cached for 5 minutes
- Cached permissions: Permission checks cached per request
- Indexed relationships: Database indexes for fast lookups
<10msoverhead: Authorization adds minimal latency
// Warm cache
const permissions = await $.User.permissions(userId)
// First call: 50ms (database lookup)
// Subsequent calls: `<1ms` (cached)
// Batch authorization
const authorized = await $.User.authorizeMany(userId, [
{ action: 'project.read', resource: project1.id },
{ action: 'project.read', resource: project2.id },
{ action: 'project.read', resource: project3.id }
])
// Single database query, bulk permission checkBest Practices
1. Principle of Least Privilege
Grant minimum permissions necessary:
// Good: Specific permissions
const viewer = {
permissions: ['project.read', 'comment.create']
}
// Avoid: Overly broad permissions
const badViewer = {
permissions: ['*'] // Too much access
}2. Use Roles, Not Individual Permissions
Group permissions into roles:
// Good: Role-based
await $.User.assign.Role({ userId, roleId: 'editor' })
// Avoid: Individual permissions
await $.User.grant({ userId, permission: 'project.create' })
await $.User.grant({ userId, permission: 'project.read' })
await $.User.grant({ userId, permission: 'project.update' })
// Harder to manage, audit, and revoke3. Regular Access Reviews
Periodically review who has access:
// Get all users with admin access
const admins = await $.User.list({
where: {
roles: { contains: 'admin' }
}
})
// Review and revoke if needed
for (const user of admins) {
if (!shouldBeAdmin(user)) {
await $.User.revoke.Role({ userId: user.id, roleId: 'admin' })
}
}4. Audit Critical Actions
Log sensitive operations:
on($.User.role.assigned, async ({ user, role }) => {
if (role.name === 'admin') {
await $.Alert.send({
severity: 'high',
message: `Admin role assigned to ${user.email}`,
recipient: securityTeam
})
}
})Migration
Moving from custom authorization:
Before: Custom Implementation
// Manual permission checks everywhere
app.delete('/projects/:id', async (req, res) => {
const project = await db.projects.findById(req.params.id)
// Custom permission logic
if (req.user.id !== project.ownerId && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' })
}
await db.projects.delete(req.params.id)
res.json({ success: true })
})After: Platform Authorization
// Platform handles automatically
on($.API.delete('/projects/:id'), async ({ params }) => {
// Platform already checked:
// - Is user authenticated?
// - Is user authorized for 'project.delete'?
// - Does user have access to this specific project?
await $.Project.delete(params.id)
return { success: true }
})Integration with External Systems
Sync with external authorization systems:
// Sync from external system
on($.Schedule.hourly, async () => {
const externalRoles = await fetchFromOkta()
for (const assignment of externalRoles) {
await $.User.sync.Role({
userId: assignment.userId,
roleId: assignment.roleId,
source: 'okta'
})
}
})
// Export to external system
on($.User.role.assigned, async ({ user, role }) => {
await sendToExternalSystem({
userId: user.id,
role: role.name,
action: 'assign'
})
})Next Steps
- Enterprise Authentication - SSO and identity integration
- Multi-Tenancy - Organization isolation
- Vault - Secrets management
- Compliance - Audit logging
Authorization is complex. The .do platform makes it simple with built-in RBAC, fine-grained authorization, and automatic enforcement across your entire application.
Enterprise Authentication
Built-in SSO authentication with 50+ identity providers, automatic MFA enforcement, and seamless user experience
Vault & Secrets Management
Built-in secrets management - store API keys, credentials, and sensitive configuration securely with automatic encryption and access control