Enterprise-ready Role-Based Access Control (RBAC) for Next.js 13+ App Router with TypeScript support. Supports MongoDB, Prisma (PostgreSQL, MySQL, SQLite, etc.), hierarchical roles, and middleware protection.
npm install @khannara/next-rbac, for declarative access control
bash
npm install @khannara/next-rbac
`
Peer Dependencies:
- next >= 13.0.0
- react >= 18.0.0
Optional:
- mongodb >= 6.0.0 (if using MongoDB adapter)
- @prisma/client >= 6.0.0 (if using Prisma adapter)
🚀 Quick Start
$3
Prisma (PostgreSQL, MySQL, SQLite, SQL Server, MongoDB, etc.)
`typescript
// lib/rbac.ts
import { PrismaClient } from '@prisma/client';
import { PrismaAdapter } from '@khannara/next-rbac/adapters';
const prisma = new PrismaClient();
export function getRBACAdapter() {
return new PrismaAdapter({
prisma,
roleModel: 'role', // Your Prisma model name
userModel: 'user',
enabled: true, // Enable caching
ttl: 300, // Cache for 5 minutes
});
}
`
Required Prisma Schema:
`prisma
model Role {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String @unique
permissions String[]
inherits String? // Optional: parent role for inheritance
deleted_at DateTime?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
role String
}
`
MongoDB (Direct Connection)
`typescript
// lib/rbac.ts
import { MongoDBAdapter } from '@khannara/next-rbac/adapters';
import clientPromise from '@/lib/mongodb';
export async function getRBACAdapter() {
const client = await clientPromise;
const db = client.db('myapp');
return new MongoDBAdapter({
db,
rolesCollection: 'roles',
usersCollection: 'users',
});
}
`
In-Memory (Testing/Demos)
`typescript
// lib/rbac.test.ts
import { InMemoryAdapter } from '@khannara/next-rbac/adapters';
export function getRBACAdapter() {
return new InMemoryAdapter({
roles: [
{
name: 'admin',
permissions: ['users.create', 'users.read', 'users.update', 'users.delete'],
},
{
name: 'user',
permissions: ['users.read'],
},
],
users: [
{ id: '1', role: 'admin' },
{ id: '2', role: 'user' },
],
});
}
`
$3
Create type-safe permissions with full autocomplete:
`typescript
// types/rbac.d.ts
import '@khannara/next-rbac';
declare module '@khannara/next-rbac' {
export interface RBACTypes {
Permission:
// Dashboard
| 'dashboard.view'
// Users
| 'users.create'
| 'users.read'
| 'users.update'
| 'users.delete'
// Products
| 'products.create'
| 'products.read'
| 'products.update'
| 'products.delete'
// Settings
| 'settings.read'
| 'settings.update';
Role: 'super-admin' | 'admin' | 'manager' | 'user';
}
}
`
You now get full autocomplete throughout your app!
$3
#### API Routes
`typescript
// app/api/users/route.ts
import { requirePermission } from '@khannara/next-rbac/server';
import { getRBACAdapter } from '@/lib/rbac';
import { auth } from '@/auth';
export async function POST(request: Request) {
const session = await auth();
const adapter = getRBACAdapter();
// Throws if user lacks permission
await requirePermission(adapter, session.user.id, 'users.create');
// User has permission, proceed...
return Response.json({ success: true });
}
`
#### Server Actions
`typescript
// app/actions/users.ts
'use server';
import { hasPermission } from '@khannara/next-rbac/server';
import { getRBACAdapter } from '@/lib/rbac';
import { auth } from '@/auth';
export async function deleteUser(userId: string) {
const session = await auth();
const adapter = getRBACAdapter();
if (!await hasPermission(adapter, session.user.id, 'users.delete')) {
throw new Error('Insufficient permissions');
}
// Delete user...
}
`
$3
Protect entire route groups with Next.js middleware:
`typescript
// middleware.ts
import { createRBACMiddleware } from '@khannara/next-rbac/server';
import { getRBACAdapter } from './lib/rbac';
import { getSession } from './lib/auth';
const rbacMiddleware = createRBACMiddleware({
adapter: getRBACAdapter(),
getUserId: async (req) => {
const session = await getSession(req);
return session?.user?.id || null;
},
unauthorizedUrl: '/login',
forbiddenUrl: '/forbidden',
});
export async function middleware(req: NextRequest) {
return rbacMiddleware(req, {
'/admin': { roles: ['admin', 'super-admin'] },
'/api/users': { permissions: ['users.create', 'users.update', 'users.delete'] },
'/settings': { anyPermissions: ['settings.update', 'admin.access'] },
'/dashboard': {
custom: async (req, userId, adapter) => {
// Custom logic
return true;
},
},
});
}
export const config = {
matcher: ['/admin/:path', '/api/:path', '/settings/:path', '/dashboard/:path'],
};
`
$3
`tsx
// app/users/page.tsx
import { PermissionGate } from '@khannara/next-rbac/react';
import { getRolePermissions } from '@khannara/next-rbac/server';
import { getRBACAdapter } from '@/lib/rbac';
import { auth } from '@/auth';
export default async function UsersPage() {
const session = await auth();
const adapter = getRBACAdapter();
const userRole = await adapter.getUserRole(session.user.id);
const permissions = await getRolePermissions(adapter, userRole);
return (
Users
permissions={['users.update', 'users.delete']}
userPermissions={permissions}
requireAll
fallback={You need both update and delete permissions
}
>
);
}
`
🏗️ Hierarchical Roles
Define role inheritance for easier permission management:
`javascript
// Database roles collection
{
name: 'user',
permissions: ['users.read', 'profile.update']
}
{
name: 'manager',
permissions: ['users.update', 'reports.read'],
inherits: 'user' // Inherits all 'user' permissions
}
{
name: 'admin',
permissions: ['users.delete', 'settings.update'],
inherits: 'manager' // Inherits 'manager' + 'user' permissions
}
{
name: 'super-admin',
permissions: ['system.admin'],
inherits: 'admin' // Inherits entire chain
}
`
Use inheritance utilities:
`typescript
import {
resolveRolePermissions,
inheritsFrom,
getRoleHierarchy,
} from '@khannara/next-rbac/server';
// Get all permissions (including inherited)
const permissions = await resolveRolePermissions(adapter, 'admin');
// Returns: ['users.read', 'profile.update', 'users.update', 'reports.read', 'users.delete', 'settings.update']
// Check if role inherits from another
const isDescendant = await inheritsFrom(adapter, 'admin', 'user');
// Returns: true
// Get complete hierarchy
const hierarchy = await getRoleHierarchy(adapter, 'admin');
// Returns: ['admin', 'manager', 'user']
`
📚 API Reference
$3
#### Permission Checking
- hasPermission(adapter, userId, permission) - Check single permission
- hasAnyPermission(adapter, userId, permissions) - Check if user has ANY of the permissions
- hasAllPermissions(adapter, userId, permissions) - Check if user has ALL permissions
- requirePermission(adapter, userId, permission) - Throw if missing permission
- requireAllPermissions(adapter, userId, permissions) - Throw if missing any permission
#### Role Checking
- hasRole(adapter, userId, role) - Check user's role
- hasAnyRole(adapter, userId, roles) - Check if user has any of the roles
- requireRole(adapter, userId, role) - Throw if wrong role
#### Role Utilities
- getRolePermissions(adapter, roleName) - Get direct permissions for a role
- resolveRolePermissions(adapter, roleName) - Get all permissions (including inherited)
- inheritsFrom(adapter, roleName, parentRole) - Check if role inherits from parent
- getRoleHierarchy(adapter, roleName) - Get full role hierarchy
#### Middleware
- createRBACMiddleware(config) - Full-featured route protection
- createRoleMiddleware(config) - Simple role-based protection
- createPermissionMiddleware(config) - Simple permission-based protection
$3
####
`tsx
permission="users.create" // Single permission
permissions={['a', 'b']} // Multiple permissions
requireAll={false} // Require all vs any (default: false)
userPermissions={permissions} // User's permissions
fallback={ } // Optional fallback
>
####
`tsx
role="admin" // Single role
roles={['admin', 'manager']} // Multiple roles
userRole={session.user.role} // User's role
fallback={ } // Optional fallback
>
🗄️ Database Examples
$3
`prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Role {
id String @id @default(uuid())
name String @unique
permissions String[]
inherits String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
model User {
id String @id @default(uuid())
email String @unique
role String
}
`
$3
`prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Role {
id Int @id @default(autoincrement())
name String @unique
permissions Json // Store as JSON in MySQL
inherits String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
}
`
$3
`javascript
// roles collection
{
_id: ObjectId,
name: "admin",
permissions: ["users.create", "users.delete"],
inherits: "manager", // Optional
created_at: ISODate,
updated_at: ISODate,
deleted_at: null
}
// users collection
{
_id: ObjectId,
email: "admin@example.com",
role: "admin"
}
`
🔧 Custom Adapters
Create your own adapter for any database:
`typescript
import { RBACAdapter, Role, Permission, RoleDocument } from '@khannara/next-rbac';
export class CustomAdapter implements RBACAdapter {
async findRole(roleName: Role): Promise {
// Your implementation
}
async getUserRole(userId: string): Promise {
// Your implementation
}
async getRolePermissions(roleName: Role): Promise {
// Your implementation
}
}
`
🎯 Use Cases
- Multi-tenant SaaS - Different permissions per tenant
- Admin Dashboards - Granular admin access control
- B2B Applications - Role-based organization access
- Content Management - Editor, Reviewer, Publisher roles
- E-commerce - Customer, Manager, Admin permissions
🧪 Testing
We provide an InMemoryAdapter for easy testing:
`typescript
import { InMemoryAdapter } from '@khannara/next-rbac/adapters';
describe('User Management', () => {
it('should allow admin to create users', async () => {
const adapter = new InMemoryAdapter({
roles: [
{ name: 'admin', permissions: ['users.create'] },
{ name: 'user', permissions: ['users.read'] },
],
users: [
{ id: 'admin1', role: 'admin' },
],
});
const canCreate = await hasPermission(adapter, 'admin1', 'users.create');
expect(canCreate).toBe(true);
});
});
``