Back to blogs

How to Implement Role-Based Access Control RBAC in Next js App Router

June 28, 2025
5 min read
How to Implement Role-Based Access Control RBAC in Next js App Router

Role-Based Access Control (RBAC) is essential for securing Next.js applications by restricting user access based on assigned roles. This guide provides a step-by-step implementation using Next.js App Router, covering authentication setup, middleware configuration, and UI enforcement.


Prerequisites


  1. Next.js project with App Router enabled
  2. Authentication system (e.g., Auth.js, Clerk, or custom)
  3. User roles defined in your database or authentication provider


Step 1: Define Roles and Permissions


Start by establishing role hierarchies and permissions. Example roles:


  1. admin: Full access
  2. editor: Content management
  3. viewer: Read-only access


Store roles in user metadata (e.g., via publicMetadata in Clerk or session tokens).


Step 2: Configure Authentication with Roles


Using Clerk

Attach roles to users via publicMetadata:


// Clerk Dashboard → Sessions → Customize session token
{
"publicMetadata": {
"role": "{{user.public_metadata.role}}"
}
}


Using Auth.js

Extend session types to include roles:


// auth.ts
import NextAuth from "next-auth";

declare module "next-auth" {
interface Session {
user: {
role: string;
};
}
}

export const { handlers, auth } = NextAuth({
callbacks: {
session({ session, token }) {
session.user.role = token.role;
return session;
}
}
});


Step 3: Protect Routes with Middleware


Create middleware.ts to validate roles:


// src/middleware.ts
import { auth } from "@/auth";
import { NextResponse } from "next/server";

export default auth((req) => {
const { pathname } = req.nextUrl;
const role = req.auth?.user?.role;

// Admin-only routes
if (pathname.startsWith("/admin") && role !== "admin") {
return NextResponse.redirect(new URL("/denied", req.url));
}

// Editor-only routes
if (pathname.startsWith("/edit") && !["admin", "editor"].includes(role)) {
return NextResponse.redirect(new URL("/denied", req.url));
}

return NextResponse.next();
});

export const config = {
matcher: ["/((?!api|_next|static|public).*)"],
};


Step 4: Implement Server-Side Enforcement


API Routes

Validate roles in API handlers:


// app/api/admin/route.ts
import { auth } from "@/auth";

export async function GET() {
const session = await auth();
if (session?.user.role !== "admin") {
return Response.json({ error: "Unauthorized" }, { status: 403 });
}
// Proceed with admin logic
}


Server Components

Use auth() to conditionally render content:


// app/dashboard/page.tsx
import { auth } from "@/auth";

export default async function Dashboard() {
const session = await auth();

return (
<>
{session?.user.role === "admin" && <div>Admin Content</div>}
{session?.user.role === "editor" && <div>Editor Content</div>}
</>
);
}


Step 5: Client-Side UI Controls

For client components, use conditional rendering:


// components/UserActions.tsx
"use client";
import { useSession } from "next-auth/react";

export default function UserActions() {
const { data: session } = useSession();

return (
<>
{session?.user.role === "admin" && (
<button>Delete User</button>
)}
</>
);
}


Best Practices

  1. Least Privilege Principle: Grant minimal permissions necessary
  2. Code Splitting: Dynamically load role-specific components


const AdminView = dynamic(() => import("@/components/AdminView"));
  1. SEO for Protected Content:
  2. Block search indexing of private routes via robots.txt
  3. Use noindex meta tags for sensitive pages
  4. Audit Logs: Track role changes and access attempts


Security Considerations


  1. Server-Side Validation: Never rely solely on client-side checks
  2. Metadata Encryption: Encrypt sensitive role data in transit/storage
  3. Session Timeouts: Implement idle session expiration


SEO Optimization for Next.js


While implementing RBAC, ensure public pages remain SEO-friendly:

  1. Structured Data: Add JSON-LD schemas to public pages
  2. Image Optimization: Use Next.js Image component
  3. Metadata: Define titles/descriptions in layout.tsx


export const metadata = {
title: "Public Page",
description: "Accessible to all users",
};


  1. Sitemap: Exclude protected routes in sitemap.xml


Conclusion

Implementing RBAC in Next.js App Router involves:


  1. Integrating role metadata with authentication
  2. Enforcing access via middleware and server components
  3. Applying UI-level conditional rendering
  4. Maintaining security through server-side validation


This approach ensures scalable and maintainable access control while keeping public content SEO-optimized. For advanced scenarios, consider attribute-based access control (ABAC) or third-party solutions like Permit.io.


Next Steps:

  1. Add multi-factor authentication for sensitive roles
  2. Implement permission testing workflows
  3. Explore analytics for role usage patterns
Next.jsRBACApp Routeraccess controlauthenticationauthorizationNext.js securityrole-based access control in Next.jsNext.js App Router RBACimplement RBAC in Next.jsNext.js route protection by roleNext.js authentication with rolessecure Next.js routes with RBACNext.js 14 role-based access controlNext.js user roles and permissionsNext.js middleware access controlNext.js server components RBAC