Clerk
Blog

Back

Engineering


Dec 13, 2023

Back

Engineering


Dec 13, 2023

Introducing has(), protect(), and <Protect>

Colin Sidoti

Colin Sidoti


Authorization is now a core feature of Clerk – learn more about our new authorization helpers for B2B SaaS


Clerk is best-known for our dead-simple authentication with <SignIn/> and <SignUp/> components. But since launching our B2B SaaS Suite for onboarding business customers, our top feature request has become better authorization capabilities: not just helping developers determine who a user is, but also what permissions they have within their organization.

Today we’ve launched two new features to make robust authorization easy:

  1. Custom roles and permissions
  2. has(), protect(), and <Protect> – our new authorization helpers

Custom roles and permissions

In Clerk’s dashboard, developers can now define custom roles, which their business customers can assign to their employees. Previously, roles were restricted to just “admin” and “member.”

In addition, fine-grained permissions can now be defined and mapped to roles. When used properly, this extra layer of abstraction allows developers to create additional roles as-needed, without making any changes to their codebase.

Each role and permission defined in Clerk’s dashboard has a corresponding key, like org:admin or org:widgets:create.

The new role manager in Clerk's dashboard
The new role manager in Clerk's dashboard

has(), protect(), and <Protect>

To make it easy to leverage permissions for authorization checks, we’ve introduced three new helpers in our SDKs.

1. has()

Developers can use the has() function on both the frontend and the backend to determine if the active user “has” a particular role or permission.

has() can be used anywhere that Clerk returns an “auth” object:

  • auth() in Next.js App Router
  • useAuth() in client components, including during SSR
  • getAuth(request) in server contexts outside of the App Router (Remix Loaders, Node, Express, Fastify, etc)
1
// Within a server component, server action, or route handler
2
const CreateWidget = () => {
3
const { has } = auth();
4
if(!has({permission: "org:widgets:create"})){
5
return null;
6
}
7
...
8
}
9
10
// Within a client component
11
export const AdminsOnly = () => {
12
const { has } = useAuth();
13
if(!has({role: "org:admin"})){
14
return null;
15
}
16
...
17
}
18
19
// Within an Express API route
20
app.post('/api/widgets', (req, res) => {
21
const { has } = getAuth(req);
22
if(!has({permission: "org:widgets:create"})){
23
return null;
24
}
25
...
26
})

2. protect()

The protect() function is a declarative extension of has(), which takes definitive action to block further processing, instead of allowing developers to manually handle the unauthorized case. It is intended to be used in cases where unauthorized users are assumed to be attackers.

1
export const POST = () => {
2
const { userId, orgId } = auth().protect({permission: "org:widgets:create"});
3
4
// Protect would throw an error if the user is unauthorized
5
// If this point is reached, it is guaranteed that userId and ordId exist,
6
// and that userId has the "org:widgets:create" permission in orgId
7
8
// Bonus: TypeScript knows that userId and orgId exist!
9
...
10
}

Consider an endpoint that your product would never issue a request to unless the current user is authorized. For example, you may have a page with a button that can issue requests to that endpoint, but the page is only accessible to authorized users. That is the perfect place to use protect() – you don't need a precisely-written error, just confidence that an attacker's requests will fail.

Note: protect() has launched in Next.js only, though we will extend the helper to other frameworks in the future. Please use feedback.clerk.com to request the feature for your framework.

3. <Protect>

Similar to the protect() function, the <Protect> component is intended to block further processing. It only renders children for authorized users, and optionally renders a fallback for unauthorized users.

<Protect> can be used across all React frameworks, in CSR, SSR, and RSC contexts.

1
export const Navigation = () => {
2
return (
3
<nav>
4
<Link href="/">Home</Link>
5
<Link href="/widgets">Widgets</Link>
6
<Protect role="org:admin">
7
<Link href="/admin">Admin Panel</Link>
8
</Protect>
9
</nav>
10
)
11
}

Documentation

For full details and usage information, please see our updated Organization documentation, including:

Authorization is now a core feature of Clerk

With today’s launch, authorization has become a core feature of Clerk, and you will see significant, continued investment in the future.

In 2024, we will extend our authorization helpers to support entitlements, so Clerk can be used to gate features based on the subscription plan a customer selected.

Further, you likely noticed above that every role and permission is namespaced with the org: prefix, which relates to the Organization resource. We recognize this is limiting, and in the future we will be extending our authorization helpers to work with other resources.

Clerk's logo

Start now,
no strings attached

Start completely free for up to 10,000 monthly active users and up to 100 monthly active orgs. No credit card required.

Start Building

Pricing built for
businesses of all sizes.

Learn more about our transparent per-user costs to estimate how much your company could save by implementing Clerk.

View pricing
Clerk's logo

Newsletter!

The latest news and updates from Clerk, sent to your inbox.

Clerk logo

Clerk - Complete User Management

TwitterLinkedInGitHubDiscordFacebook

© 2023 Clerk Inc.


product
Components

© 2023 Clerk Inc.