Skip to Content
DocumentationRole-Based Permissions

Role-Based Permissions

In this section, we will explore how to define and enforce role-based permissions using Asgardian. Role-based access control (RBAC) allows you to assign permissions to users based on their roles, making it easier to manage access control in your application.

Role-Based Permission Patterns

There are several patterns you can use to implement role-based permissions with Asgardian:

Pattern 1: Separate Ability Instances

Create distinct ability instances for different roles:

import { createAbility } from '@nordic-ui/asgardian' // Admin abilities - full access const adminAbility = createAbility().can('manage', 'all') // Editor abilities - can manage content but not delete const editorAbility = createAbility() .can(['create', 'read', 'update'], 'Post') .can(['create', 'read', 'update'], 'Comment') // Author abilities - can manage own content const authorAbility = createAbility() .can(['read', 'create'], 'Post') .can(['update', 'delete'], 'Post', { authorId: 123 }) .can(['read', 'create'], 'Comment') .can(['update', 'delete'], 'Comment', { authorId: 123 }) // Visitor abilities - read-only access const visitorAbility = createAbility() .can('read', 'Post', { published: true }) .can('read', 'Comment')

Pattern 2: Role-Based Ability Factory

Create a factory function that returns the appropriate ability instance:

type Role = 'admin' | 'editor' | 'author' | 'visitor' type Resources = 'Post' | 'Comment' function createRoleAbility(role: Role, userId?: number) { const ability = createAbility() switch (role) { case 'admin': ability.can('manage', 'all') break case 'editor': ability .can(['create', 'read', 'update'], 'Post') .can(['create', 'read', 'update'], 'Comment') break case 'author': ability .can(['read', 'create'], 'Post') .can(['update', 'delete'], 'Post', { authorId: 123 }) .can(['read', 'create'], 'Comment') .can(['update', 'delete'], 'Comment', { authorId: 123 }) break case 'visitor': ability .can('read', 'Post', { published: true }) .can('read', 'Comment') break } return ability }

Pattern 3: Composable Ability Modules

Break down permissions into smaller, focused modules that can be composed together:

// Define specific permission modules function createContentPermissions(userId: number) { return createAbility() .can('read', 'Post', { published: true }) .can(['create', 'update'], 'Post', { authorId: userId }) .can('read', 'Comment') } function createModeratorPermissions() { return createAbility() .can('delete', 'Post') .can('delete', 'Comment') .can('update', 'Post', { reported: true }) } function createAdminPermissions() { return createAbility().can('manage', 'all') } type User = { id: number, roles: ('admin', 'moderator', 'user')[] } // Compose permissions based on user roles function createUserAbility(user: User) { const abilities = [ // Base permissions everyone gets createContentPermissions(user.id) ] if (user.roles.includes('moderator')) { abilities.push(createModeratorPermissions()) } if (user.roles.includes('admin')) { abilities.push(createAdminPermissions()) } // Combine all permissions const finalAbility = createAbility() for (const ability of abilities) { ability.rules.forEach(rule => { if (rule.inverted) { finalAbility.cannot(rule.action, rule.resource, rule.conditions) } else { finalAbility.can(rule.action, rule.resource, rule.conditions) } }) } return finalAbility }

Using Role-Based Abilities

Here’s how to use the role-based abilities in your application:

// Create ability instance based on user's role const user = { id: 123, role: 'author' } const ability = createRoleAbility(user.role, user.id) // Check permissions ability.isAllowed('read', 'Post', { published: true }) // true ability.isAllowed('update', 'Post', { authorId: 123 }) // true ability.isAllowed('update', 'Post', { authorId: 456 }) // false ability.isAllowed('delete', 'Post', { authorId: 456 }) // false

Multiple Roles

To handle users with multiple roles, you can combine permissions from different roles:

function createMultiRoleAbility(roles: Role[], userId?: number) { const ability = createAbility<never, Resources>() if (roles.includes('admin')) { ability.can('manage', 'all') return ability // Admin has all permissions, no need to check other roles } if (roles.includes('editor')) { ability .can(['create', 'read', 'update'], 'Post') .can(['create', 'read', 'update'], 'Comment') } if (roles.includes('author')) { ability .can('read', 'Post') .can(['create', 'update'], 'Post', { authorId: userId }) .can(['read', 'create'], 'Comment') } // Visitor permissions are implicit for all users ability .can('read', 'Post', { published: true }) .can('read', 'Comment') return ability } // Usage with multiple roles const userWithMultipleRoles = { id: 123, roles: ['editor', 'author'] as Role[] } const ability = createMultiRoleAbility(userWithMultipleRoles.roles, userWithMultipleRoles.id)

Best Practices

  1. Start restrictive
    // ✅ Good: Start with limited permissions const ability = createAbility() .can('read', 'Post', { published: true }) .can('update', 'Post', { authorId: userId }) // ❌ Bad: Starting with broad permissions const ability = createAbility() .can('manage', 'all') .cannot('delete', 'Post')
  2. Be explicit
    // ✅ Good: Specific resource access const editorAbility = createAbility() .can('manage', 'Post') // Explicitly grant all actions on posts .can('read', 'Comment') // Explicitly grant only read access on comments // ❌ Bad: Over-permissive resource access const editorAbility = createAbility() .can('manage', 'all') // Too broad, grants access to all current and future resources
  3. Use type safety
    // ✅ Good: Type-safe roles and resources type Role = 'admin' | 'editor' | 'author' | 'visitor' type Resources = 'Post' | 'Comment' const ability = createAbility<never, Resources>() // ❌ Bad: No type safety const ability = createAbility<any, any>()

Summary

In this section, we learned how to define and enforce role-based permissions using Asgardian. Role-based access control allows you to manage user permissions efficiently by grouping them into roles and defining rules that apply to these roles.

Last updated on