Project: Role-Based Access Control in SaaS Dashboards
In this project, we will implement Role-Based Access Control (RBAC). We will assign user roles (such as admin, editor, or user) inside the Clerk session user metadata, and protect SaaS pages using server-side metadata checks.
1. Storing Roles inside publicMetadata
Clerk provides a publicMetadata object on the user record. This object is secure, read-only on the frontend client, and can be edited from your backend APIs or the Clerk console.
Defining User Metadata Schema
{
"publicMetadata": {
"role": "admin"
}
}2. Implementing the RBAC Server Component Check
Create a helper function to retrieve the current user role on the server:
// src/lib/roles.ts
import { auth } from "@clerk/nextjs/server";
export async function checkUserRole() {
const authObject = await auth();
const sessionClaims = authObject.sessionClaims;
// Read metadata role claim from user token session
const role = sessionClaims?.metadata?.role || "user";
return role as "admin" | "editor" | "user";
}Note: To map publicMetadata into sessionClaims.metadata, navigate to the Clerk Dashboard -> Sessions -> Customize Session Token, and add role: "{{user.public_metadata.role}}" to the claims.
3. Protecting the Admin Settings Dashboard
Here is the Next.js server page layout for an admin settings workspace:
// app/admin/settings/page.tsx
import React from "react";
import { checkUserRole } from "../../../lib/roles";
import { redirect } from "next/navigation";
export default async function AdminSettingsPage() {
const activeRole = await checkUserRole();
// If the user is not an admin, block access and redirect to the default dashboard
if (activeRole !== "admin") {
redirect("/dashboard?error=unauthorized_admin_access");
}
return (
<div className="p-8 max-w-4xl mx-auto bg-white rounded-3xl border shadow-sm mt-10">
<div className="border-b pb-4">
<h2 className="text-2xl font-bold text-gray-950">System Configurations</h2>
<p className="text-gray-500 text-sm mt-1">This panel is restricted to Administrator roles only.</p>
</div>
<div className="grid gap-6 mt-8">
<div className="p-4 border rounded-xl bg-gray-50 flex justify-between items-center">
<div>
<h4 className="font-semibold text-gray-900">Maintenance Mode</h4>
<p className="text-xs text-gray-400">Put the SaaS database in read-only status.</p>
</div>
<button className="bg-red-600 text-white px-4 py-2 rounded-lg text-sm hover:bg-red-700">
Activate
</button>
</div>
</div>
</div>
);
}Published on Last updated: