Role-Based Access Control (RBAC)

Course: Next.js Authentication and Authorization

Introduction

Now that we can sign users up and store them in the database, let’s talk about permissions. Not every user in an app should have the same level of access. For example, on an ecommerce site, you don’t want regular customers logging into the admin dashboard and changing product prices. That’s where Role-Based Access Control (RBAC) comes in.

What is RBAC?

RBAC is a way of controlling what actions different users are allowed to do in your application, based on their role. Instead of writing complicated permission checks for each individual user, we assign each user a role (like user or admin), and then define what each role is allowed to do.

Creating a Protected Page (User Dashboard)

One way of limiting access to a particular page is by protecting certain pages so only logged-in users (or users with the right role) can access them.

Take the user dashboard as an example. This page usually contains personal information, upcoming appointments, or account settings. It doesn’t make sense for just anyone to type /dashboard into the URL and see that data, only the authenticated user should have access.

The following is how we will protect the dashboard page:

import { getSession } from "@/lib/auth"
import { db } from "@/lib/db"
import React from "react"
import { redirect } from "next/navigation"
import SignOutButton from "../components/SignOutButton"

const page = async () => {
  const session = await getSession()
  if (!session) {
    redirect("/signin")
  }

  const user = await db.user.findUnique({ where: { id: session.userId } })

  if (!user) {
    redirect("/signin")
  }
  return (
    <div className="min-h-screen bg-gray-50 p-20">
      <div className="flex items-center justify-center">
        <div className="text-center">
          <h2 className="text-2xl font-bold text-gray-900 mb-4">
            Welcome to your Dashboard!
          </h2>
          <div className="bg-white p-6 rounded-lg shadow-sm border">
            <h3 className="text-lg font-medium text-gray-900 mb-2">
              User Information
            </h3>
            <div className="space-y-2 text-sm text-gray-600">
              <p>
                <strong>ID:</strong> {user.id}
              </p>
              <p>
                <strong>Email:</strong> {user.email}
              </p>
              <p>
                <strong>Role:</strong> {user.role}
              </p>
              <SignOutButton />
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

export default page

To break it down, we call getSession which returns an object with the user info like their role, id, and if they’re logged in. If they are, we query them from our database and render their data. If, for some reason the user does not exist (account deleted) then they get redirected.

Also, as mentioned in a previous lesson, the Sign Out button needs to be imported here, since we are running various server functions on this page (getSession and database query).

Protecting a Group of Pages

So far, we’ve seen how to protect a single page like the user dashboard. But what if you need to protect an entire group of pages, like an admin or premium access area?

We could add logic to each page like in the user dashboard, or we can do it by wrapping the whole route with a layout component. Next.js App Router allows you to wrap groups of pages in a layout component.

In this scenario, we will create app/admin/layout.tsx and put the RBAC logic there. Any page inside /admin will then automatically enforce the admin check without repeating the code:

import { getSession } from "@/lib/auth"
import { redirect } from "next/navigation"
import React from "react"

export default async function AdminLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  const session = await getSession()

  if (!session || session.role !== "admin") {
    redirect("/signin")
  }
  return <div>{children}</div>
}

So the role check is similar to the dashboard, however, we've simply put it on the layout.tsx file. It check's to see if the user is an admin and redirects the user if not.

Back to Course
Placeholder