Back to blogs

How to Use Middleware for Role Based Access Control in Next js 15 App Router

September 3, 2025
6 min read
How to Use Middleware for Role Based Access Control in Next js 15 App Router

Learn how to secure your Next.js 15 app with role-based access control (RBAC) using middleware. Protect pages and API routes by enforcing roles like admin, editor, and viewer.


The Challenge


Imagine building a Next.js 15 application with an admin dashboard. You want:

  1. admins → full control
  2. editors → manage content only
  3. viewers → read-only access


But here’s the problem: without access control, any logged-in user could type /admin in the URL and open restricted pages. That’s both a security and user experience issue.

The solution: Implement Role-Based Access Control (RBAC) with Next.js App Router middleware to handle permissions before requests reach your pages.


What is RBAC?


Role-Based Access Control (RBAC) is a security strategy where permissions depend on user roles:

  1. Admin → Manage everything (users, content, settings)
  2. Editor → Edit content only
  3. Viewer → View-only


RBAC keeps access rules centralized and maintainable, instead of scattering checks across your app.


Why Middleware is Perfect for RBAC in Next.js 15

Next.js 15 introduces middleware (middleware.ts), which executes before pages or API routes load.


Benefits:

  1. Block unauthorized access early
  2. Centralize access rules in one place
  3. Protect both UI pages and API routes


Middleware Flow

Request → Middleware → Check Role →
• Allowed → Continue
• Denied → Redirect (/unauthorized or /login)


Project Setup: Next.js 15 + Auth.js


We’ll use Auth.js (NextAuth) for authentication and session handling. You can swap in Clerk, Supabase Auth, or Auth0 if you prefer.


Step 1: Install Auth.js

npm install next-auth


Step 2: Configure Authentication (lib/auth.ts)

import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";

export const authOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const users = [
{ id: 1, name: "Alice", role: "admin", username: "alice", password: "pass" },
{ id: 2, name: "Bob", role: "editor", username: "bob", password: "pass" },
{ id: 3, name: "Charlie", role: "viewer", username: "charlie", password: "pass" },
];

if (!credentials) return null;

const user = users.find(
(u) =>
u.username === credentials.username &&
u.password === credentials.password
);

return user ?? null;
},
}),
],

callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},

async session({ session, token }) {
if (token?.role) {
session.user.role = token.role;
}
return session;
},
},
};

export default NextAuth(authOptions);


Implementing RBAC in Middleware

Now let’s enforce role-based rules in middleware.ts.


import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";

// Role-based access rules
const roleAccess: Record<string, string[]> = {
"/admin": ["admin"],
"/editor": ["admin", "editor"],
"/dashboard": ["admin", "editor", "viewer"],
};

export async function middleware(req: NextRequest) {
const token = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
});

// Redirect unauthenticated users to login
if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}

const { pathname } = req.nextUrl;

// Check if user role matches allowed roles for the route
for (const [route, allowedRoles] of Object.entries(roleAccess)) {
if (pathname.startsWith(route)) {
const userRole = token.role as string;
if (!allowedRoles.includes(userRole)) {
return NextResponse.redirect(new URL("/unauthorized", req.url));
}
}
}

return NextResponse.next();
}

export const config = {
matcher: ["/admin/:path*", "/editor/:path*", "/dashboard/:path*"],
};


Unauthorized users get redirected before restricted pages load.


Securing Pages and API Routes

Example: Protected API (app/api/admin/route.ts)


import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { NextResponse } from "next/server";

export async function GET() {
const session = await getServerSession(authOptions);

// Block unauthenticated or unauthorized users
if (!session) {
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
}

if (session.user.role !== "admin") {
return NextResponse.json({ error: "Forbidden: Admins only" }, { status: 403 });
}

// Authorized
return NextResponse.json({ message: "Secure admin data" });
}


Example: Role-Based UI (DashboardActions.tsx)


"use client";

import { useSession } from "next-auth/react";

export default function DashboardActions() {
const { data: session } = useSession();
const role = session?.user.role;

if (!role) {
return <p>Loading...</p>;
}

const roleActions: Record<string, JSX.Element> = {
admin: <button>Manage Users</button>,
editor: <button>Edit Content</button>,
viewer: <p>Read-only user</p>,
};

return roleActions[role] ?? <p>Unauthorized</p>;
}


Testing the Middleware

  1. Login as viewer/admin → redirected to /unauthorized
  2. Login as editor/editor works, /admin blocked
  3. Login as admin → unrestricted access


Perfect!


Best Practices for RBAC in Next.js


  1. Principle of least privilege → Assign minimal permissions.
  2. Always validate server-side → Don’t trust client-only checks.
  3. Centralize rules → Keep RBAC logic in one place.
  4. Default deny → If no rule matches, block by default.
  5. Audit logs → Track unauthorized attempts.


Conclusion


With RBAC middleware in Next.js 15, you can:

  1. Protect sensitive routes before rendering
  2. Enforce consistent role checks
  3. Handle both pages and API routes


Next steps:

  1. Explore ABAC (Attribute-Based Access Control) for context-aware rules
  2. Try Supabase Row-Level Security (RLS) for DB-level enforcement
  3. Integrate with Clerk/Auth0/Supabase Auth for advanced workflows


FAQ: RBAC in Next.js 15


1. What is RBAC in Next.js?

A role-based system where access depends on user roles (admin, editor, viewer).


2. Why use middleware for RBAC?

It runs before routes load, letting you block unauthorized users early.


3. Can I use Supabase roles with Next.js middleware?

Yes. Pull roles from Supabase JWT claims and enforce them in middleware.ts.


4. How is ABAC different from RBAC?

  1. RBAC → role-based (admin, editor)
  2. ABAC → attribute-based (time, department, resource type)


5. Should I only check roles on the client?

No — always validate server-side (middleware + APIs) to prevent bypassing.


You now have a production-ready approach for implementing RBAC middleware in Next.js 15.

rbac nextjsrole based access control nextjsmiddleware rbac nextjsnextjs 15 rbacrbac in nextjs app routernextjs middleware authenticationnextjs middleware role based access controlnextjs role based routingprotect routes nextjs middlewaresecure api routes nextjsnextjs app router rbacnextjs 15 middleware examplerbac with auth.js nextjsclerk rbac nextjssupabase rbac nextjsnextjs rbac tutorialnextjs rbac best practicesrole based routing in nextjs 15nextjs middleware guidenextjs access control

Recent Blogs

View All