It's best practice to always verify whether or not a user is authorized to access sensitive information, important content, or exclusive features. Authorization is the process of determining the access rights and privileges of a user, ensuring they have the necessary permissions to perform specific actions.
Clerk provides two main features that can be used to implement authorization checks:
Useful for subscription-based and feature-based access control
You can use either options independently or combine them together depending on your application's needs.
There are a few methods to perform authorization checks:
The has() helper (recommended): returns false if the user is unauthorized.
Benefits: it offers flexibility and control over the response; if a user is not authorized, you can choose how your app responds.
Limitations: when checking for permissions, it only checks for custom permissions. To check for system permissions, you have to verify the user's role instead, which isn't as flexible.
The <Protect> component: prevents content from rendering if the user is unauthorized.
Benefits: it can be used both client-side and server-side (in Server Components).
Limitations: this component only visually hides its children when the current user is not authorized. The contents of its children remain accessible via the browser's source code even if the user fails the authorization check. Do not use this component to hide sensitive information that should be completely inaccessible to unauthorized users. For truly sensitive data, it's recommended to use has() to perform authorization checks on the server before sending the data to the client.
The auth.protect() helper: throws a 404 error if the user is unauthorized.
Benefits: checks if the user is both authenticated and authorized. First, for the authentication check, if the user is not authenticated, the helper will redirect the user to the sign-in page if used on page, or will throw a 404 if used in a Route Handler. Then, for the authorization check, if the user is not authorized, the helper will throw a 404 error.
Limitations: doesn't offer control over the response, and can only be used on the server-side.
This guide will show you how to implement authorization checks in order to protect actions, content, or entire routes based on the user's permissions, but the same concepts can be applied to roles, features, and plans. When calling the has() helper, you would simply replace the permission parameter with the appropriate access control type, such as role, feature, or plan.
When doing authorization checks, it's recommended to use permission-based over role-based, and feature-based over plan-based authorization, as these approaches are more granular, flexible, and more secure.
Note: Using has()on the server-side to check permissions works only with custom permissions, as system permissions aren't included in the session token claims. To check system permissions, verify the user's role instead.
Checking for a role or permission depends on the user having an active organization. Without an active organization, the authorization checks will likely always evaluate to false by default.
If you have both organizations and billing enabled, a permission check will only work if the feature part of the permission key (org:<feature>:<permission>) is a feature included in the organization's active plan. For example, say you want to check if an organization member has the custom permission org:teams:manage, where teams is the feature. Before performing the authorization check, you need to ensure that the user's organization is subscribed to a plan that has the teams feature. If not, the authorization check will always return false, even if the user has the custom permission.
Be cautious when doing authorization checks in layouts, as these don't re-render on navigation, meaning the user session won't be checked on every route change. Read more in the Next.js docs.
The has()Clerk Icon helper returns false if the user does not have the correct access control. If they aren't authorized, you can choose how your app responds. It can be used to perform authorization checks in pages, route handlers, and Server Actions (Next.js only) to protect them from unauthorized access.
Warning
Using has()on the server-side to check permissions works only with custom permissions, as system permissions aren't included in the session token claims. To check system permissions, verify the user's role instead.
Protect a page
Protect a route handler
Protect a Server Action
The following example demonstrates how to perform authorization checks in a page in order to protect the content from unauthorized access. It uses has() to check if the user has the org:team_settings:manage permission. If they aren't authorized, null is returned and the page isn't rendered.
import { auth } from'@clerk/nextjs/server'exportdefaultasyncfunctionPage() {// Use `auth()` to access the `has()` helper// For other frameworks, use the appropriate method for accessing the `Auth` objectconst { has } =awaitauth()// Check if the user is authorizedconstcanManage=has({ permission:'org:team_settings:manage' })// If has() returns false, the user does not have the correct permissions// You can choose how your app responds. This example returns null.if (!canManage) returnnullreturn <h1>Team Settings</h1>}
The following example demonstrates how to perform authorization checks in a route handler in order to protect it from unauthorized access. It
uses the isAuthenticated returned from the Auth objectClerk Icon to check if the user is signed in. If the user is not authenticated, the Route Handler will return a 401 error.
uses has() to check if the user has the correct permission. If the user is not authorized, has() will return false, causing the Route Handler to return a 403 error.
This example is written for Next.js App Router, but can be adapted to other frameworks by using the appropriate method for accessing the Auth objectClerk Icon.
app/api/get-teams/route.tsx
import { auth } from'@clerk/nextjs/server'exportconstGET=async () => {// Use `auth()` to access the `has()` helper and the `userId`// For other frameworks, use the appropriate method for accessing the `Auth` objectconst { isAuthenticated,userId,has } =awaitauth()// Check if the user is authenticatedif (!isAuthenticated) {returnResponse.json({ error:'User is not signed in' }, { status:401 }) }// Check if the user is authorizedconstcanRead=has({ permission:'org:team_settings:read' })// If has() returns false, the user does not have the correct permissions// You can choose how your app responds. This example returns a 403 error.if (!canRead)returnResponse.json({ error:'User does not have the correct permissions' }, { status:403 })// If the user is both authenticated and authorized, move forward with your logicreturnusers.getTeams(userId)}
The following example demonstrates how to perform authorization checks in a Server Action in order to protect the action from unauthorized access. It
uses the isAuthenticated returned from the Auth objectClerk Icon to check if the user is signed in. If the user is not authenticated, the Server Action will return a 401 error.
uses has() to check if the user has the correct permission. If the user is not authorized, has() will return false, causing the Server Action to return a 403 error.
app/components/ExampleServerComponent.tsx
import { auth } from'@clerk/nextjs/server'exportdefaultasyncfunctionExampleServerComponent() {asyncfunctionmyServerAction(formData:FormData) {'use server'// Use `auth()` to access the `has()` helper and the `userId`const { isAuthenticated,has,userId } =awaitauth()// Check if the user is authenticatedif (!isAuthenticated) {returnResponse.json({ error:'User is not signed in' }, { status:401 }) }// Check if the user is authorizedconstcanManage=has({ permission:'org:team_settings:manage' })// If has() returns false, the user does not have the correct permissions// You can choose how your app responds. This example returns a 403 error.if (!canManage)returnResponse.json({ error:'User does not have the correct permissions' }, { status:403 })// If the user is both authenticated and authorized, move forward with your logicreturnusers.getTeams(userId) }return ( <formaction={myServerAction}> {/* Add UI for managing team settings */} <buttontype="submit">Submit</button> </form> )}
The <Protect> component prevents content from rendering if the user does not have the correct access control. If they aren't authorized, you can pass a fallback UI to the fallback prop. Under the hood, it uses the has()Clerk Icon helper so it can only check for custom permissions. It can be used both client-side and server-side (in Server Components).
The following example uses the <Protect> component to only render the content for users with the org:team_settings:manage permission. If they aren't authorized, <Protect> will render the fallback UI that's passed to the fallback prop.
app/page.tsx
exportdefaultfunctionPage() {return ( <Protectpermission="org:team_settings:manage"fallback={<p>You do not have the permissions to manage team settings.</p>} > <form>{/* Add UI for managing team settings */}</form> </Protect> )}
The following example demonstrates how to use auth.protect()Next.js Icon to protect a page from unauthenticated and unauthorized access.
If the user is not authenticated, auth.protect() will redirect the user to the sign-in route.
If the user is authenticated but is not authorized (as in, does not have the org:team_settings:read permission), auth.protect() will throw a 404 error.
If the user is both authenticated and authorized, auth.protect() will return the user's userId.
app/dashboard/settings/page.tsx
import { auth } from'@clerk/nextjs/server'exportdefaultasyncfunctionPage() {const { userId } =awaitauth.protect({ permission:'org:team_settings:read' })return <p>{userId} is authorized to access this page.</p>}
The following example demonstrates how to use auth.protect()Next.js Icon to protect a route handler from unauthenticated and unauthorized access.
If the user is not authenticated nor authorized (as in, does not have the org:team_settings:manage permission), auth.protect() will throw a 404 error.
If the user is both authenticated and authorized, auth.protect() will return the user's userId.
import { Clerk } from'@clerk/clerk-js'constclerkPubKey=import.meta.env.VITE_CLERK_PUBLISHABLE_KEYconstclerk=newClerk(clerkPubKey)awaitclerk.load()// Check if the user is authenticatedif (clerk.isSignedIn) {// Check if the user is authorizedconstcanManageSettings=clerk.session.checkAuthorization({ permission:'org:team_settings:manage', })}
In order to enhance typesafety in your project, you can define a global ClerkAuthorization interface, which defines the acceptable values for custom access control types.
Note
By default, roles and permission types, such as OrganizationCustomRoleKey and OrganizationCustomPermissionKey, are assigned string. However, if a ClerkAuthorization type is defined, it will be utilized instead.
The following example demonstrates how to define a global ClerkAuthorization interface with the default roles that Clerk provides.
types/globals.d.ts
export {}declare global {interfaceClerkAuthorization { permission:'' role:'org:admin'|'org:member' }}
Because Clerk supports custom access control types, you can modify ClerkAuthorization to align with the custom access control types configured in your Clerk application. See the following example, where the default Clerk roles org:admin and org:member are replaced with custom roles org:super_admin, org:teacher, and org:student, and custom permissions are also added.
types/globals.d.ts
export {}declare global {interfaceClerkAuthorization { permission:'org:quiz:create'|'org:quiz:grade'|'org:quiz:read'|'org:quiz:fill' role:'org:super_admin'|'org:teacher'|'org:student' }}