DEV Community

huangyongshan46-a11y
huangyongshan46-a11y

Posted on

Role-Based Access Control in Next.js 16 with Auth.js v5

Most auth tutorials stop at login. Real SaaS apps need roles. Here is RBAC with Auth.js v5.

1. Define roles in Prisma

enum Role { USER ADMIN }

model User {
  id    String @id @default(cuid())
  email String @unique
  role  Role   @default(USER)
}
Enter fullscreen mode Exit fullscreen mode

2. Inject role into JWT

callbacks: {
  async jwt({ token }) {
    if (!token.sub) return token;
    const user = await db.user.findUnique({
      where: { id: token.sub },
      select: { role: true },
    });
    if (user) token.role = user.role;
    return token;
  },
  async session({ token, session }) {
    if (token.sub) {
      session.user.id = token.sub;
      session.user.role = token.role as string;
    }
    return session;
  },
}
Enter fullscreen mode Exit fullscreen mode

3. Extend TypeScript types

// src/types/next-auth.d.ts
import { DefaultSession } from "next-auth";
declare module "next-auth" {
  interface Session {
    user: { id: string; role: string } & DefaultSession["user"];
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Protect server components

const session = await auth();
if (!session?.user) redirect("/login");
if (session.user.role !== "ADMIN") redirect("/dashboard");
Enter fullscreen mode Exit fullscreen mode

5. Protect API routes

const session = await auth();
if (session?.user?.role !== "ADMIN") {
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
Enter fullscreen mode Exit fullscreen mode

Tips

  • Always enforce on server, never only on client
  • Start with USER/ADMIN — add granularity later
  • Check roles in every mutation API route

This RBAC system comes pre-wired in LaunchKit alongside Stripe, AI chat, email, and a full dashboard.

GitHub | Get LaunchKit ($49)

Top comments (0)