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)
}
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;
},
}
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"];
}
}
4. Protect server components
const session = await auth();
if (!session?.user) redirect("/login");
if (session.user.role !== "ADMIN") redirect("/dashboard");
5. Protect API routes
const session = await auth();
if (session?.user?.role !== "ADMIN") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
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.
Top comments (0)