Skip to main content
Docs

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
  • 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): 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.

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.
  • 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.

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.

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.

app/page.tsx
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 the Auth object 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 object.

app/api/get-teams/route.tsx
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 the Auth object 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'

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.

app/page.tsx
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>
  )
}

Warning

auth.protect() is only available for App Router, and only works on the server-side.

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 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'

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 a 404 error.
  • If the user is both authenticated and authorized, auth.protect() will return the user's userId.
app/api/create-team/route.tsx
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.

main.js
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.

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 {
  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.

types/globals.d.ts
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

What did you think of this content?

Last updated on