Implementing RBAC in Next.js 15
Role-Based Access Control (RBAC) restricts what authenticated users can do based on their role. In Next.js, RBAC spans middleware, Server Components, Server Actions, and the UI.
Extending the Session Type
// types/next-auth.d.ts
import 'next-auth';
import 'next-auth/jwt';
declare module 'next-auth' {
interface User {
role: 'user' | 'editor' | 'admin';
}
interface Session {
user: User & {
id: string;
};
}
}
declare module 'next-auth/jwt' {
interface JWT {
role: 'user' | 'editor' | 'admin';
}
}
Permission Check Utility
// lib/permissions.ts
type Role = 'user' | 'editor' | 'admin';
const permissions = {
'posts:create': ['editor', 'admin'] as Role[],
'posts:delete': ['admin'] as Role[],
'posts:publish': ['editor', 'admin'] as Role[],
'users:manage': ['admin'] as Role[],
} as const;
type Permission = keyof typeof permissions;
export function hasPermission(role: Role, permission: Permission): boolean {
return permissions[permission].includes(role);
}
Using Permissions in Server Components
// app/blog/[slug]/page.tsx
import { auth } from '@/auth';
import { hasPermission } from '@/lib/permissions';
export default async function PostPage({ params }) {
const { slug } = await params;
const session = await auth();
const post = await getPost(slug);
const userRole = session?.user?.role ?? 'user';
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
{hasPermission(userRole, 'posts:publish') && (
<PublishButton postId={post.id} published={post.published} />
)}
{hasPermission(userRole, 'posts:delete') && (
<DeleteButton postId={post.id} />
)}
</article>
);
}
Conditional Navigation by Role
// components/navigation.tsx
import { auth } from '@/auth';
import { hasPermission } from '@/lib/permissions';
export default async function Navigation() {
const session = await auth();
const role = session?.user?.role ?? 'user';
return (
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
{session && <a href="/dashboard">Dashboard</a>}
{hasPermission(role, 'posts:create') && <a href="/blog/new">New Post</a>}
{hasPermission(role, 'users:manage') && <a href="/admin">Admin</a>}
</nav>
);
}
The combination of middleware for coarse-grained protection and permission checks for fine-grained access control covers the full RBAC spectrum.