Perfect for: Complex permission logic, organization-based access, and resource-specific authorization.
Actions and Subjects
The boilerplate defines specific actions and subjects (resources) for CASL:Available Actions
// From src/services/authorization/casl-abilities.ts
export type Actions = 'create' | 'read' | 'update' | 'delete' | 'manage'
export const ActionsConst = {
CREATE: 'create' as Actions,
READ: 'read' as Actions,
UPDATE: 'update' as Actions,
DELETE: 'delete' as Actions,
MANAGE: 'manage' as Actions, // All CRUD operations
} as constAvailable Subjects (Resources)
export type Subjects =
| 'User' // User management
| 'Subscription' // Billing subscriptions
| 'Organization' // Organizations
| 'Project' // Projects
| 'Task' // Tasks within projects
| 'File' // File management
| 'Technical' // Technical operations
| 'Log' // System logs
| 'Notification' // Notifications
| 'Post' // Content posts
| 'Category' // Content categories
| 'Hashtag' // Content hashtags
| 'all' // All resourcesCreating User Abilities
The boilerplate provides a main service to create user abilities:// From src/services/authorization/authorization-service.ts
import { defineAbilitiesFor, createUserAbility } from '@/services/authorization/authorization-service'
// Create abilities for a user (with optional organization context)
const ability = defineAbilitiesFor(user, orgContext)
// Shortcut function
const ability = createUserAbility(user)
// Check if user can perform action
if (ability.can('read', 'Project')) {
// User can read projects
}Permission Levels by Role
// From buildGuestAbilities()
export function buildGuestAbilities(builder: AppAbilityBuilder) {
const { can } = builder
// Can read public content
can('read', 'User', { visibility: 'public' })
can('read', 'Post', { status: 'published' })
can('read', 'Category')
can('read', 'Hashtag')
can('read', 'Log', ['id', 'name']) // Limited fields
}Organization-Based Permissions
The boilerplate supports organization-specific roles with different permission levels:// From buildOrganizationalAbilities()
case UserOrganizationRoleConst.OWNER:
// Full organization management
can('manage', 'Organization', { id: orgContext.organizationId })
can('manage', 'User', { organizationId: orgContext.organizationId })
can('manage', 'Subscription', { organizationId: orgContext.organizationId })
can('manage', 'Project', { organizationId: orgContext.organizationId })
can('manage', 'Task', { organizationId: orgContext.organizationId })
can('manage', 'File', { organizationId: orgContext.organizationId })Helper Functions
The boilerplate provides utility functions for common permission checks:Basic Permission Checks
import {
userCan,
userCannot,
userCanOnResource,
isUserAdmin
} from '@/services/authorization/authorization-service'
// Check if user can perform action on subject type
const canCreateProject = userCan(user, 'create', 'Project', orgContext)
// Check if user cannot perform action
const cannotDeleteUser = userCannot(user, 'delete', 'User')
// Check admin status
const isAdmin = isUserAdmin(user)Entity-Specific Authorization Helpers
The boilerplate includes dedicated authorization services for each entity type:src/services/authorization/
├── authorization-service.ts # Main CASL service
├── casl-abilities.ts # Permission definitions
├── admin-dashboard-authorization.ts # Admin dashboard permissions
├── file-authorization.ts # File permissions
├── notification-authorization.ts # Notification permissions
├── organization-authorization.ts # Organization permissions
├── post-authorization.ts # Post/content permissions
├── project-authorization.ts # Project permissions
├── subscription-authorization.ts # Subscription permissions
├── user-authorization.ts # User management permissions
└── __tests__/ # Authorization tests
├── build-organizational-abilities.test.ts
└── casl-authorization-service.test.ts
Entity Helpers: Each authorization service provides specific business logic and validation rules for its respective entity, making permission checks more semantic and maintainable.
Resource-Specific Permission Checks
// Check permission on specific resource with conditions
const canEditThisProject = userCanOnResource(
user,
'update',
'Project',
{ id: projectId, organizationId: orgId },
orgContext
)
// Check if user owns specific resource
const canDeleteThisPost = userCanOnResource(
user,
'delete',
'Post',
{ authorId: user.id },
orgContext
)Organization Permission Checks
import {
getUserRoleInOrganization,
hasOrganizationRole,
isOrganizationOwner,
isOrganizationAdmin
} from '@/services/authorization/authorization-service'
// Get user's role in organization
const userRole = getUserRoleInOrganization(user, organizationId)
// Check specific organization role
const isOwner = isOrganizationOwner(user, organizationId)
const isOrgAdmin = isOrganizationAdmin(user, organizationId)
const isMember = hasOrganizationRole(user, organizationId, 'MEMBER')Using CASL in React Server Components
The boilerplate follows a DAL (Data Access Layer) pattern where permissions are fetched server-side and passed to components.Step 1: DAL Permission Functions
// From src/app/dal/project-dal.ts
import {
canCreateProject,
canReadProject,
canUpdateProject,
canDeleteProject,
} from '@/services/authorization/project-authorization'
/**
* Get permissions for a specific project
*/
export async function getProjectPermissions(projectId?: string) {
if (!projectId) {
return {
canCreate: false,
canRead: false,
canUpdate: false,
canDelete: false,
}
}
// Use entity-specific authorization functions in parallel
const [canCreate, canRead, canUpdate, canDelete] = await Promise.all([
canCreateProject(projectId),
canReadProject(projectId),
canUpdateProject(projectId),
canDeleteProject(projectId),
])
return {
canCreate,
canRead,
canUpdate,
canDelete,
}
}Step 2: Using Permissions in RSC Pages
// From src/app/[locale]/(app)/team/[slug]/projects/[id]/edit/page.tsx
import { notFound } from 'next/navigation'
import { getProjectPermissions } from '@/app/dal/project-dal'
import { ProjectActions } from '@/components/features/projects/project-actions'
import { getProjectByIdService } from '@/services/facades/project-service-facade'
export default async function ProjectPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
// Fetch data and permissions in parallel
const [project, permissions] = await Promise.all([
getProjectByIdService(id),
getProjectPermissions(id)
])
if (!project) {
notFound()
}
// Extract all permission flags
const { canCreate, canRead, canUpdate, canDelete } = permissions
// Check read permission first
if (!canRead) {
return (
<div className="text-center py-8">
<p>You don't have permission to view this project.</p>
</div>
)
}
return (
<div className="container mx-auto space-y-8 py-8">
<div>
<h1 className="mb-8 text-2xl font-bold">{project.name}</h1>
{/* Pass all permissions to component */}
<ProjectActions
project={project}
canCreate={canCreate}
canUpdate={canUpdate}
canDelete={canDelete}
/>
</div>
</div>
)
}Step 3: Permission-Based UI in Components
// In ProjectActions component
interface ProjectActionsProps {
project: Project
canCreate: boolean
canUpdate: boolean
canDelete: boolean
}
export function ProjectActions({
project,
canCreate,
canUpdate,
canDelete
}: ProjectActionsProps) {
return (
<div className="flex gap-2">
{/* Create new project button */}
{canCreate && (
<Button onClick={() => createNewProject()}>
<Plus className="h-4 w-4 mr-2" />
New Project
</Button>
)}
{/* Edit project button */}
{canUpdate && (
<Button variant="outline" onClick={() => editProject(project.id)}>
<Edit className="h-4 w-4 mr-2" />
Edit
</Button>
)}
{/* Delete project button */}
{canDelete && (
<Button
variant="destructive"
onClick={() => deleteProject(project.id)}
>
<Trash className="h-4 w-4 mr-2" />
Delete
</Button>
)}
</div>
)
}Field-Level Filtering
The boilerplate includes field-level permission filtering:import { filterFields } from '@/services/authorization/authorization-service'
// Filter sensitive fields based on user permissions
const filteredUserData = filterFields(
user,
'read',
'User',
userData,
orgContext
)
// This respects the `cannot` rules defined in abilities
// For example, regular users cannot see 'role', 'permissions', 'internalNotes'Organization Context
Many permission checks require organization context:// Create organization context
const orgContext: OrganizationContext = {
organizationId: 'org-123'
}
// Use context in permission checks
const ability = defineAbilitiesFor(user, orgContext)
const canManageProjects = ability.can('manage', 'Project')
// Or use utility functions
const canCreateProject = userCan(user, 'create', 'Project', orgContext)Key Features
Real CASL Implementation: The boilerplate provides a complete, production-ready CASL system with both global roles and organization-specific permissions.
What Makes This System Powerful
- Hierarchical Roles - Global roles (GUEST → USER → ADMIN → SUPER_ADMIN) with inheritance
- Organization Roles - Separate role system within organizations (MEMBER → ADMIN → OWNER)
- Resource Conditions - Permissions tied to specific resource properties (
{ userId: user.id }) - Field-Level Security - Control which fields users can read/write
- Context Awareness - Organization context affects permissions dynamically
Practical Usage
The system is designed to handle complex real-world scenarios:- Multi-tenant SaaS - Users can have different roles in different organizations
- Resource Ownership - Users can only modify their own content
- Progressive Access - Higher roles inherit lower role permissions
- Field Protection - Sensitive fields are hidden from unauthorized users
Enterprise-Ready! Your application now has a sophisticated, flexible permission system that scales with complex business requirements and multi-tenant architecture.