Careeroza
Role-based Access
Roles and Permissions
Do not scatter role === 'admin' checks everywhere. Define permissions once, check them through one function, and also check ownership (a customer may see only their own orders).
Example
type Role = 'customer' | 'seller' | 'admin';
type Permission = 'product:create' | 'product:update' | 'order:read' | 'order:refund' | 'user:ban';
const permissions: Record<Role, Permission[]> = {
customer: ['order:read'],
seller: ['order:read', 'product:create', 'product:update'],
admin: ['order:read', 'order:refund', 'product:create', 'product:update', 'user:ban'],
};
type User = { id: string; role: Role };
type Order = { id: string; userId: string };
function can(user: User, perm: Permission): boolean {
return permissions[user.role].includes(perm);
}
function canReadOrder(user: User, order: Order): boolean {
return can(user, 'order:read') && (user.role === 'admin' || order.userId === user.id);
}
const priya: User = { id: 'u1', role: 'customer' };
const admin: User = { id: 'u9', role: 'admin' };
const order: Order = { id: 'ORD-1001', userId: 'u1' };
console.log('priya reads own order :', canReadOrder(priya, order));
console.log('priya reads other order :', canReadOrder(priya, { id: 'ORD-2002', userId: 'u2' }));
console.log('priya refunds :', can(priya, 'order:refund'));
console.log('admin refunds :', can(admin, 'order:refund'));Output
priya reads own order : true priya reads other order : false priya refunds : false admin refunds : true
A Reusable Guard
// lib/authz.ts
import 'server-only';
import { forbidden, unauthorized } from 'next/navigation'; // experimental authInterrupts in 15/16
import { auth } from '@/auth';
export async function requireRole(...roles: Array<'customer' | 'seller' | 'admin'>) {
const session = await auth();
if (!session?.user) unauthorized(); // renders app/unauthorized.tsx (401)
if (!roles.includes(session.user.role)) forbidden(); // renders app/forbidden.tsx (403)
return session.user;
}
// usage in a page or action
// const admin = await requireRole('admin');Tip: If you do not enable authInterrupts, use redirect('/login') and notFound() instead of unauthorized()/forbidden().