Authorize users
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:
- Organizations
- Users can be assigned roles and permissions
- Useful for role-based and permission-based access control
- Billing
- Users can subscribe to plans and features
- 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): returnsfalse
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 a404
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 a404
error. - Limitations: doesn't offer control over the response, and can only be used on the server-side.
- 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
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
.
Important considerations
- 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.
- Note: Using
- 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 would like to perform role-based authorization checks without using Clerk's organizations feature, see the Role Based Access Control (RBAC) guide.
- 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.
Use has()
for authorization checks
The has()
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.
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.
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
object.
import { auth } from '@clerk/nextjs/server'
export default async function Page() {
// Use `auth()` to access the `has()` helper
// For other frameworks, use the appropriate method for accessing the `Auth` object
const { has } = await auth()
// Check if the user is authorized
const canManage = 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) return null
return <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
userId
returned from theAuth
object to check if the user is signed in. If the user is not authenticated, the Route Handler will return a401
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 a403
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
object.
import { auth } from '@clerk/nextjs/server'
export const GET = async () => {
// Use `auth()` to access the `has()` helper and the `userId`
// For other frameworks, use the appropriate method for accessing the `Auth` object
const { userId, has } = await auth()
// Check if the user is authenticated
if (!userId) {
return Response.json({ error: 'User is not signed in' }, { status: 401 })
}
// Check if the user is authorized
const canRead = 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)
return Response.json({ error: 'User does not have the correct permissions' }, { status: 403 })
// If the user is both authenticated and authorized, move forward with your logic
return users.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
userId
returned from theAuth
object to check if the user is signed in. If the user is not authenticated, the Server Action will return a401
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 a403
error.
import { auth } from '@clerk/nextjs/server'
export default async function ExampleServerComponent() {
async function myServerAction(formData: FormData) {
'use server'
// Use `auth()` to access the `has()` helper and the `userId`
const { has, userId } = await auth()
// Check if the user is authenticated
if (!userId) {
return Response.json({ error: 'User is not signed in' }, { status: 401 })
}
// Check if the user is authorized
const canManage = 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)
return Response.json({ error: 'User does not have the correct permissions' }, { status: 403 })
// If the user is both authenticated and authorized, move forward with your logic
return users.getTeams(userId)
}
return (
<form action={myServerAction}>
{/* Add UI for managing team settings */}
<button type="submit">Submit</button>
</form>
)
}
Use <Protect>
for authorization checks
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()
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.
export default function Page() {
return (
<Protect
permission="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()
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 a404
error. - If the user is both authenticated and authorized,
auth.protect()
will return the user'suserId
.
import { auth } from '@clerk/nextjs/server'
export default async function Page() {
const { userId } = await auth.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()
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 a404
error. - If the user is both authenticated and authorized,
auth.protect()
will return the user'suserId
.
import { auth } from '@clerk/nextjs/server'
export const GET = async () => {
const { userId } = await auth.protect({
permission: 'org:team_settings:manage',
})
return Response.json({ userId })
}
Authorization checks in JavaScript
If you are not using React-based frameworks, you can use the Clerk JavaScript SDK to perform authorization checks. The following example demonstrates how to use the checkAuthorization()
method to check if a user is authorized.
import { Clerk } from '@clerk/clerk-js'
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
const clerk = new Clerk(clerkPubKey)
await clerk.load()
// Check if the user is authenticated
if (clerk.user) {
// Check if the user is authorized
const canManageSettings = clerk.session.checkAuthorization({
permission: 'org:team_settings:manage',
})
}
Add custom types
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.
The following example demonstrates how to define a global ClerkAuthorization
interface with the default roles that Clerk provides.
export {}
declare global {
interface ClerkAuthorization {
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.
export {}
declare global {
interface ClerkAuthorization {
permission: 'org:quiz:create' | 'org:quiz:grade' | 'org:quiz:read' | 'org:quiz:fill'
role: 'org:super_admin' | 'org:teacher' | 'org:student'
}
}
Feedback
Last updated on