diff --git a/docs/guides/basic-rbac.mdx b/docs/guides/basic-rbac.mdx
index 89f2730d9c..c112e4d34f 100644
--- a/docs/guides/basic-rbac.mdx
+++ b/docs/guides/basic-rbac.mdx
@@ -3,7 +3,7 @@ title: Implement basic Role Based Access Control (RBAC) with metadata
description: Learn how to leverage Clerk's publicMetadata to implement your own basic Role Based Access Controls.
---
-To control which users can access certain parts of your application, you can leverage the [roles feature.](/docs/organizations/roles-permissions#roles) Although Clerk offers a roles feature as part of the feature set for [organizations](/docs/organizations/overview), 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.**
+To control which users can access certain parts of your app, you can use the [roles feature](/docs/organizations/roles-permissions#roles). Although Clerk offers roles as part of the [organizations](/docs/organizations/overview) 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.
@@ -14,8 +14,7 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
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.
- 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions).
- 1. In the top navigation, select **Configure**. Then in the sidebar, select **Sessions**.
+ 1. In the Clerk Dashboard, navigate to the [**Sessions**](https://dashboard.clerk.com/last-active?path=sessions) page.
1. Under the **Customize session token** section, select **Edit**.
1. 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.
@@ -31,10 +30,8 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
### Create a global TypeScript definition
- 1. In your application's root folder, create a `types` directory.
- 1. Inside this directory, add a `globals.d.ts` file. This file will provide auto-completion and prevent TypeScript errors when working with roles.
-
- For this guide, only the `admin` and `moderator` roles will be defined.
+ 1. In your application's root folder, create a `types/` directory.
+ 1. 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 the `admin` and `moderator` roles will be defined.
```ts {{ filename: 'types/globals.d.ts' }}
export {}
@@ -55,8 +52,7 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
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.
- 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=users) .
- 1. In the top navigation, select **Users**.
+ 1. In the Clerk Dashboard, navigate to the [**Users**](https://dashboard.clerk.com/last-active?path=users) page.
1. Select your own user account.
1. Scroll down to the **User metadata** section and next to the **Public** option, select **Edit**.
1. Add the following JSON and select **Save**.
@@ -72,11 +68,10 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
Create a helper function to simplify checking roles.
1. In your application's root directory, create a `utils/` folder.
- 1. Inside this directory, add a `roles.ts` file.
- 1. Create a `checkRole()` helper that uses the [`auth()`](/docs/references/nextjs/auth) helper to access the user's session claims. From the session claims, access the `publicMetadata` object to check the user's role. The `checkRole()` helper should accept a role of type `Roles`, which you created in the [Create a global TypeScript definition](#create-a-global-typescript-definition) step. It should return `true` if the user has that role or `false` if they do not.
+ 1. Inside this directory, create a `roles.ts` file with the following code. The `checkRole()` helper uses the [`auth()`](/docs/references/nextjs/auth) helper to access the user's session claims. From the session claims, it accesses the `metadata` object to check the user's role. The `checkRole()` helper accepts a role of type `Roles`, which you created in the [Create a global TypeScript definition](#create-a-global-typescript-definition) step. It returns `true` if the user has that role or `false` if they do not.
```ts {{ filename: 'utils/roles.ts' }}
- import { Roles } from '@/types/global'
+ import { Roles } from '@/types/globals'
import { auth } from '@clerk/nextjs/server'
export const checkRole = async (role: Roles) => {
@@ -93,8 +88,7 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
Now, it's time to create an admin dashboard. The first step is to create the `/admin` route.
1. In your `app/` directory, create an `admin/` folder.
- 1. In the `admin/` folder, create a file named `page.tsx`.
- 1. Add the following placeholder code to the file.
+ 1. In the `admin/` folder, create a `page.tsx` file with the following placeholder code.
```tsx {{ filename: 'app/admin/page.tsx' }}
export default function AdminDashboard() {
@@ -107,16 +101,14 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
To protect the `/admin` route, choose **one** of the two following methods:
1. **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.
- 1. **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.
+ 1. **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.
> [!IMPORTANT]
> You only need to follow **one** of the following methods to secure your `/admin` route.
#### Option 1: Protect the `/admin` route using middleware
- 1. In your app's root directory, create a `middleware.ts` file.
- 1. Use the `createRouteMatcher()` function to identify routes starting with `/admin`.
- 1. Apply `clerkMiddleware` to intercept requests to the `/admin` route, and check the user's role in their `publicMetadata` to verify that they have the `admin` role. If they don't, redirect them to the home page.
+ 1. In your app's root directory, create a `middleware.ts` file with the following code. The `createRouteMatcher()` function identifies routes starting with `/admin`. `clerkMiddleware()` intercepts requests to the `/admin` route, and checks the user's role in their `metadata` to verify that they have the `admin` role. If they don't, it redirects them to the home page.
```tsx {{ filename: 'middleware.ts' }}
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
@@ -144,16 +136,16 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
#### Option 2: Protect the `/admin` route at the page-level
- 1. Navigate to your `app/admin/page.tsx` file.
- 1. Use the `checkRole()` function to check if the user has the `admin` role. If they don't, redirect them to the home page.
+ 1. Add the following code to the `app/admin/page.tsx` file. The `checkRole()` function checks if the user has the `admin` role. If they don't, it redirects them to the home page.
```tsx {{ filename: 'app/admin/page.tsx' }}
- import { auth } from '@clerk/nextjs/server'
+ import { checkRole } from '@/utils/roles'
import { redirect } from 'next/navigation'
- export default function AdminDashboard() {
+ export default async function AdminDashboard() {
// Protect the page from users who are not admins
- if (!checkRole('admin')) {
+ const isAdmin = await checkRole('admin')
+ if (!isAdmin) {
redirect('/')
}
@@ -161,15 +153,9 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
}
```
- ### Add admin tools to find users and manage roles
+ ### Create server actions for managing a user's role
- You can use the `checkRole()` function along with server actions to build basic tools for finding users and managing roles.
-
- Create a server action for managing a user's role.
-
- 1. In your `app/admin/` directory, create an `_actions.ts` file.
- 1. Create a server action that sets a user's role. Use the `checkRole()` function to verify that the current user has the `admin` role. If they do, proceed to update the specified user's role using the [JavaScript Backend SDK](/docs/references/backend/user/update-user). This ensures that only administrators can modify user roles.
- 1. Create a server action that removes a user's role.
+ 1. In your `app/admin/` directory, create an `_actions.ts` file with the following code. The `setRole()` action checks that the current user has the `admin` role before updating the specified user's role using Clerk's [JavaScript Backend SDK](/docs/references/backend/user/update-user). The `removeRole()` action removes the role from the specified user.
```ts {{ filename: 'app/admin/_actions.ts' }}
'use server'
@@ -178,13 +164,15 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
import { clerkClient } from '@clerk/nextjs/server'
export async function setRole(formData: FormData) {
+ const client = await clerkClient()
+
// Check that the user trying to set the role is an admin
if (!checkRole('admin')) {
return { message: 'Not Authorized' }
}
try {
- const res = await clerkClient().users.updateUser(formData.get('id') as string, {
+ const res = await client.users.updateUser(formData.get('id') as string, {
publicMetadata: { role: formData.get('role') },
})
return { message: res.publicMetadata }
@@ -194,8 +182,10 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
}
export async function removeRole(formData: FormData) {
+ const client = await clerkClient()
+
try {
- const res = await clerkClient().users.updateUser(formData.get('id') as string, {
+ const res = await client.users.updateUser(formData.get('id') as string, {
publicMetadata: { role: null },
})
return { message: res.publicMetadata }
@@ -205,10 +195,9 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
}
```
- With the server action set up, now build the `` component. This component includes a form for searching users, and when submitted, appends the search term to the URL as a search parameter. Your `/admin` route will then perform a query based on the updated URL.
+ ### Create a component for searching for users
- 1. In your `app/admin/` directory, create a `SearchUsers.tsx` file.
- 1. Add the following code to the file.
+ 1. In your `app/admin/` directory, create a `SearchUsers.tsx` file with the following code. The `` 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.
```tsx {{ filename: 'app/admin/SearchUsers.tsx' }}
'use client'
@@ -230,7 +219,7 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
router.push(pathname + '?search=' + queryTerm)
}}
>
-
+
@@ -239,12 +228,11 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
}
```
- With the server action and the search form set up, it's time to refactor the `app/admin/page.tsx`. It will check whether a search parameter has been appended to the URL by the search form. If a search parameter is present, it will query for users matching the entered term.
+ ### Refactor the admin dashboard
- If one or more users are found, the component will display a list of users, showing their first and last names, primary email address, and current role. Each user will have `Make Admin` and `Make Moderator` buttons, which include hidden inputs for the user ID and role. These buttons will use the `setRole()` server action to update the user's role.
+ With the server action and the search form set up, it's time to refactor the `app/admin/page.tsx`.
- 1. Navigate to your `app/admin/page.tsx` file.
- 1. Replace the code with the following code.
+ 1. 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 has `Make Admin` and `Make Moderator` buttons, which include hidden inputs for the user ID and role. These buttons use the `setRole()` server action to update the user's role.
```tsx {{ filename: 'app/admin/page.tsx' }}
import { redirect } from 'next/navigation'
@@ -253,14 +241,18 @@ This guide assumes that you're using Next.js App Router, but the concepts can be
import { clerkClient } from '@clerk/nextjs/server'
import { removeRole, setRole } from './_actions'
- export default async function AdminDashboard(params: { searchParams: { search?: string } }) {
+ export default async function AdminDashboard(params: {
+ searchParams: Promise<{ search?: string }>
+ }) {
if (!checkRole('admin')) {
redirect('/')
}
- const query = params.searchParams.search
+ const query = (await params.searchParams).search
+
+ const client = await clerkClient()
- const users = query ? (await clerkClient().users.getUserList({ query })).data : []
+ const users = query ? (await client.users.getUserList({ query })).data : []
return (
<>