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. Clerk's built-in <OrganizationSwitcher/> component anduseOrganizationList().setActive({ organization: <orgId> }) method are two available options to set an organization as active for a user.

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.
  1. 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 examples below work for both SSR and CSR. Examples are written for Next.js App Router but they are supported by any React meta framework, such as Remix or Gatsby.

The following example uses Clerk's <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 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>
    )
  }

Use the useAuth() hook to access the has() helper in Client Components.

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 function SettingsForm(){
    const { has } = useAuth();

    const canManageSettings = has({ permission: "org:team_settings:manage" });

    if(!canManageSettings) return null;

    return (
      <form>
        {/* Add UI for managing team settings */}
      </form>
    )
  }

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

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

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

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

    const canAccessSettings = has({ permission: "org:team_settings:read" });

    if(!canAccessSettings) return null;

    return props.children;
  }

Warning

auth().protect() only works for App Router and is considered experimental.

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/layout.tsx
import type { PropsWithChildren } from "react";
import { auth } from "@clerk/nextjs/server";

export default function SettingsLayout(props: PropsWithChildren){
  const { userId } = auth().protect({ permission: "org:team_settings:read" })

  return props.children
}

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 function ExampleServerComponent() {
    async function myAction(formData: FormData) {
      'use server'
      const { has } = auth();

      const canManage = has({ permission:"org:team_settings:manage" });

      // If has() returns false, the user does not have the correct permissions
      if(!canManage) return Response.json({ error: "User does not have the correct permissions" }, { status: 403 });

      // Add logic for managing team settings
    }

    return (
      <form action={myAction}>
        {/* 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 { useAuth } from "@clerk/nextjs";

  export const GET = () => {
    const { userId, has } = auth();

    if(!userId){
      return Response.json({ error: "User is not signed in" }, { status: 401 })
    }

    const canRead = has({ permission: "org:team_settings:read" });

    if(!canRead) return Response.json({ error: "User does not have the correct permissions" }, { status: 403 });

    return users.getTeams(userId)
  }

Warning

auth().protect() only works for App Router and is considered experimental.

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 { useAuth } from "@clerk/nextjs";

  export const POST = () => {
    const { userId } = auth().protect({
      permission: "org:team_settings:manage"
    })

    return users.createTeam(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);

    if (!userId) return res.status(401);

    const canRead = has({ permission: "org:team_settings:read" });

    if(!canRead) return res.status(403);

    return users.getTeams(userId);
  }
export const loader: LoaderFunction = async args => {
  const { has } = await getAuth(args);

  if(has({permission: "org:team_settings:manage"})){
    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

Performing role checks is not considered a best-practice and developers should avoid it as much as possible. 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/layout.tsx
  import type { PropsWithChildren } from "react";
  import { Protect } from "@clerk/nextjs";

  export default function SettingsLayout(props: PropsWithChildren){
    return (
      <Protect
        condition={has => has({role: "org:admin"}) || has({role: "org:billing_manager"})}
      >
        {props.children}
      </Protect>
    )
  }

Warning

auth().protect() only works for App Router and is considered experimental.

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/layout.tsx
  import type { PropsWithChildren } from "react";
  import { auth } from "@clerk/nextjs/server";

  export default function SettingsLayout(props: PropsWithChildren){
    const { userId } = auth().protect(has => has({ role: "org:admin" }) || has({ role: "org:billing_manager" }))

    return props.children
  }

Use the useAuth() hook to access the has() helper in Client Components.

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();

    const canAccessSettings = has({ role: "org:admin" }) || has({ role: "org:billing_manager" })

    if(!canAccessSettings) return null;

    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

[!INFO] By default, types related to roles and permissions, such as OrganizationCustomRoleKey and OrganizationCustomPermissionKey, will be assigned to string if ClerkAuthorization is not defined. If you define ClerkAuthorization in your project, OrganizationCustomRoleKey and OrganizationCustomPermissionKey will be assigned to the keys of the ClerkAuthorization interface.

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?