Back to roadmaps clerk Course

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: