Implement basic Role Based Access Control (RBAC) with metadata
To control which users can access certain parts of your app, you can use the roles feature. Although Clerk offers roles as part of the organizations feature set, not every app implements organizations. This guide covers a workaround to set up a basic Role Based Access Control (RBAC) system for products that don't use Clerk's organizations or roles.
This guide assumes that you're using Next.js App Router, but the concepts can be adapted to Next.js Pages Router and Remix.
Configure the session token
Clerk provides user metadata, which can be used to store information, and in this case, it can be used to store a user's role. Since publicMetadata
can only be read but not modified in the browser, it is the safest and most appropriate choice for storing information.
To build a basic RBAC system, you first need to make publicMetadata
available to the application directly from the session token. By attaching publicMetadata
to the user's session, you can access the data without needing to make a network request each time.
- In the Clerk Dashboard, navigate to the Sessions page.
- Under the Customize session token section, select Edit.
- In the modal that opens, enter the following JSON and select Save. If you have already customized your session token, you may need to merge this with what you currently have.
Create a global TypeScript definition
- In your application's root folder, create a
types/
directory. - Inside this directory, create a
globals.d.ts
file with the following code. This file will provide auto-completion and prevent TypeScript errors when working with roles. For this guide, only theadmin
andmoderator
roles will be defined.
Set the admin role for your user
Later in the guide, you will add a basic admin tool to change a user's role. For now, manually add the admin
role to your own user account.
- In the Clerk Dashboard, navigate to the Users page.
- Select your own user account.
- Scroll down to the User metadata section and next to the Public option, select Edit.
- Add the following JSON and select Save.
Create a reusable function to check roles
Create a helper function to simplify checking roles.
- In your application's root directory, create a
utils/
folder. - Inside this directory, create a
roles.ts
file with the following code. ThecheckRole()
helper uses theauth()
helper to access the user's session claims. From the session claims, it accesses themetadata
object to check the user's role. ThecheckRole()
helper accepts a role of typeRoles
, which you created in the Create a global TypeScript definition step. It returnstrue
if the user has that role orfalse
if they do not.
Create the admin dashboard
Now, it's time to create an admin dashboard. The first step is to create the /admin
route.
- In your
app/
directory, create anadmin/
folder. - In the
admin/
folder, create apage.tsx
file with the following placeholder code.
Protect the admin dashboard
To protect the /admin
route, choose one of the two following methods:
- Middleware: Apply role-based access control globally at the route level. This method restricts access to all routes matching
/admin
before the request reaches the actual page. - Page-level role check: Apply role-based access control directly in the
/admin
page component. This method protects this specific page. To protect other pages in the admin dashboard, apply this protection to each route.
Option 1: Protect the /admin
route using middleware
- In your app's root directory, create a
middleware.ts
file with the following code. ThecreateRouteMatcher()
function identifies routes starting with/admin
.clerkMiddleware()
intercepts requests to the/admin
route, and checks the user's role in theirmetadata
to verify that they have theadmin
role. If they don't, it redirects them to the home page.
Option 2: Protect the /admin
route at the page-level
- Add the following code to the
app/admin/page.tsx
file. ThecheckRole()
function checks if the user has theadmin
role. If they don't, it redirects them to the home page.
Create server actions for managing a user's role
- In your
app/admin/
directory, create an_actions.ts
file with the following code. ThesetRole()
action checks that the current user has theadmin
role before updating the specified user's role using Clerk's JavaScript Backend SDK. TheremoveRole()
action removes the role from the specified user.
Create a component for searching for users
- In your
app/admin/
directory, create aSearchUsers.tsx
file with the following code. The<SearchUsers />
component includes a form for searching for users. When submitted, it appends the search term to the URL as a search parameter. Your/admin
route will then perform a query based on the updated URL.
Refactor the admin dashboard
With the server action and the search form set up, it's time to refactor the app/admin/page.tsx
.
- Replace the code in your
app/admin/page.tsx
file with the following code. It checks whether a search parameter has been appended to the URL by the search form. If a search parameter is present, it queries for users matching the entered term. If one or more users are found, the component displays a list of users, showing their first and last names, primary email address, and current role. Each user hasMake Admin
andMake Moderator
buttons, which include hidden inputs for the user ID and role. These buttons use thesetRole()
server action to update the user's role.
Finished 🎉
The foundation of a custom RBAC (Role-Based Access Control) system is now set up. Roles are attached directly to the user's session, allowing your application to access them without the need for additional network requests. The checkRole()
helper function simplifies role checks and reduces code complexity. The final component is the admin dashboard, which enables admins to efficiently search for users and manage roles.
Feedback
Last updated on