@khannara/next-rbac
v0.3.0
Published
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.
Downloads
211
Maintainers
Readme
@khannara/next-rbac
Enterprise-ready Role-Based Access Control (RBAC) for Next.js 13+ App Router with TypeScript support
Production-ready RBAC system extracted from a live Next.js application serving 1000+ users.
✨ Features
- ✅ Next.js 13+ App Router - Built for Server Components and Server Actions
- ✅ TypeScript First - Full type safety with module augmentation for autocomplete
- ✅ Database Agnostic - Prisma (10+ databases), MongoDB, or bring your own
- ✅ Hierarchical Roles - Role inheritance with circular detection
- ✅ Middleware Support - Protect routes at the edge with Next.js middleware
- ✅ Server & Client - Permission checks on both sides
- ✅ React Components -
<PermissionGate>,<RoleGate>for declarative access control - ✅ Production Tested - Battle-tested in production with 1000+ active users
- ✅ Zero Dependencies - Only peer dependencies (Next.js, React)
- ✅ Full Test Coverage - 116+ tests, 96%+ coverage
📦 Installation
npm install @khannara/next-rbacPeer Dependencies:
next>= 13.0.0react>= 18.0.0
Optional:
mongodb>= 6.0.0 (if using MongoDB adapter)@prisma/client>= 6.0.0 (if using Prisma adapter)
🚀 Quick Start
1. Choose Your Database Adapter
// 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:
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
}// 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',
});
}// 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' },
],
});
}2. Define Your Permissions & Roles (TypeScript)
Create type-safe permissions with full autocomplete:
// 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. Server-Side Permission Checks
API Routes
// 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
// 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...
}4. Middleware Protection
Protect entire route groups with Next.js middleware:
// 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*'],
};5. Client-Side Permission Gates
// 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 (
<div>
<h1>Users</h1>
<PermissionGate permission="users.create" userPermissions={permissions}>
<button>Create User</button>
</PermissionGate>
<PermissionGate
permissions={['users.update', 'users.delete']}
userPermissions={permissions}
requireAll
fallback={<p>You need both update and delete permissions</p>}
>
<button>Manage Users</button>
</PermissionGate>
</div>
);
}🏗️ Hierarchical Roles
Define role inheritance for easier permission management:
// 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:
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
Server Functions
Permission Checking
hasPermission(adapter, userId, permission)- Check single permissionhasAnyPermission(adapter, userId, permissions)- Check if user has ANY of the permissionshasAllPermissions(adapter, userId, permissions)- Check if user has ALL permissionsrequirePermission(adapter, userId, permission)- Throw if missing permissionrequireAllPermissions(adapter, userId, permissions)- Throw if missing any permission
Role Checking
hasRole(adapter, userId, role)- Check user's rolehasAnyRole(adapter, userId, roles)- Check if user has any of the rolesrequireRole(adapter, userId, role)- Throw if wrong role
Role Utilities
getRolePermissions(adapter, roleName)- Get direct permissions for a roleresolveRolePermissions(adapter, roleName)- Get all permissions (including inherited)inheritsFrom(adapter, roleName, parentRole)- Check if role inherits from parentgetRoleHierarchy(adapter, roleName)- Get full role hierarchy
Middleware
createRBACMiddleware(config)- Full-featured route protectioncreateRoleMiddleware(config)- Simple role-based protectioncreatePermissionMiddleware(config)- Simple permission-based protection
React Components
<PermissionGate>
<PermissionGate
permission="users.create" // Single permission
permissions={['a', 'b']} // Multiple permissions
requireAll={false} // Require all vs any (default: false)
userPermissions={permissions} // User's permissions
fallback={<AccessDenied />} // Optional fallback
>
<ProtectedContent />
</PermissionGate><RoleGate>
<RoleGate
role="admin" // Single role
roles={['admin', 'manager']} // Multiple roles
userRole={session.user.role} // User's role
fallback={<AccessDenied />} // Optional fallback
>
<AdminPanel />
</RoleGate>🗄️ Database Examples
PostgreSQL with 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
}MySQL with 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
}MongoDB (Direct)
// 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: "[email protected]",
role: "admin"
}🔧 Custom Adapters
Create your own adapter for any database:
import { RBACAdapter, Role, Permission, RoleDocument } from '@khannara/next-rbac';
export class CustomAdapter implements RBACAdapter {
async findRole(roleName: Role): Promise<RoleDocument | null> {
// Your implementation
}
async getUserRole(userId: string): Promise<Role | null> {
// Your implementation
}
async getRolePermissions(roleName: Role): Promise<Permission[]> {
// 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:
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);
});
});🤝 Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
📝 License
MIT © Khannara Phay
📞 Support
Production Ready - Extracted from a live Next.js application serving 1000+ active users
