Docs

Verify the active user's permissions in an organization

Important

The following authorization checks are predicated on a user having an active organization. Without this, they will likely always evaluate to false by default. Learn more about active organizations. If you would like to perform authorization checks without using Clerk's organizations feature, see the Role Based Access Control (RBAC) guide.

In general, you should always verify whether or not a user is authorized to access sensitive information, important content, or exclusive features. The most secure way to implement authorization is by checking the active user's role or permissions.

Clerk enables two broad approaches to role and permissions-based authorization:

  1. If you would like to immediately prevent unauthorized users from accessing content, you can:
    • Use the <Protect> component to prevent content from rendering if the active user is unauthorized.
    • Call auth.protect() to throw a 404 error if the active user is unauthorized.
  2. If you would like more control over the response when a user is unauthorized, you can:
    • Call the has() helper, which returns false if the active user lacks the role or permissions you're checking for. You can choose how your app responds instead of immediately preventing content from rendering or throwing an error.

Authorization in Client Components

The following examples work for both SSR and CSR.

The following example uses the <Protect> component to only render the form for users with the correct permission. The example uses the fallback prop to render a different UI if the user is not authorized.

/app/dashboard/settings/form.tsx
'use client'
import { Protect } from '@clerk/nextjs'

export default function SettingsForm() {
  return (
    <Protect
      permission="org:team_settings:manage"
      fallback={<p>You are not allowed to see this section.</p>}
    >
      <form>{/* Add UI for managing team settings */}</form>
    </Protect>
  )
}

The following example uses has() to inspect a user's permissions granularly. If the user doesn't have the permission, has() returns false, causing the component to return null instead of rendering its children.

/app/dashboard/settings/form.tsx
'use client'
import { useAuth } from '@clerk/nextjs'

export default function SettingsForm() {
  const { has } = useAuth()

  if (!has) return null

  // Check if the user is authorized
  const canManageSettings = 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 (!canManageSettings) return null

  // If the user is both authenticated and authorized, move forward with your logic
  return <form>{/* Add UI for managing team settings */}</form>
}

The following example uses the <Protect> component to only render the layout for users with the correct permission. If the user is not authorized, the component will not render its children.

Warning

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.

/app/dashboard/settings/layout.tsx
import type { PropsWithChildren } from 'react'
import { Protect } from '@clerk/nextjs'

export default function SettingsLayout(props: PropsWithChildren) {
  return <Protect permission="org:team_settings:read">{props.children}</Protect>
}

The following example uses has() to inspect a user's permissions granularly. If the user doesn't have the correct permission, has() returns false, causing the component to return null instead of rendering its children.

Warning

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.

/app/dashboard/settings/layout.tsx
import type { PropsWithChildren } from 'react'
import { auth } from '@clerk/nextjs/server'

export default async function SettingsLayout(props: PropsWithChildren) {
  const { has } = await auth()

  // Check if the user is authorized
  const canAccessSettings = 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 null.
  if (!canAccessSettings) return null

  return props.children
}

Warning

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

The following example uses auth.protect() to protect a RSC 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 uses has() to inspect a user's permissions granularly in a Next.js Server Action. If the user doesn't have the correct permission, has() returns 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'
    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 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>
  )
}

The following example demonstrates how to use has() in a Next.js Route Handler.

The example:

  • uses the userId returned from auth() 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.
app/api/get-teams/route.tsx
import { auth } from '@clerk/nextjs/server'

export const GET = async () => {
  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)
}

Warning

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

The following example uses auth.protect() to protect a Next.js 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 })
}

Use the getAuth() helper to access the has() helper in a Next.js Pages Router application.

The following example:

  • uses the userId returned from getAuth() to check if the user is signed in. If the user is not authenticated, the route 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 to return a 403 error.
src/pages/api/get-teams.ts
import { getAuth } from '@clerk/nextjs/server'

export default async function handler(req: NextApiRequest) {
  const { userId, has } = await getAuth(req)

  // Check if the user is authenticated
  if (!userId) return res.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 res.status(403)

  // If the user is both authenticated and authorized, move forward with your logic
  return users.getTeams(userId)
}

Authorization in Remix Loaders

The following example uses the has() helper to check if the user has the correct permission. If the user is not authorized, has() will return false, causing the loader to redirect the user to the /request-access route.

export const loader: LoaderFunction = async (args) => {
  const { has } = await getAuth(args)

  if (has({ permission: 'org:team_settings:manage' }) === false) {
    return redirect('/request-access')
  }

  return {}
}

export default function Settings() {
  return (
    <div>
      <h1>Settings Page</h1>
    </div>
  )
}

Authorization in JavaScript

If you are not using React or any of the meta-frameworks we support, you can use the Clerk JavaScript SDK. 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'

// Initialize Clerk with your Clerk publishable key
const clerk = new Clerk('YOUR_PUBLISHABLE_KEY')
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',
  })
}

Warning

It's best practice to use permission-based authorization over role-based authorization, as it reduces complexity and increases security. Usually, complex role checks can be refactored with a single permission check.

You can pass a role the same way you can pass a permission in all the examples above.

The following example uses <Protect>'s condition prop to conditionally render its children if the user has the correct role.

/app/dashboard/settings/Page.tsx
import { Protect } from '@clerk/nextjs'

export default function Page() {
  return (
    <Protect
      condition={(has) => has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })}
    >
      <p>Admin settings</p>
    </Protect>
  )
}

Warning

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

The following example uses auth.protect() to protect a RSC 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:admin or org:billing_manager role), 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(
    (has) => has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' }),
  )

  return <p>{userId} is authorized to access this page.</p>
}

The following example uses has() to inspect a user's roles granularly. If the user doesn't have the correct role, has() returns false, causing the component to return null instead of rendering its children.

/app/dashboard/settings/form.tsx
'use client'
import { useAuth } from '@clerk/nextjs'

export function SettingsForm() {
  const { has } = useAuth()

  // Check if the user is authorized
  const canAccessSettings = has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })

  // If has() returns false, the user does not have the correct permissions
  // You can choose how your app responds. This example returns null.
  if (!canAccessSettings) return null

  // If the user is both authenticated and authorized, move forward with your logic
  return <form>{/* Add UI for team settings */}</form>
}

How to add types for roles and permissions

In order to enhance typesafety in your project, you can define a global ClerkAuthorization interface, which defines the acceptable values for roles and permissions.

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.

In the example below, ClerkAuthorization is defined 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 roles and permissions, you can modify ClerkAuthorization to align with the roles and permissions configured in your Clerk application. See how in the example below, the default Clerk roles org:admin and org:member are replaced with custom roles org:super_admin, org:teacher, and org:student.

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