# Clerk Blog — Page 6

# Comparing Clerk Webhooks vs Backend API
URL: https://clerk.com/blog/webhooks-v-bapi.md
Date: 2024-08-29
Category: Guides
Description: Learn when to use Clerk Webhooks or the Backend API to efficiently access user data and avoid unnecessary complexity.

This post compares Clerk Webhooks and the Backend API, focusing on their roles in querying user data specifically.

Whether you need to query information about a specific unauthenticated user, a list of users, or synchronize Clerk user data with another system, this comparison will help you choose the best option for your circumstances.

> \[!IMPORTANT]
> This guide specifically addresses situations where querying data about unauthenticated users is necessary. For guidance on reading data about the currently authenticated user from your server or client, please refer to [A guide to reading authenticated user data from Clerk](/blog/read-user-data-guide).

## What is the Backend API?

The [Clerk Backend API](/docs/reference/backend-api) is designed to query or update information from your Clerk application, such as user data.

You can query users one at a time, or, if you need a list of users, it's possible to effectively batch the query to improve efficiency.

Backend API requests are [limited](/docs/backend-requests/resources/rate-limits) to 100 per 10 seconds for your Clerk application. While the Backend API is straightforward to use, you should be judicious so as not to exceed your request allowance, otherwise, your application might stop working properly.

### How does the Backend API work?

The easiest way to interface with the Backend API is by using a Clerk backend SDK. The most popular option is the [JavaScript Backend SDK](https://clerk.com/docs/references/backend/overview) although there are [others](https://clerk.com/docs/references/overview).

To retrieve a specific user, call [`getUser`](/docs/references/backend/user/get-user) with their identifier. This awaitable function returns a [Clerk `User` object](/docs/references/backend/overview) populated with all the information Clerk stores about the user.

```ts {{ title: 'getUser Example' }}
const userId = 'user_123'

const response = await clerkClient.users.getUser(userId)

console.log(response)

// _User {
//   id: 'user_123',
//   passwordEnabled: true,
//   totpEnabled: false,
//   backupCodeEnabled: false,
//   twoFactorEnabled: false,
//   banned: false,
//   locked: false,
//   createdAt: 1708103362688,
//   updatedAt: 1708103362701,
//   imageUrl: 'https://img.clerk.com/eyJ...',
//   hasImage: false,
//   primaryEmailAddressId: 'idn_123',
//   primaryPhoneNumberId: null,
//   primaryWeb3WalletId: null,
//   lastSignInAt: null,
//   externalId: null,
//   username: null,
//   firstName: 'Test',
//   lastName: 'User',
//   publicMetadata: {},
//   privateMetadata: {},
//   unsafeMetadata: {},
//   emailAddresses: [
//     _EmailAddress {
//       id: 'idn_123',
//       emailAddress: 'testclerk123@gmail.com',
//       verification: [_Verification],
//       linkedTo: []
//     }
//   ],
//   phoneNumbers: [],
//   web3Wallets: [],
//   externalAccounts: [],
//   lastActiveAt: null,
//   createOrganizationEnabled: true
// }
```

When you need to fetch a list of users by their IDs, [`getUserList`](/docs/references/backend/user/get-user-list) effectively batches `getUser` queries into one. This is not only simpler than sending and handling a sequence of requests, it's more efficient as well. Because this operation initiates only one API call under the hood, your backend request allowance goes further.

```ts {{ title: 'getUserList Example' }}
const userId = ['user_123', 'user_456']

const response = await clerkClient.users.getUserList({ userId })

console.log(response)
// {
//   data: [
//     _User {
//       id: 'user_123',
//       passwordEnabled: false,
//       totpEnabled: false,
//       backupCodeEnabled: false,
//       twoFactorEnabled: false,
//       banned: false,
//       locked: false,
//       createdAt: 1707561967007,
//       updatedAt: 1707561967095,
//       imageUrl: 'https://img.clerk.com/eyJ...',
//       hasImage: true,
//       primaryEmailAddressId: 'idn_123',
//       primaryPhoneNumberId: null,
//       primaryWeb3WalletId: null,
//       lastSignInAt: 1707561967014,
//       externalId: null,
//       username: null,
//       firstName: 'First',
//       lastName: 'Test',
//       publicMetadata: {},
//       privateMetadata: {},
//       unsafeMetadata: {},
//       emailAddresses: [Array],
//       phoneNumbers: [],
//       web3Wallets: [],
//       externalAccounts: [Array],
//       lastActiveAt: 1707523200000,
//       createOrganizationEnabled: true
//     },
//     _User {
//       id: 'user_456',
//       passwordEnabled: false,
//       totpEnabled: false,
//       backupCodeEnabled: false,
//       twoFactorEnabled: false,
//       banned: false,
//       locked: false,
//       createdAt: 1707539597250,
//       updatedAt: 1707539597331,
//       imageUrl: 'https://img.clerk.com/eyJ...',
//       hasImage: true,
//       primaryEmailAddressId: 'idn_456',
//       primaryPhoneNumberId: null,
//       primaryWeb3WalletId: null,
//       lastSignInAt: 1707539597260,
//       externalId: null,
//       username: null,
//       firstName: 'Second',
//       lastName: 'Test',
//       publicMetadata: {},
//       privateMetadata: {},
//       unsafeMetadata: {},
//       emailAddresses: [Array],
//       phoneNumbers: [],
//       web3Wallets: [],
//       externalAccounts: [Array],
//       lastActiveAt: 1707523200000,
//       createOrganizationEnabled: true
//     }
//   ],
//   totalCount: 2
// }
```

While the focus of this post is querying user data, the Backend API also supports manipulating user data with `createUser`, `updateUser`, and specific helpers like `banUser`. Additionally, the Backend API supports similar operations for organizations, sessions, and more.

> \[!NOTE]
> Explore [everything the Backend API has to offer](/docs/reference/backend-api) in the reference documentation.

## What are Clerk Webhooks?

A [Webhook](/docs/integrations/webhooks/overview) is a way for Clerk to send data to another system when specific events happen, such as when a user is created or updated.

They're most commonly used to register events with external systems, send analytics events, and synchronize databases with Clerk.

Think of Webhooks like a notification that automatically sends information to a URL you specify, allowing different systems to react to Clerk to events when they happen.

Webhooks are more complex than calling the Backend API. You need to [verify that the request came from Clerk](/docs/integrations/webhooks/overview#protect-your-webhooks-from-abuse), manage occasional duplicate and out-of-order events, plus handle the asynchronous nature of Webhooks, which can complicate building synchronous workflows such as a [custom onboarding flow](/blog/add-onboarding-flow-for-your-application-with-clerk). Despite these challenges, Webhooks do not enforce any rate limits. Clerk will send as many Webhooks events as your server can handle.

### How do Clerk Webhooks work?

To enable Webhook events, register your Webhook endpoints from the dashboard. Once configured, Clerk will push event data to these endpoints as events occur in your Clerk application.

Example events:

- `user.created`
- `user.updated`
- `user.deleted`

> \[!TIP]
> Configure your Webhook endpoints to receive only the necessary event types for your integration. Listening for unnecessary or all events can strain your server, which we strongly advise against.

Clerk uses svix to ensure Webhooks are delivered reliably with [retries and other mechanisms](/docs/integrations/webhooks/overview#how-clerk-handles-delivery-issues).

![Flowchart illustrating the process of handling webhooks with Clerk. On the left, the Clerk logo is connected to an event that triggers a webhook, such as user creation. This event is linked to a Clerk-powered application on the right side via the Internet/Local network. Below, the Svix logo is connected to a tunnel (e.g., ngrok, localtunnel), which then routes to the webhook route in your application. The flow shows how events from Clerk trigger webhooks that are routed through Svix and tunnels to reach the application.](./webhooks_diagram.webp)

> \[!TIP]
> [Learn more about Webhooks](/docs/integrations/webhooks/overview) and [how to debug them](/docs/integrations/webhooks/debug-your-webhooks) in the documentation.

## Comparing Webhooks vs Backend API

Below is a table summarizing the differences between Webhooks and the Backend API.

Use it to understand your options at a glance or reference the next time you return to this page.

|                           | Backend API                                         | Webhooks                                                                                                                  |
| ------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Purpose                   | Directly query and manipulate user data from Clerk. | Used to send events from Clerk to another system, allowing for automated and instantaneous communication between systems. |
| Model                     | Request/response (manual fetching or polling).      | Event-driven.                                                                                                             |
| Response handling         | Initiate and `await` API responses.                 | Must handle events asynchronously.                                                                                        |
| Implementation complexity | Requires API calls, typically simpler to implement. | Requires setting up reliable and secure endpoints to receive Webhooks.                                                    |
| Scalability               | Dependent on rate limit.                            | Can handle high volumes of events when needed.                                                                            |
| Reliability               | Highly available.                                   | Dependent on the quality of your Webhook endpoint implementation.                                                         |
| Example                   | Example functions:  `getUser` and `getUserList`.    | Example events:  `user.created`, `user.updated`, and `user.deleted`.                                                      |

Key differences:

- **Complexity** Using Webhooks to keep your database in sync with Clerk can be more complex than calling the Backend API due to their asynchronous nature.
- **Rate limits** The Backend API imposes rate limits, which can impede your application's functionality if exceeded. You can often avoid these limits by using [alternative methods to read authenticated user data](/blog/read-user-data-guide) and by batching queries with `getUserList` when appropriate. Unlike the Backend API, Webhooks don't have rate limits. For this reason, synchronizing your database with Clerk using Webhooks is a viable alternative to the Backend API if you are likely to surpass the request allowance.
- **Always up-to-date** When you query records from the Backend API, you query the freshest data from the source of truth. This is in contrast to reading from a database synchronized with Clerk using Webhooks, which might not have received the latest events yet.
- **Synchronous vs asynchronous** With the Backend API, you control when to initiate an API call and how to handle the response, making it a predictable method. By comparison, Webhooks provide an asynchronous mechanism to receive and react to events. They are best used for things like sending notifications or updating systems where it isn't critical that the data is immediately up-to-date. For example, an email provider for weekly newsletters or an analytics platform for daily event rollups.
- **Adding or updating resources** The Backend API is suitable for both querying and manipulating data, unlike Webhooks which can only react to events.

## Guidance

### When the Backend API is better

- **Straightforward access** The Backend API is the most straightforward tool to programmatically query the most up-to-date data from Clerk. It can support a variety of use cases, provided you don't encroach on the rate limits.
- **Synchronous events** The Backend API's request-response pattern is well-suited to operations that require synchronization or that must act in a serialized fashion, such as an onboarding flow.

### When Webhooks are better

- **Integrations** Webhooks are the preferred method to update external systems with data from Clerk. Use them to notify another system like an email platform or CMS when a new Clerk user is created or if they update their email.
  Or send events to an analytics platform like Google Analytics or [Posthog](/blog/how-to-use-clerk-with-posthog-identify-in-nextjs).
  You can even use Webhooks to [create new user notifications in Discord or Slack](https://x.com/r_marked/status/1816551015543447575)!
- **Synchronizing Clerk with your database** The Backend API limits can be restrictive for certain applications. For instance, a B2C social media application where you frequently need to render user information with their posts. In such cases, you would likely hit the Backend API limit quickly. Webhooks provide a means to synchronize your database with Clerk. You can then query your database to the sidestep rate limit.

---

# Automate Neon schema changes with Drizzle and GitHub Actions
URL: https://clerk.com/blog/automate-neon-schema-changes-with-drizzle-and-github-actions.md
Date: 2024-08-22
Category: Guides
Description: Learn about schema migrations and how they can be applied to a Neon database with Drizzle and GitHub Actions

While it’s relatively straightforward to deploy updated code to different environments, applying the same techniques to a relational database can be disastrous.

Application code is stateless, meaning you can rebuild the code to recreate the application at any time. In fact, when deploying updated code to platforms like Vercel, the tooling will simply build and ship the latest version of the application, overwriting the previous one.

However, databases are stateful since the value is in the data contained within them. Applying the same deployment methodology to a database (removing and replacing it with a new version) would be detrimental to your users or business.

In this article, you’ll learn what schema migrations are, how they can be used to safely make database changes, and how to automate those changes to a Neon database using Drizzle and GitHub Actions.

## What is a schema migration?

Schema migrations are a way to apply changes to the schema of a database in a controlled way.

Typically each schema migration is a SQL script that is applied to the database to update the schema to the latest version. The migration files can stored in version control so the state of the schema can be tracked over time.

Schema migrations are used for updating the database schema as changes are required, but can also be used to create new environments. To recreate the database up to a specific point in time, the migrations can applied in the same order they were generated.

### Schema migrations and deployments

When building an application that uses a relational database, you’ll often have a different database per environment. Each database is isolated from one another so modifying the schema in one database environment does not affect the others.

Let's say you have two environments, a dev environment for building new features, and a production environment that your users are actively using.

When a feature is complete, merging your code from the `dev` branch to `main` is relatively straightforward. Once the newest version of the application is built, the artifacts of that build are deployed into the production environment, replacing the previous version.

The latest schema migrations are also applied to the database, updating the schema to support the latest version of the application. If done properly, all of the data within the database will remain intact.

## How to generate and apply schema migrations with Drizzle

Now that you understand what schema migrations are and how they are used when deploying new versions of your database, let’s explore a practical example using Drizzle.

Drizzle is a type-safe ORM for applications built with TypeScript. The team also built Drizzle Kit for managing the schema of the database. Drizzle Kit can be used to analyze your TypeScript models, generate schema migrations from the models, and apply them to the database.

### The demo application

The remainder of this article uses a sample to-do app to demonstrate how to use schema migrations to apply database changes. The application uses a Postgres database hosted by Neon, with a relatively simple schema.

I’ll be showing the process of adding a single column to the `tasks` table (shown below) so that each task can have a description stored with it.

![A database table showing columns named "name", "is\_done", "owner\_id", "created\_on", and "created\_by\_id"](./table1.png)

To simulate multiple environments, the Neon database has a `main` branch that contains the production data and a `dev` branch that is an isolated environment used for adding and testing new features.

> \[!NOTE]
> All of the code shown in this article is available [on GitHub](https://github.com/bmorrisondev/team-todo-demo).

### Updating the development environment

I’ll start by updating the code on the `dev` branch to support a “description” field of the `tasks` model:

```ts {{ filename: 'src/db/schema.ts', ins: [8] }}
export const tasks = pgTable('tasks', {
  id: serial('id').primaryKey(),
  name: text('name'),
  is_done: boolean('is_done'),
  owner_id: text('owner_id'),
  created_in: timestamp('created_on'),
  created_by_id: text('created_by_id'),
  description: text('description'),
})
```

The application has the following `drizzle.config.ts` which Drizzle Kit uses to locate the schema files in the project, generate and store migrations, and connect to the database:

```ts {{ filename: 'drizzle.config.ts' }}
import type { Config } from 'drizzle-kit'

export default {
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL as string,
  },
  verbose: true,
  strict: true,
} satisfies Config
```

Next, I’ll run the following command which will generate a new schema migration file. I’m also setting the `DATABASE_URL` environment variable used by `drizzle.config.ts`:

```bash
export DATABASE_URL=postgresql://teamtodo_owner:mydbpass@ep-weathered-wildflower-a5okjpjr.us-east-2.aws.neon.tech/teamtodo?sslmode=require
drizzle-kit generate
```

A new file is automatically generated and placed in the `drizzle` folder with the necessary SQL to apply:

```sql {{ filename: 'drizzle/0001_loose_mojo.sql' }}
ALTER TABLE "tasks" ADD COLUMN "description" text;
```

Next, I’ll run the following command to update the database schema in Neon, adding the `description` column:

```bash
export DATABASE_URL=postgresql://teamtodo_owner:mydbpass@ep-weathered-wildflower-a5okjpjr.us-east-2.aws.neon.tech/teamtodo?sslmode=require
drizzle-kit migrate
```

Once the migration is applied, the new column is added to the database and is ready to test. The schema will now look like this:

![A database table showing columns named "name", "is\_done", "owner\_id", "created\_on", "created\_by\_id", and "description"](./table2.png)

### Updating the production environment

When I’m ready to move the code to production, I can apply the migrations to the `main` database branch by passing in that branch's connection string:

```bash
export DATABASE_URL=postgresql://teamtodo_owner:mydbpass@ep-frosty-tree-a54nb30r.us-east-2.aws.neon.tech/teamtodo?sslmode=require
drizzle-kit migrate
```

Once the migrations are applied to the `main` database branch, I can deploy the production version of my application. Platforms like Vercel (which I am using to host this application) will commonly monitor the `main` branch of a repository for changes and kick off the deploy process when a change is detected. Merging my code changes into `main` will trigger Vercel’s CI tools to deploy the newest version of the code.

I can deploy my application to Vercel by merging the changes into the `main` code branch and letting Vercel’s CI tools deploy the newest version of the code.

While this example makes a single change, multiple schema migrations can be generated and applied between deployments for more complex schema changes. The files will be applied in the order they were created, ensuring that the state of the production database matches the development environment.

### Automating migrations with GitHub Actions

Using Drizzle Kit to apply schema migrations helps to ensure that your schema is safely updated between deployments, but manually performing this operation introduces a point of failure in the process. If you forget to apply the migrations, and your schema doesn't match what the application expects, you could inadvertently take your service down.

GitHub Actions, a platform built into GitHub, provides a way for developers to define workflows that execute automatically when specific events (such as making changes to a repository branch) occur on GitHub. Let’s look at how GitHub Actions can automatically apply migrations when the code on the `main` branch changes.

First, I’ll need to securely store the connection string for my database in GitHub in a way that the GitHub Actions service can access it. This is done by adding a repository secret in **"Settings"** under **"Secrets and variables"**, then **"Actions"**.

![GitHub repository settings, adding the "DATABASE\_URL" secret.](./gha.png)

The following GitHub Actions workflow can be used to execute `drizzle-kit migrate` each time a change is performed on the `main` branch in GitHub:

```yaml
name: Apply schema migrations

# 👉 Only run this workflow when a change is made to the main branch
on:
  push:
    branches:
      - main

jobs:
  apply_migrations:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install dependencies & tooling
        run: |
          npm install
          npm install -g drizzle-orm drizzle-kit pg
      - name: Apply migrations
        run: drizzle-kit migrate
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
```

Since Vercel triggers a deployment when changes are made on the `main` repository branch, the Actions workflow will trigger simultaneously with the deployment, ensuring that your code and schema versions always stay in sync.

## Conclusion

Properly applying changes to a database when new features are added to an application is important to retaining the data within the database, as well as maintaining the uptime of your application.

After reading this article, you now understand what schema migrations are and how they’re used in the development lifecycle of a database. You should also know how to use Drizzle to generate and apply migrations, and how to automate this process using GitHub Actions.

---

# A guide to reading authenticated user data from Clerk
URL: https://clerk.com/blog/read-user-data-guide.md
Date: 2024-08-15
Category: Guides
Description: Learn how to access data about the currently authenticated user with Clerk's APIs and session claims.

Reading data about the authenticated user is a fundamental part of any application.

Whether you need to access the user's ID, username, contact information, or profile data, Clerk provides various methods to accomplish this depending on your runtime environment and application needs.

Options are great! But they can also make it challenging to decide which approach to take. I wrote this guide to explain the different methods and their appropriate use cases. This way, you can choose the most suitable and efficient option for your circumstances.

> \[!IMPORTANT]
> This post is about accessing data about the currently-authenticated user. If you want to query data about unauthenticated users, you can use the Backend API or Webhooks. For guidance on which is best for your circumstances, please refer to [Clerk Webhooks vs BackendAPI](/blog/webhooks-v-bapi).

## Methods to read authenticated user data

Below is a table summarizing the different approaches to read  authenticated user data from Clerk.

Use it to understand your options at a glance or reference the next time you return to this page.

![Read User Data Guide setup guide](./table.png)

## Frontend API with `useUser()`

Clerk's [Frontend API](/docs/reference/frontend-api) enables authenticated users to access their own data and perform actions specific to their account from a browser or native environment.

To access the authenticated user, you could call the `/v1/me` endpoint. However, the convenient option in Next.js is to invoke [`useUser()`](/docs/references/react/use-user), which queries `/v1/me` under the hood.

```tsx {{ title: 'useUser()' }}
'use client'
import { useUser } from '@clerk/nextjs'

export default function Example() {
  const { isLoaded, isSignedIn, user } = useUser()

  if (!isLoaded || !isSignedIn) {
    return null
  }

  return <div>Hello, {user.firstName} welcome to Clerk</div>
}
```

Certain Frontend API requests are rate-limited but `/v1/me` is not. This endpoint has no defined limit, making it practically unlimited.

### Guidance

When to use Frontend API with `useUser()`:

- Call `useUser()` from a client component when you need to update the UI with information about the authenticated user.
- Best-suited for when you need to dynamically update the UI in response to a client event. `useUser()` returns a [`User`](/docs/references/javascript/user/user) with a `reload()` function that makes it even easier to render the most up-to-date user information.
- Also useful for loading user information on page load, though server-side rendering (SSR) may be preferred for this.

When to avoid:

- `useUser()` won't work from a server environment and is therefore inappropriate for server-side-rendering user information.
- Unsuitable for accessing user private metadata.
- Avoid querying and sending user data from the frontend to the backend. Fetch and verify the data directly on the backend to ensure its integrity.

## Backend API with `currentUser()`

Clerk's [Backend API](/docs/reference/backend-api) is designed to query user data from a server environment.

The [`currentUser()`](/docs/references/nextjs/current-user) helper is used to access information about the currently authenticated user from server components, server actions, and other server-side code like so:

```tsx {{ title: 'currentUser()' }}
import { auth, currentUser } from '@clerk/nextjs/server'

export default async function Page() {
  // Get the Backend API User object when you need access to the user's information
  const user = await currentUser()
  // Use `user` to render user details or create UI elements
}
```

> \[!IMPORTANT]
> `currentUser()` calls Clerk's Backend API behind the scenes. This counts towards your application's [Backend API request rate limit](/docs/backend-requests/resources/rate-limits#backend-api-requests) of 100 requests per 10 seconds.

### Guidance

When to use `currentUser()`:

- Call `currentUser()` from a Next.js server component, server action, API route handler, or other backend code where you require data about the authenticated user.
- Suitable for accessing [user private metadata](/docs/users/metadata#private-metadata).
- A good option when you need to query potentially large attributes such as `user.organizatons` from a backend environment. Dynamic attributes like these are usually too big for custom session claims (session claims are the subject of the next section).
- Suitable for accessing user data infrequently due to rate limits.

When to avoid:

- Do not exceed 100 requests per 10 seconds. This limit applies to *all* Backend API requests made by your application. Exceeding this limit will result in a `429 Too Many Requests` error and your application might not work properly.
- Avoid querying and storing user data in your database. If a user updates their information via a Clerk component like [`<UserProfile />`](https://clerk.com/docs/components/user/user-profile), your database won't be synchronized. Instead, store only the user ID and fetch the latest user data from Clerk as needed.

> \[!NOTE]
> If you *only* need the authenticated user ID, calling the Backend API is not efficient.
>
> Read the `userId` default session claim with `auth()` or `useAuth()` using the guidance in the [next section](#session-claims) instead.

## Session claims

Claims are pieces of information about the authenticated user. They're encoded in the [Clerk session token](/docs/backend-requests/resources/session-tokens) and digitally signed to ensure the information is authentic.

Claims are accessible from frontend or backend environments, and since they don't require an API roundtrip, rate limits do not apply.

The user ID is included in the token by [default](/docs/backend-requests/resources/session-tokens#default-session-claims). However, the user's username, contact information, and other attributes are not. To access this optional data, it's necessary to customize the session token with custom claims.

### Read user ID

Below are two code examples demonstrating how to access the authenticated user's ID on the client and on the server with the  [`useAuth()`](/docs/references/react/use-auth) and [`auth()`](/docs/references/nextjs/auth) helpers respectively:

```tsx {{ title: 'useAuth()' }}
'use client'
import { useAuth } from '@clerk/nextjs'

export default function Page() {
  const { isLoaded, userId, sessionId } = useAuth()

  // In case the user signs out while on the page.
  if (!isLoaded || !userId) {
    return null
  }

  return (
    <div>
      Hello, {userId} your current active session is {sessionId}
    </div>
  )
}
```

```tsx {{ title: 'auth()' }}
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  // Get the userId from auth() -- if null, the user is not signed in
  const { userId } = auth()

  if (userId) {
    // Use `userId` to store an entity in the database
  }
}
```

### Set and read custom session claims

To customize session claims for your application, open the Clerk dashboard then click **Sessions**.

Look for the "Customize session token section" and press **Edit**.

Define a JSON structure that includes dynamic placeholders called shortcodes (remember to **Save** after). Clerk will replace these shortcodes with the actual values at runtime:

```json {{ title: 'Claims' }}
{
  "firstName": "{{user.first_name}}",
  "lastName": "{{user.last_name}}",
  "email": "{{user.primary_email_address}}",
  "avatarUrl": "{{user.image_url}}",
  "publicMetadata": "{{user.public_metadata}}"
}
```

Read custom session claims from a backend environment with the same [`auth()`](/docs/references/nextjs/auth) helper and the returned `sessionClaims` property.

```tsx {{ title: 'auth()' }}
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  // Get the userId from auth() -- if null, the user is not signed in
  const { userId, sessionClaims } = auth()

  if (userId && sessionClaims) {
    const { firstName, lastName, email, avatarUrl, publicMetadata } = sessionClaims
  }
}
```

> \[!TIP]
> To get auto-complete and prevent TypeScript errors when working with custom session claims, [define a global type](/docs/backend-requests/making/custom-session-token#add-global-type-script-type-for-custom-session-claims).

Custom session claims are technically accessible from frontend environments, but this is rarely needed, so the Next.js SDK doesn't provide a dedicated helper.

### Guidance

When to use session claims:

- Custom session claims are best suited for scenarios where you need to frequently access certain user attributes from the backend, but the Backend API rate limits would normally be prohibitive.
- In either frontend or server environments, session claims are the most efficient way to access just the user ID. This eliminates the need for unnecessary API calls.

When to avoid:

- The *entire* session token must not exceed 4KB due to browser cookie size limits. Use good judgment when including custom claims such as `user.organizations`, which can cause the token to exceed this limit and break your app. Only store essential, predictably-sized user data in the token, and occasionally fetch larger claims through separate Backend API calls.
- Unsuitable for accessing user private metadata.
- Do not use custom session claims in a frontend environment. It requires less configuration and code to use the Frontend API with `useUser()`, as described at [the top](#frontend-api-with-use-user) of this guide.

---

# Role based access control with Clerk Organizations
URL: https://clerk.com/blog/role-based-access-control-with-clerk-orgs.md
Date: 2024-08-09
Category: Guides
Description: Learn what role based access control is and how to use it with Clerk Organizations to simplify permissions management.

Managing permissions in large SaaS applications can be a nightmare.

Providing team owners a way to grant functionality to users in a simplified way can be the difference between companies purchasing your software or going with a competitor. Clerk provides you with a way to build this functionality with minimal effort. By utilizing roles and permissions built into [Organizations](/docs/organizations/overview), you can implement role-based access control for your users.

> RBAC is essential for B2B applications. [Learn more about our B2B SaaS solution](/b2b-saas) for comprehensive enterprise features.

In this article, you'll learn how roles and permissions in Clerk can be used to allow functionality within an application for a user based solely on the role they are assigned.

## What is Role-Based Access Control?

Role-Based Access Control (RBAC) is a method of managing application security by granting permissions to systems based on the role assigned to the user.

When a user signs into an application, they provide their credentials to prove their identity. This process of the user proving *who they are* is known as Authentication in cybersecurity. Role-based access control is involved in the process of Authorization, which is identifying *what they can do*.

With RBAC, individual permissions can be linked to one or more roles. Roles are often correlated to the job function of the user, granting them access to everything they need to fulfill their duties while preventing them from having access to what they don't need.

As your system evolves, you can simply update the permissions assigned to a given role to grant those members access to new functionality. This approach to security makes security management easier compared to assigning permissions to individual users.

## Implement RBAC with organizations

The example used in this article is built on the concept of a team-based task management app built with Next.js, where each team is an organization in Clerk.

There will be three roles each with a different set of permissions included. Based on the role assigned to the user, their ability to perform operations within the app will vary. The following diagram demonstrates how three separate users with different roles will inherit their permissions:

- Charlie will have the Viewer role and can view the tasks, but not create or modify them.
- Bob has the Member and will be able to add and edit their own tasks, but not tasks created by another user.
- Alice will have the Manager role and will be able to manage all tasks in the organization.

![A diagram showing how permissions roll into roles and are then assigned to users.](./rbac-diagram.png)

The code referenced in this article is open-source and can be [accessed on GitHub](https://github.com/bmorrisondev/team-todo-demo/tree/article-3) in the `article-3` branch.

## Defining custom roles and permissions

When you enable organizations for a Clerk application, there are two default roles and a set of default permissions.

The Admin role contains all of the necessary permissions to manage the organization and its members, and the Member role can view the active organization, but not manage it. Developers can create custom roles and permissions that can be assigned by organization administrators as well, located in the Clerk dashboard in the **"Organization settings"** of the side nav under **"Roles"** and **"Permissions"** respectively.

In this example, there are three custom permissions created:

| Name         | Key          | Description                       |
| ------------ | ------------ | --------------------------------- |
| Manage tasks | tasks:manage | Allow users to edit all tasks.    |
| Edit tasks   | tasks:edit   | Allows users to edit their tasks. |
| View tasks   | tasks:view   | Allows users to view tasks.       |

There are also two custom roles created, and the Member role is updated to include the `View tasks` and `Edit tasks` permissions:

| Name     | Key     | Description                                                     | Permissions                                        |
| -------- | ------- | --------------------------------------------------------------- | -------------------------------------------------- |
| Manager  | manager | Users with this role can create tasks and edit all tasks.       | View tasks, Edit tasks, Manage tasks, Read Members |
| Member\* | member  | Users with this role can create tasks and edit their own tasks. | View tasks, Edit tasks, Read members               |
| Viewer   | viewer  | Users with this role can only view tasks.                       | View tasks, Read members                           |

- The Member role is a default role.

Now when a user is invited, administrators can set their role even before they accept the invitation.

![The Clerk user invite modal with brian@clerk.dev populated in the text area and a red arrow pointing to a dropdown showing the Admin role selected.](./invite-users.png)

## Adjusting functionality based on permissions

A role defines a set of permissions, but the permissions themselves should dictate what parts of the application users within that role have access to.

Clerk provides [a set of authorization helper functions](/blog/introducing-authorization) that can be used to check if the active user has a specific set of permissions and appropriately adjust the way the application behaves. The following example demonstrates how the `has()` function can be used with the name of the permission to determine if the user can create or edit tasks:

```ts {{ filename: 'src/app/security.ts' }}
import { auth } from '@clerk/nextjs/server'

export function canCreateTasks() {
  const { sessionClaims, has } = auth()
  if (!isLicensed()) return false
  let canCreateTasks = false
  // 👉 If there is no org, it's the user's personal account
  if (!sessionClaims?.org_id) {
    canCreateTasks = true
  }
  // 👉 Check to make sure the user has the 'org:tasks:edit' permission
  if (sessionClaims?.org_id && has({ permission: 'org:tasks:edit' })) {
    canCreateTasks = true
  }
  return canCreateTasks
}

export function canEditTask(createdById: string) {
  if (!isLicensed()) return false
  const { userId, sessionClaims, has } = auth()
  let canEditTask = false
  // 👉 If there is no org, it's the user's personal account
  if (!sessionClaims?.org_id) {
    canEditTask = true
  } else {
    // 👉 If the user has the 'org:tasks:manage' permission, they can edit any task
    if (has({ permission: 'org:tasks:manage' })) {
      canEditTask = true
      // 👉 If the user has the 'org:tasks:edit' permission AND the user IDs match, they can edit this task
    } else if (has({ permission: 'org:tasks:edit' }) && createdById === userId) {
      canEditTask = true
    }
  }
  return canEditTask
}
```

These security functions are passed into the rendered components to determine if they should be disabled:

```tsx {{ filename: 'src/app/page.tsx' }}
<div className="flex flex-col">
  <AddTaskForm disabled={!canCreateTasks()} />
  <div className="flex flex-col gap-2 p-2">
    {tasks.map((task) => (
      <TaskRow key={task.id} task={task} disabled={!canEditTask(task.created_by_id)} />
    ))}
  </div>
</div>
```

The security functions can also be used to verify user's permissions in server actions. The following function is executed when the user wants to create a task. By leveraging the `canCreateTasks()` function, an erorr is thrown if the user attempts to do something they are not permitted to do:

```ts {{ filename: 'src/app/actions.ts' }}
import { canCreateTasks, getUserInfo } from './security'

export async function createTask(name: string) {
  if (!canCreateTasks()) {
    throw new Error('User not permitted to create tasks')
  }

  const { userId, ownerId } = getUserInfo()
  await sql`
    insert into tasks (name, owner_id, created_by_id) values (${name}, ${ownerId}, ${userId});
  `
}
```

## Working directly with roles and permissions

While Clerk offers a simple way to check the active user's permissions out of the box, there may be a situation where you want to check their roles or permissions manually.

By default, a user's organizational permissions can be accessed through the `sessionClaims` object of the `auth()` function:

```ts
const { sessionClaims } = auth()
```

This gives you the flexibility to leverage the roles and permissions as you see fit. The following sample shows the structure of the `sessionClaims` if a user has selected an organization:

```json
{
  "azp": "http://localhost:3005",
  "exp": 1721770193,
  "iat": 1721770133,
  "iss": "https://assuring-cod-50.clerk.accounts.dev",
  "jti": "daeb648e4c6dbfbdd2ce",
  "nbf": 1721770123,
  "org_id": "org_2ieWEfZl0M6ccS1Ap1XVRbEm9Kk",
  "org_metadata": {
    "isLicensed": true
  },
  "org_permissions": ["org:tasks:edit", "org:tasks:view", "org:tasks:manage"],
  "org_role": "org:manager",
  "org_slug": "d2-gamers",
  "sid": "sess_2jfFHBJQpqzwZbnwHXti0yxCz1G",
  "sub": "user_2iVvo8iFCJQJ1WCXeBk5T9lUTO5"
}
```

## Conclusion

With minimal effort, role-based access control is made accessible to applications of any size using organizations in Clerk. By checking the list of permissions a user has based on their assigned role, you can easily enable different areas of your application, or restrict functionality.

---

# Mitigating OAuth’s recently discovered Open Response Type vulnerability
URL: https://clerk.com/blog/open-response-type-vulnerability.md
Date: 2024-08-07
Category: Company
Description: How Clerk mitigated the recently discovered Open Response Type vulnerability

On July 29, security researchers at Salt [released details](https://salt.security/blog/over-1-million-websites-are-at-risk-of-sensitive-information-leakage---xss-is-dead-long-live-xss)
of an OAuth vulnerability that can reliably be chained with any XSS vulnerability
to establish a long-lived account takeover. This is a general OAuth vulnerability, not particular to Clerk’s implementation.

At Clerk, we believe it’s our responsibility to protect our customers from vulnerabilities
like this, so we worked quickly to understand the attack and devise a mitigation.
Fortunately, we discovered that **Clerk’s default configuration is not susceptible to this
chaining, and over 99.7% of our customers were already protected.** For the last 0.3%, we
released an update today that mitigates the attack.

In this post, we dive into the technical details of the vulnerability, why most customers were protected by default, and how we mitigated the attack for the last few.

## Understanding the Open Response Type vulnerability: a clever variation of the Open Redirect

### The OAuth happy path

The OAuth flow starts by redirecting a user to the “authorization URL” of an identity
provider, with `redirect_uri` and `response_type` in the query param, as well as an application identifier and the scope of authorization requested. Here’s a sample for Google:

```plaintext {{ mark: [2] }}
https://accounts.google.com/o/oauth2/auth
  ?redirect_uri=https%3A%2F%2Fmyapp.com%2Foauth_callback
  &response_type=code
  &client_id={oauth_client_id}
  &scope={requested_authorization_scopes}
```

On Google’s website, the user is asked to authorize the requested scope, which
normally only includes profile information so the user can be signedin.

After authorization, the user is redirected back to the `redirect_uri` using
the given `response_type`. In this case, the response type is `code`, which means
a single-use secret code is appended to the `redirect_uri` as a query param:

```
https://myapp.com/oauth_callback?code={secret_code}
```

### The Open Redirect vulnerability

An “Open Redirect” is when an attacker tricks a user into visiting an
authorization URL with a nefarious value passed to `redirect_uri`.
For example, instead of passing `https://myapp.com/oauth_callback`,
they can pass `https://attackapp.com/oauth_callback`. This accomplishes two things:

1. `https://myapp.com/oauth_callback` doesn’t receive the secret code,
   so its single-use nature does not provide any protection.
2. The attacker gets access to a secret code.  With it, they can open
   a new browser on their own machine, and navigate to the valid `redirect_uri`
   with the code appended. This tricks the application into granting a
   session for the victim to the attacker.

Thankfully, the OAuth specification guides implmentors on how to prevent Open Redirects, and Google and
other identity providers maintain a strict allowlist of acceptable `redirect_uri`s. The allowlist ensures that
the user, *and the secret code,* will not be sent to a URL the attacker controls.

### The Open Response Type vulnerability

In OAuth, the response type dictates the format and mechanism for how
the identity provider passes sensitive information back to the application.
With `code`, a single-use token is passed by query param.
With `token`, an access token is passed by the URL’s hash fragment.

Unlike `redirect_uri`s, the `response_type` parameter is not strictly
bound to a per-application allowlist. This lack of enforcement is pivotal
to the attack, which is why we’re calling it the “Open Response Type”
vulnerability. Here is [Salt’s explanation](https://salt.security/blog/over-1-million-websites-are-at-risk-of-sensitive-information-leakage---xss-is-dead-long-live-xss) of how the parameter was leveraged:

> Note that we changed the original OAuth link to Google - we used the response
> type “code,token” instead of only “code”, which makes Google send the code in
> the hash fragment (#code=...). This enables us to read the code from the URL
> and ensure Hotjar doesn’t consume the code, which can be consumed only once.

After changing the response type to `code,token`, the secret code is returned
in the fragment instead of the query param. In all likelihood, the application
implementing OAuth has not prepared for a secret code to be in the fragment,
so it doesn’t consume it, and it remains in the URL instead.

Put another way: the open nature of `response_type` means an attacker has a
reliable way to get a valid, unused secret code in the URL bar.

Thankfully, getting the code in the URL bar alone is not sufficient, since the
attacker also must be able to read its value. This is not normally possible,
but it is when an XSS vulnerability is present. Depending on the page the XSS
is present on, it may be as easy as reading `window.location.href`.
In Salt’s case, they opened a new tab to the Authorization URL via `window.open`,
then polled the tab’s `location` until the OAuth flow completed.

### The impact

You might be wondering: **Do we really need to worry about Open Response Type
if it needs to be chained with an XSS attack?**

The answer is **absolutely yes**. When managing user sessions on the web, it’s
critical to mitigate the effects of XSS as much as possible. After an XSS is
patched, it should be impossible for the attacker to continue acting on behalf
of any users.

This is why developers use the `HttpOnly` directive when managing session cookies:
it ensures that session tokens cannot be extracted for the attacker to continue
using after the XSS is patched. Without this feature, developers would need to
revoke sessions after an XSS attack and force users to sign in again.

The challenge with the Open Response Type vulnerability is that it completely
bypasses the protections afforded by `HttpOnly`. The attacker is able to generate
new sessions, and those sessions will continue to be valid after the XSS is patched.

## Mitigating the Open Response Type vulnerability

The Open Response Type vulnerability relies on two behaviors:

1. The user must land on a page with an unused code in the URL bar.
2. The application must have an open XSS vulnerability that allows them to
   programmatically read the URL bar.

Below, we list two techniques that can be used to suppress these behaviors.

### Mitigation 1: Process the OAuth code on a separate origin

At the top of this post, we mentioned that over 99.7% of Clerk customers
were protected by default. That is because Clerk forces developers to set
the OAuth `redirect_uri` to be an endpoint on our API, which is normally
hosted on a subdomain like `https://clerk.yourdomain.com`. When the Open
Response Type attack is attempted against a Clerk customer, the user would
land on URL on this subdomain, like:

```
https://clerk.yourdomain.com/v1/oauth_callback#code={secret_code}
```

Fortunately, this subdomain does not serve HTML, so it’s exceedingly unlikely
to be the source of an XSS. An XSS on any other subdomain cannot read the URL
of a tab on the Clerk subdomain, since it breaks the web’s cross-origin rules.

If your application also leverages a separate origin for processing the OAuth
code (e.g. `identity.yourdomain.com` or `auth.yourdomain.com`), it will also
be protected as long as that origin never introduces an XSS.

While this technique helps, it’s hard to guarantee that you will never introduce
an XSS on the origin processing the OAuth code. For that reason, we recommend
using Mitigation 2 instead.

### Mitigation 2: Eliminate unexpected fragments

Some Clerk customers use a reverse proxy to host our API directly on their
primary domain, so OAuth codes are not processed on a separate origin. For
these users, and for a more robust solution overall, we needed to find an
additional mitigation.

Our solution is to eliminate unexpected fragments from the URL bar before
an XSS vulnerability has the chance to read them. To do this, we’ve added an extra redirect to any OAuth failure that eliminates
the fragment.

Again, let’s consider a user that lands on this URL as a result of an Open
Response Type attack:

```
https://clerk.yourdomain.com/v1/oauth_callback#code={secret_code}
```

Fragments aren’t sent to the server, so our server cannot tell that `code`
is even present. But, this URL is expecting `code` in the query param, and
since it’s missing, Clerk is going to return an error.

Instead of simply returning the error, we are now issuing a redirect first.
We issue the redirect with status=301 and a Location header that has a
trailing #, which clears any fragment that might exist:

```
Location: https://clerk.yourdomain.com/v1/oauth_callback#
```

Since we use Clerk at Clerk, you can confirm the behavior by visiting the
URL on our domain with a fake code – the code will be stripped away.
(You’ll notice we add an extra query param to prevent an infinite redirect.)

```
https://clerk.clerk.com/v1/oauth_callback#code=fakerandomcode
```

This behavior is easy to implement in your own OAuth handler and will
effectively protect you against Open Response Type attacks.

---

# Per-user B2B monetization with Stripe and Clerk Organizations
URL: https://clerk.com/blog/per-user-licensing-with-stripe-and-clerk-organizations.md
Date: 2024-08-02
Category: Guides
Description: Learn how to architect a B2B application for per-user licensing with Stripe and Clerk Organizations

Businesses tend to spend more money on software compared to individual consumers.

One of the most popular monetization models for B2B applications is the Per-User model, where a business purchases one “seat” for each user who will be using the application. Per-user pricing is a great way for application developers to generate income. The model is relatively straightforward, provides predictable pricing for finance departments, and allows for a steady stream of income that scales with the use of your application

In this article, you'll learn how Clerk Organizations and Stripe can be configured to implement per-user monetization into a web application.

> This guide focuses on implementation details. For a comprehensive overview of B2B SaaS architecture and features, [learn more about our B2B SaaS solution](/b2b-saas).

## Project Overview

This article will use an open-source application called Team Task that is preconfigured with the functionality described below. All of the critical parts of the code that enable per-user licensing will be thoroughly explained, however, you are welcome to dive right into the [code hosted in GitHub](https://github.com/bmorrisondev/team-todo-demo/tree/article-2).

Everything is built with self-service in mind, so users will be able to do the following without assistance from you:

- Create organizations and invite users
- Add and manage licenses using Stripe
- Assign and remove licenses from individual users

Using the pre-built Clerk components, users will be able to create organizations and invite users.

Once the organization is created, they will be prompted to purchase licenses via Stripe. Once purchased, administrators can toggle users to gain full use of the application.

Finally, administrators will also be able to easily manage their Stripe subscription with the click of a button.

## Creating organizations

Clerk Organizations allows you to easily add multi-tenancy into an application by letting users create organizations and invite users into them using the `OrganizationSwitcher` component.

When a user creates an organization, they'll immediately be asked if there are any users they want to invite into the organization by simply providing a list of email addresses. Behind the scenes, our system will also check to see if the Clerk application has any endpoints that are configured to receive a webhook when an organization is created.

Webhooks are a way for one service to inform another when an event occurs. The event, in this case, was that an organization was created. Team Task contains a route handler at `/api/clerk-hooks` that is configured to accept the following payload that Clerk will send when an organization is created:

```json
{
  "data": {
    "admin_delete_enabled": true,
    "created_at": 1721316613833,
    "created_by": "user_2iNu3heTeGj0U8G2gGFPWnVLbZm",
    "has_image": false,
    "id": "org_2jQQ2U3ykrhcoElPbh6ZVgUPKlV",
    "image_url": "https://img.clerk.com/eyJ0eXBlIjoiZGVmYXVsdCIsImlpZCI6Imluc18yaU50WjRDSGh2V1UwUW14bzYzZE81S3NNRjIiLCJyaWQiOiJvcmdfMmpRUTJVM3lrcmhjb0VsUGJoNlpWZ1VQS2xWIiwiaW5pdGlhbHMiOiJEIn0",
    "logo_url": null,
    "max_allowed_memberships": 5,
    "name": "Dev Ed",
    "object": "organization",
    "private_metadata": {},
    "public_metadata": {},
    "slug": "dev-ed",
    "updated_at": 1721316613833
  },
  "event_attributes": {
    "http_request": {
      "client_ip": "73.36.196.123",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
    }
  },
  "object": "event",
  "type": "organization.created"
}
```

To automate the process of creating Stripe customer records based on organizations in the application, we can accept the webhook message, create a Stripe customer, and create a record in a Neon table to associate the Clerk `org_id` to the Stripe `customer_id`.

```tsx {{ filename: 'src/app/api/clerk-hooks/route.ts' }}
const stripe = new Stripe(process.env.STRIPE_KEY as string)
const sql = neon(process.env.DATABASE_URL as string)

const handler = createWebhooksHandler({
  secret: process.env.CLERK_WEBHOOK_SECRET as string,
  onOrganizationCreated: async (org) => {
    // Create customer in Stripe
    const customer = await stripe.customers.create({
      name: org.name,
    })

    // Create record in neon
    await sql`insert into orgs (org_id, stripe_customer_id) values (${org.id}, ${customer.id})`
  },
})
```

This table will also track the number of licenses an organization has purchased, using a default value of `0` when the record is created.

![A sample of data from the orgs table in Neon](./neon-table.png)

### Process overview

1. A user starts the process by creating an organization in Clerk.
2. Clerk's backend will asynchronously send a message to a route handler informing the application that a new organization was created.
3. The application will create a customer in Stripe.
4. Finally, the application will insert a new row into a Neon database to associate the Clerk Organization with the Stripe Customer, along with a default license count of 0.

![The process diagram walking through the following steps: creating an organization in Clerk, a webhook being sent from Clerk to the application, the application creating a customer record in Stripe, the application storing the values in a Neon database.](./create-org-flow.png)

## Initial license purchase

Once the organization is created and users are invited, we'll redirect the current user to a page that lets them purchase licenses for the organization.

The `OrganizationSwitcher` uses the `afterCreateOrganizationUrl` prop to automatically forward the user to the `/licensing` page.

```tsx {{ filename: 'src/components/navbar.tsx' }}
<OrganizationSwitcher afterCreateOrganizationUrl={'/licensing'} />
```

The licensing page is used for both the initial license purchase as well as managing and allocating licesnes over time. When the page is loading, it queries the `license_count` value in the `orgs` table for that organization to determine how to render the page.

```tsx {{ filename: 'src/app/licensing/page.tsx' }}
<div className="mb-4 flex justify-center">
  {currentLicenseCount === 0 ? (
    <PurchaseLicensesCard />
  ) : (
    <ManageLicensesCard
      licensedUsersCount={currentlyLicensedUsers}
      purchasedLicensesCount={currentLicenseCount}
    />
  )}
</div>
```

The `PurchaseLicensesCard` component displays an input for the user to select how many licenses are required. Selecting the **"Purchase via Stripe"** button will use that value to create a Stripe Checkout Session using a server action.

Creating a Checkout Session requires the customer ID, a product ID that represents the per-user rate, purchase quantity, and redirect URLs. The session object returned from Stripe will contain the `url` that the user should be sent to for completing the transaction.

```tsx {{ filename: 'src/app/licensing/actions.ts' }}
export async function getCheckoutUrl(clerkOrgId: string, quantity: number) {
  const [row] = await sql`select stripe_customer_id from orgs where org_id=${clerkOrgId}`
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    customer: row.stripe_customer_id,
    line_items: [
      {
        price: 'price_1PajlBGVJ29rMAV1JmqqgEwa',
        quantity: quantity,
        adjustable_quantity: {
          enabled: true,
          minimum: 1,
        },
      },
    ],
    success_url: 'http://localhost:3005/licensing',
    cancel_url: 'http://localhost:3005/licensing',
  })
  return session.url
}
```

Since the `PurchaseLicensesCard` is a client component, we can redirect them using `window.location.href`:

```tsx {{ filename: 'src/app/licensing/PurchaseLicensesCard.tsx' }}
async function onPurchaseClicked() {
  setIsLoading(true)
  const url = await getCheckoutUrl(organization?.id as string, count)
  window.location.href = url as string
}
```

The user will then be prompted for payment info to complete the transaction.

![The Stripe checkout page.](./stripe-checkout.png)

After payment, they'll be redirected back to `/licensing`, where a list of users will be displayed to allocate licenses to.

Stripe offers webhooks for a wide array of events that occur on their end as well. The `customer.subscription.created` webhooks can be used to update the `license_count` value of an organization in the Neon database to match the value that was purchased:

```tsx {{ filename: 'src/app/api/stripe-hooks/route.ts' }}
export async function updateLicenseCount(stripeCustomerId: string, quantity: number) {
  const sql = neon(process.env.DATABASE_URL as string)
  await sql`update orgs set license_count=${quantity} where stripe_customer_id=${stripeCustomerId}`
}

export async function POST(request: NextRequest) {
  const sig = request.headers.get('stripe-signature')
  const body = await request.text()
  const event = stripe.webhooks.constructEvent(body, sig, endpointSecret)
  // Handle the event
  switch (event.type) {
    case 'customer.subscription.created':
      await updateLicenseCount(
        event.data.object.customer as string,
        // @ts-ignore
        event.data.object.quantity,
      )
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }
  return new NextResponse(null, { status: 200 })
}
```

### Process overview

1. The user provides the number of licenses to purchase, and the application requests a Checkout Session URL from Stripe.
2. The user is sent to Stripe to complete the transaction.
3. Stripe will redirect the user back to the application URL while simultaneously sending an asynchronous webhook message to a route handler of the application.
4. The application will update the `license_count` value for that organization in the Neon database.

![A process diagram showing the following steps: the application requesting creating a checkout session from Stripe, the user being redirected for checkout, stripe redirecting back to the application on purchase as well as sending a webhook message to the application informing it of the number of licenses purchased, finally the application setting the value in the Neon database.](./purchase-licenses-flow.png)

## Licensing users

Now that licenses have been purchased, they need to be assigned to users.

As mentioned in the previous section, the `/licensing` page will automatically be updated to render a list of users who are members of an organization by querying the `license_count` value on load.

Since individual users can have access to multiple organizations within a single Clerk application, we use the concept of a “membership” to associate a user to an organization, modeling what is effectively a “many to many” relationship:

![A crowfoot diagram showing the many to many relationship between Clerk users and Clerk organizations by using Memberships as the joining entity.](./memberships-model.png)

Clerk offers various types of metadata to add arbitrary data to entities in Clerk, and memberships can also contain metadata independent of the organization or user. Toggling on the last column will flag the user as “licensed” in their membership metadata using the following server action:

```tsx {{ filename: 'src/app/licensing/actions.ts' }}
export async function toggleUserLicense(orgId: string, userId: string, status: boolean) {
  await clerkClient.organizations.updateOrganizationMembershipMetadata({
    organizationId: orgId,
    userId: userId,
    publicMetadata: {
      isLicensed: status,
    },
  })
}
```

When the licensing page loads, the Clerk Backend API is used to query the memberships of the organization, which includes the metadata used to flag a specific user as licensed in that organization. This both sets the toggle for the user row as well as to aggregate the total number of currently licensed users, which will also be used to determine how many licenses are available.

```tsx {{ filename: 'src/app/licensing/page.tsx' }}
const [row] =
  await sql`select license_count from orgs where org_id=${sessionClaims?.org_id as string}`
const currentLicenseCount = row.license_count
let currentlyLicensedUsers = 0

// Load users
let res = await clerkClient.organizations.getOrganizationMembershipList({
  organizationId: sessionClaims?.org_id as string,
})
const users: UserRowViewModel[] = []
res.data.forEach((el) => {
  let name = el.publicUserData?.firstName
    ? `${el.publicUserData?.firstName} ${el.publicUserData?.lastName}`
    : ''
  const isLicensed = (el.publicMetadata?.isLicensed as boolean) || false
  if (isLicensed) {
    currentlyLicensedUsers++
  }
  users.push({
    id: el.publicUserData?.userId as string,
    orgId: sessionClaims?.org_id as string,
    email: el.publicUserData?.identifier as string,
    name: name,
    isLicensed,
  })
})
```

If the `currentlyLicensedUsers` value is equal or greater than `currentLicenseCount` and the user  is not already licensed, the ability to enable licenses for a user can be disabled:

```tsx {{ filename: 'src/app/licensing/page.tsx' }}
<TableBody>
  {users?.map((u) => (
    <UserRow
      key={u.id}
      id={u.id}
      orgId={u.orgId}
      name={u.name ? u.name : u.email}
      isLicensed={u.isLicensed}
      emailAddress={u.email}
      disabled={!u.isLicensed && currentlyLicensedUsers >= currentLicenseCount}
    />
  ))}
</TableBody>
```

## Managing the subscription

Besides rendering a list of users, the `/licensing` page now also renders the `ManageLicensesCard` component to display the available of licenses along with a button to manage the current subscription using the Stripe Customer Portal.

![The application licensing page showing a "Manage Subscription" card with the number of available seats, number purchased, and a button to manage the subscription. A list of users with toggles to indicate license status is also shown.](./licensing-page.png)

The [Customer Portal](https://docs.stripe.com/customer-management) is a hosted solution from Stripe that allows users to manage their own subscriptions without developers having to build a custom user interface.

This feature is off by default, but can easily be enabled in the [Stripe Dashboard](https://dashboard.stripe.com/test/settings/billing/portal). Since licenses are managed via Stripe subscriptions, we only need to allow subscription management for our customers for this specific SKU.

![The settings page for the Stripe Customer Portal.](./stripe-customer-portal-settings.png)

As with the Checkout Session for the initial license purchase, a custom URL will be generated based on the Stripe customer ID:

```tsx {{ filename: 'src/app/licensing/actions.ts' }}
export async function getPortalUrl(clerkOrgId: string) {
  const stripeId = await getStripeCustomerIdFromOrgId(clerkOrgId)
  const session = await stripe.billingPortal.sessions.create({
    customer: stripeId,
    return_url: 'http://localhost:3005/licensing',
  })
  return session.url
}
```

After the URL is returned to the front end, the user will be redirected to the Customer Portal where they can adjust their licenses as needed:

![The Stripe Customer Portal.](./stripe-customer-portal.png)

The `customer.subscription.updated` event from Stripe will also be handled in the route handler to update the license count for a specific organization:

```tsx {{ filename: 'src/app/api/stripe-hooks/route.ts', prettier: false }}
case 'customer.subscription.updated':
  await updateLicenseCount(
    event.data.object.customer as string,
    // @ts-ignore
    event.data.object.quantity,
  )
  break
```

### Process overview

1. The application generates a Customer Portal URL from Stripe for the active customer.
2. The user can manage the number of licenses active in their subscription.
3. Upon updating the subscription, Stripe sends a webhook to the application informing it of the change.
4. The application updates the `license_count` in the database.

![A process diagram showing the following steps: the application requesting a Customer Portal URL from Stripe, redirecting the user to Stripe to update the licenses, Stripe sending a webhook to the application informing it of the updated licensing count, the application updating the license count in the Neon table.](./manage-licenses-flow.png)

## Accessing license status

Clerk makes it easy to access information about the currently logged-in user, and accessing membership metadata is no exception.

Recall that we stored the `isLicensed` flag within the membership metadata. To access these values, the `sessionClaims` object of the `auth()` function can be used:

```tsx {{ filename: 'src/app/page.tsx' }}
const { sessionClaims } = auth()

let isLicensed = false
if (sessionClaims?.org_metadata && sessionClaims?.org_metadata.isLicensed) {
  isLicensed = true
}
```

Then you can decide what functionality of the application can be restricted based on that flag, for example:

```tsx {{ filename: 'src/app/page.tsx', prettier: false }}
<AddTaskForm disabled={!isLicensed} />
<div className="flex flex-col gap-2 p-2">
  {tasks.map((task) => <TaskRow key={task.id} task={task} disabled={!isLicensed} />)}
</div>
```

## Summary

Per-user licensing is a great way to monetize an application. Stripe offers a great suite of tools that allows developers to process transactions and generate revenue from their work. By combining with power of Clerk Organizations with Stripe, you can build a seamless workflow for your users to independently create their own tenants, purchase and assign licenses for those tenants, and change the subscription at any time.

---

# Build a team-based task manager with Next.js, Neon, and Clerk
URL: https://clerk.com/blog/build-a-team-based-task-manager-with-organizations.md
Date: 2024-07-09
Category: Guides
Description: Use Clerk Organizations to build a task management app that isolates tasks to specific teams.

Building a multi-tenant application with robust permissions can be tricky.

Clerk Organizations were designed to simplify adding multi-tenant functionality to your application. By implementing Organizations, your users will be empowered to create isolated areas of your application, while also allowing users to have granular permissions based on their assigned roles, restricting or permitting functionality as needed.

> Organizations are a core feature of B2B applications. [Learn more about our B2B SaaS solution](/b2b-saas) for comprehensive multi-tenant architecture.

In this article, we'll take leverage the common example of a to-do app and implement Clerk Organizations to enable users to access task lists that are shared across teams.

## Project overview and setup

This guide uses an open-source starter project that you can clone to your computer to build on.

The project is a multi-tenant take on a simple concept; a task management app. Upon setting up the project, you'll have a functional to-do app where you can add, complete, and update tasks. Throughout this guide, you'll add the ability to create and switch between organizations, where each organization has an isolated list of tasks where multiple users can be invited into for collaboration.

To follow along, this guide assumes you have a general understanding of Next.js as well as [Node and NPM installed locally](https://nodejs.org/en/download/package-manager) on your workstation.

### Clone the project locally

To follow this guide, open your terminal and clone the starter repository using the following script. This will also switch you to the `article-1` branch which contains the proper starting point for this article:

```bash
git clone https://github.com/bmorrisondev/team-todo-demo.git
git checkout article-1
```

Now run the following script in your terminal to install the necessary dependencies.

```bash
npm install
```

### Set up a Clerk project

If you do not have a Clerk account, create one before proceeding, which will walk you through creating a project. If you already have an account, create a new project for this guide. Give the project a name and accept the default login providers: Email and Google.

You'll be presented with a Next.js quick start guide. Follow only step 2, which instructs you to create the `.env.local` file and populate it with the necessary environment variables. The remainder of the steps are already completed as part of the starter repo.

### Set up a Neon database

While any Postgres database should be usable, this guide leverages Neon. If you do not have an account, create one at neon.tech. If you do, create an empty database, copy the connection string from the “**Connection Details**” block, and add it to your `.env.local` file as `DATABASE_URL` like so:

```bash
DATABASE_URL=postgresql://teamtodo_owner:*********@ep-frosty-tree-a54nb30r.us-east-2.aws.neon.tech/teamtodo?sslmode=require
```

Next, access the SQL Editor from the left navigation and paste in the following database script to set up the schema:

```sql
create table tasks (
  id serial primary key,
  name text not null,
  description text,
  is_done boolean not null default false,
  owner_id text not null,
  created_on timestamp not null default now(),
  created_by_id text not null
);
```

### Access the project

Back on your computer, start the project with the following command:

```bash
npm run dev
```

By default, the application will be accessible at [`http://localhost:3000`](http://localhost:3000) however the port might be different if another process is using port 3000, so use what's shown in the terminal. Accessing the URL from your browser should prompt you to create an account using Clerk before rendering this:

![A to-do app logged in as a user with an input box, add button, and no tasks.](./team-task-start.png)

Feel free to test it out by adding a few tasks.

## Enable Clerk Organizations

Now that you understand the project and the current state it's in, let's start by setting up the Organizations feature in Clerk.

Log into the Clerk Dashboard and select **"Organization Settings"** from the left navigation. In the settings tab, click the toggle next to **"Enable organizations"** if it's not on already.

![The Clerk Dashboard with one arrow pointing towards "Organization Settings" in the left nav, and another pointing towards the toggle next to "Enable organizations".](./enable-orgs.png)

From now on, users within this Clerk application will be able to create organizations and invite other users to them. This setting is configurable on this same page. Make sure to leave the default checked for the remainder of this guide.

## Update the code

Now that Clerk Organizations are set up, we need to update the project to enable users to create organizations, switch between them, and create tasks specifically for that organization.

Start by adding the `OrganizationSwitcher` to the `Navbar` component:

```ts {{ filename: 'src/app/layout/Navbar.tsx', del: [3], ins: [4, 15] }}
import * as React from 'react'
import Link from 'next/link'
import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import { OrganizationSwitcher, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import { metadata } from '@/app/layout'

function Navbar() {
  return (
    <nav className="flex items-center justify-between border-b border-slate-200 bg-slate-100 p-2">
      <div className="flex items-center gap-2">
        <div>{metadata.title as string}</div>
      </div>
      <SignedIn>
        <div className="flex items-center gap-2">
          <OrganizationSwitcher />
          <UserButton />
        </div>
      </SignedIn>
      <SignedOut>
        <Link href="/sign-in">Sign in</Link>
      </SignedOut>
    </nav>
  )
}

export default Navbar
```

The application should update to show “**Personal account**” next to your avatar in the upper right. This essentially indicates that the user does not have an organization selected that they are working in.

![The app with an arrow pointing towards the newly-added OrganizationSwitcher](./the-org-switcher.png)

This same menu gives you the ability to create an organization, however, the database queries will need to be modified to recognize that the user is in an organization, so let's get those updated now.

The `auth()` function, part of Clerk's Next.js SDK, returns token claims for the current user stored in `sessionClaims`. These claims can be used to determine if an organization is selected along with the permissions the user has set for that specific organization. By default the `org_id` value of the claims is not set unless the user has an organization selected, indicating they are in the “Personal account”.

The following is what `sessionClaims` looks like with an organization selected:

```tsx {{ prettier: false }}
{
  exp: 1719602340,
  iat: 1719602280,
  iss: 'https://assuring-cod-50.clerk.accounts.dev',
  jti: '3194d5c953057b24c256',
  nbf: 1719602270,
  org_id: 'org_2iR0dJJzzY3q9kLK0gsDVT08IP4',
  org_role: 'org:admin',
  org_slug: 'd2-gamers',
  sid: 'sess_2iWNGtu9GSFzttofPmhfB23FL9q',
  sub: 'user_2iNu3heTeGj0U8G2gGFPWnVLbZm',
}
```

Currently, the `getUserInfo` function in `src/app/actions.ts` uses the `auth()` function to return the user's ID from the claims, which is used as the `owner_id` in the database for a given task. The following snippet shows `getUserInfo` updated to conditionally return `org_id` if it is populated and how it is used in the database queries.

Note however that `userId` is still being used to maintain a record of who creates specific tasks, regardless if an organization is set or not.

```tsx {{ filename: 'src/app/actions.ts', ins: [18, [21, 23], 30, 34, 41, 45, 51, 55, 61, 65], del: [29, 33, 40, 44, 50, 54, 60, 64] }}
'use server'
import { auth } from '@clerk/nextjs/server'
import { neon } from '@neondatabase/serverless'

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL is missing')
}
const sql = neon(process.env.DATABASE_URL)

function getUserInfo() {
  const { sessionClaims } = auth()
  if (!sessionClaims) {
    throw new Error('No session claims')
  }

  let userInfo = {
    userId: sessionClaims.sub,
    ownerId: sessionClaims.sub,
  }

  if (sessionClaims.org_id) {
    userInfo.ownerId = sessionClaims.org_id
  }

  return userInfo
}

export async function getTasks() {
  const { userId } = getUserInfo()
  const { ownerId } = getUserInfo()
  let res = await sql`
    select * from tasks
      where owner_id = ${userId};
      where owner_id = ${ownerId};
  `
  return res
}

export async function createTask(name: string) {
  const { userId } = getUserInfo()
  const { userId, ownerId } = getUserInfo()
  await sql`
    insert into tasks (name, owner_id, created_by_id)
      values (${name}, ${userId}, ${userId});
      values (${name}, ${ownerId}, ${userId});
  `
}

export async function setTaskState(taskId: number, isDone: boolean) {
  const { userId } = getUserInfo()
  const { ownerId } = getUserInfo()
  await sql`
    update tasks set is_done = ${isDone}
      where id = ${taskId} and owner_id = ${userId};
      where id = ${taskId} and owner_id = ${ownerId};
  `
}

export async function updateTask(taskId: number, name: string, description: string) {
  const { userId } = getUserInfo()
  const { ownerId } = getUserInfo()
  await sql`
    update tasks set name = ${name}, description = ${description}
      where id = ${taskId} and owner_id = ${userId};
      where id = ${taskId} and owner_id = ${ownerId};
  `
}
```

## Test Organization lists

To test the changes, use the Organization Switcher from the navigation bar and create a new organization.

The task list should automatically refresh into a blank list. Create a task to make sure it gets added to the list. Now switch back to the “Personal account” organization and notice how your previous list of tasks is rendered without the task you created while the organization was selected.

Now let's invite another user into the organization to demonstrate how the tasks in the organization are shared, but the “Personal account” tasks are isolated between users.

If you are in the “Personal account” still, use the switcher to select the organization you created. Once active, use the switcher again and click the gear icon next to the organization name. Then select Members from the left navigation in the modal, and finally click the “Invite” button. Invite another user via their email address.

Many email providers support [plus notation](https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html) where you can add a + to the end of your username along with an identifier to create a separate email address.

This can be used if you do not have multiple email addresses.

The user should receive an email with an invitation to the organization. Accept the invitation and create an account with the application. Upon completing the sign-up process, you should be dropped into the “Personal account” of the new user, which will have a blank list of tasks.

![A blank to do app logged in as a different account](./new-user-todo.png)

Now use the Organization Switcher to select your organization to see the tasks created by the previous user.

![The organization todo list as seen by the different user](./org-todo-list-as-new-user.png)

Adding additional tasks will show them for all users that have access to this organization.

## Conclusion

Clerk Organizations feature provides a way to easily add multi-tenancy into your application.

In this article, you learned how Organizations can be used to allow users to add and modify data across available organizations. By enabling Organizations and slightly tweaking the code based on the token claims, your applications can take advantage of isolated, collaborative environments just like the demo app that was built onto in this guide.

If you enjoyed this article, share it on X and let us know what you liked about it by tagging [@clerk](https://x.com/clerk)!

---

# Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements
URL: https://clerk.com/blog/building-a-hybrid-sign-up-and-subscribe-form-with-stripe.md
Date: 2024-06-18
Category: Guides
Description: Using custom flows, webhooks, and user metadata, learn how to build a single form that automatically subscribes new users.

I had a user reach out to me on X asking if there was any way to integrate a Stripe credit card entry field with Clerk's sign-up forms.

Can you actually create a sign up + card details input in a single page when using @clerk?

Using the OTP method.

Kostas is building a Chrome extension that uses AI to let users write responses to LinkedIn posts directly from their browser. To reduce the friction of users who want to sign up for the trial, he presented the following requirements:

1. It should be a single form that accepts an email address, tier selection, and credit card details.
2. The user should complete the sign-up using a one-time passcode sent to their email account.
3. Upon verifying their email, the user should automatically be signed up for a trial of the selected tier with no further interaction.

In this article, I'll walk through the process of building a completely custom sign-up form that matches the above requirements, starting with the end result.

## The final product

Before walking through how this solution was built, it's worth seeing it in action. The first phase of the signup process has the user entering the details outlined above.

![The hybrid sign-up and subscribe form.](./sign-up-form.png)

Upon completing this form, the user receives an email from Clerk with their sign-up code. The form in the previous screenshot will automatically update to accept a verification code.

![Email OTP verification form](./verification-form.png)

After the code is entered, the user is presented with a loading view, indicating that their account is being created in Clerk and the subscription is being registered in Stripe. Although the user experience appears seamless, the process happening behind the scenes is rather complex with a number of moving parts. Let's explore how this solution was built, starting with the front-end part.

![The user that has been registered and subscribed in Stripe](./stripe-subscription.png)

## Constructing the form

We'll start by exploring the components of both Clerk and Stripe that are used to build the user-facing part of this flow.

### Custom flows

Clerk has a great set of predesigned components that developers can drop directly into their application to provide a great sign-up and sign-in experience for their users.

In this scenario, however, the default components are not flexible enough to embed a product selection and credit card form, so we'll need to use [Custom flows](/docs/custom-flows/overview). Custom flows in Clerk allow you to build custom forms with your own logic to both register and sign in users, as well as customize the logic behind these actions to do whatever you need to for your application. Instead of using any of the components, we can instead build an HTML `<form>` with an `onSubmit` function to handle the submit process.

### Stripe Elements

[Stripe Elements](https://stripe.com/payments/elements) is a set of prebuilt components that can be used during the payment processing flow of your application.

One of these components is a credit card entry form that can generate a token for a given set of credit card details, allowing us to securely store a reference to the card and not the card details themselves. This token can be used later in the process to tie the card as a form of payment to the customer in Stripe. In order to use Elements in a Next.js application, the component that renders the form must be wrapped in the `<Elements>` component.

Because we're using a Custom flow, we can create a separate component that renders and handles the form logic and wrap it in `<Elements>` on the page, allowing us to combine the credit card entry form with our sign-in form.

### Unsafe metadata

Users in Clerk have a number of different [metadata](/docs/users/metadata#user-metadata) categories that are used for different purposes:

- public metadata - readable on the frontend, but writeable from the backend
- private metadata - accessible only from the backend
- unsafe metadata - readable and writeable from the front end, can also store pre-signup info about the user

Since unsafe metadata can be used to store information before the signup process is complete, we can take advantage of this to store information about the selected tier (a “product” in Stripe) and payment details provided in the custom form. When the user completes signup, the data stored locally in unsafe metadata will also be saved with the user on Clerks systems.

### Exploring the form code

After walking through all the moving parts required to solve this on the front end, let's take a look at the code.

To start, we have the `page.tsx` file which renders one of two forms based on if the signup attempt is verifying or not. If `verifying` is true, it means that the user has submitted the required details and the application is just waiting for them to add the OTP code they received via email. Take note that `SignUpForm` is wrapped in the Stripe `<Elements>` node, which is required to use Elements.

```tsx {{ filename: 'src/app/sign-up/[[...sign-up]]/page.tsx' }}
'use client'

import * as React from 'react'
import { useState } from 'react'
import SignUpForm from './SignUpForm'
import { loadStripe } from '@stripe/stripe-js'
import { Elements } from '@stripe/react-stripe-js'
import VerificationForm from './VerificationForm'

export default function Page() {
  const [verifying, setVerifying] = useState(false)
  const options = {
    appearance: {
      theme: 'stripe',
    },
  }
  const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string)

  // 👉 Render the verification form, meaning OTP email has been set
  if (verifying) {
    return <VerificationForm />
  }

  // 👉 Render the signup form by default
  return (
    <div className="mt-20 flex items-center justify-center">
      {/* @ts-ignore */}
      <Elements options={options} stripe={stripePromise}>
        <SignUpForm setVerifying={setVerifying} />
      </Elements>
    </div>
  )
}
```

Next let's explore the `SignUpForm.tsx` which is the form that accepts an email address, product selection, and credit card information. This component accepts a single prop of `setVerifying` which is only to signal to the page that the form has been submitted and the `VerificationForm` component can be shown instead.

When the form is submitted, three main things happen:

1. The card info is tokenized.
2. Clerk is notified that a signup is being attempted using the provided email address. This is where unsafe metadata is set as well.
3. The `setVerifying` prop is set to true, indicating to the parent that the `VerificationForm` component can now be rendered.

```tsx {{ filename: 'src/app/sign-up/[[...sign-up]]/SignUpForm.tsx' }}
'use client'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js'
import { useSignUp } from '@clerk/nextjs'
import { useState } from 'react'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'

type Props = {
  setVerifying: (val: boolean) => void
}

function SignUpForm({ setVerifying }: Props) {
  const { isLoaded, signUp } = useSignUp()
  const stripe = useStripe()
  const elements = useElements()
  const [priceId, setPriceId] = useState('')
  const [email, setEmail] = useState('')

  // 👉 Handles the sign-up process, including storing the card token and price id into the users metadata
  async function onSubmit() {
    if (!isLoaded && !signUp) return null

    try {
      if (!elements || !stripe) {
        return
      }

      let cardToken = ''
      const cardEl = elements?.getElement('card')
      if (cardEl) {
        const res = await stripe?.createToken(cardEl)
        cardToken = res?.token?.id || ''
      }

      await signUp.create({
        emailAddress: email,
        unsafeMetadata: {
          cardToken,
          priceId,
        },
      })

      // 👉 Start the verification - an email will be sent with an OTP code
      await signUp.prepareEmailAddressVerification()

      // 👉 Set verifying to true to display second form and capture the OTP code
      setVerifying(true)
    } catch (err) {
      // 👉 Something went wrong...
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <Card className="w-full sm:w-96">
        <CardHeader>
          <CardTitle>Create your account</CardTitle>
          <CardDescription>Welcome! Please fill in the details to get started.</CardDescription>
        </CardHeader>
        <CardContent className="grid gap-y-4">
          {/* // 👉  Email input */}
          <div>
            <Label htmlFor="emailAddress">Email address</Label>
            <Input
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              type="email"
              id="emailAddress"
              name="emailAddress"
              required
            />
          </div>

          {/* // 👉 Product selection radio group */}
          <div>
            <Label>Select tier</Label>
            <RadioGroup
              defaultValue="option-one"
              className="mt-2"
              value={priceId}
              onValueChange={(e) => setPriceId(e)}
            >
              <div className="flex items-center space-x-2">
                <RadioGroupItem value="price_1PG1OcF35z7flJq7p803vcEP" id="option-one" />
                <Label htmlFor="option-one">Pro</Label>
              </div>
              <div className="flex items-center space-x-2">
                <RadioGroupItem value="price_1PG1UwF35z7flJq7vRUrnOiv" id="option-two" />
                <Label htmlFor="option-two">Enterprise</Label>
              </div>
            </RadioGroup>
          </div>

          {/* // 👉 Use Stripe Elements to render the card capture form */}
          <Label>Payment details</Label>
          <div className="rounded border p-2">
            <CardElement />
          </div>
        </CardContent>

        <CardFooter>
          <div className="grid w-full gap-y-4">
            <Button type="submit" disabled={!isLoaded}>
              Sign up for trial
            </Button>
            <Button variant="link" size="sm" asChild>
              <Link href="/sign-in">Already have an account? Sign in</Link>
            </Button>
          </div>
        </CardFooter>
      </Card>
    </form>
  )
}

export default SignUpForm
```

Finally, we have the `VerificationForm.tsx` component, which simply accepts the code that was sent to the user's email address. The submit handler for this form sends the code to Clerk where it is checked to be valid. If valid, the user account will be created and the user will be redirected to `/after-sign-up` .

```tsx {{ filename: 'src/app/sign-up/[[...sign-up]]/VerificationForm.tsx' }}
import * as React from 'react'
import { useSignUp } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useState } from 'react'

function VerificationForm() {
  const { isLoaded, signUp, setActive } = useSignUp()
  const [code, setCode] = useState('')
  const router = useRouter()

  // 👉 Handles the verification process once the user has entered the validation code from email
  async function handleVerification(e: React.FormEvent) {
    e.preventDefault()
    if (!isLoaded && !signUp) return null

    try {
      // 👉 Use the code provided by the user and attempt verification
      const signInAttempt = await signUp.attemptEmailAddressVerification({
        code,
      })

      // 👉 If verification was completed, set the session to active
      // and redirect the user
      if (signInAttempt.status === 'complete') {
        await setActive({ session: signInAttempt.createdSessionId })
        router.push('/after-sign-up')
      } else {
        // 👉 If the status is not complete. User may need to complete further steps.
      }
    } catch (err) {
      // 👉 Something went wrong...
    }
  }

  return (
    <div className="mt-20 flex items-center justify-center">
      <form onSubmit={handleVerification}>
        <Card className="w-full sm:w-96">
          <CardHeader>
            <CardTitle>Create your account</CardTitle>
            <CardDescription>Welcome! Please fill in the details to get started.</CardDescription>
          </CardHeader>
          <CardContent className="grid gap-y-4">
            <div>
              <Label htmlFor="code">Enter your verification code</Label>
              <Input
                value={code}
                onChange={(e) => setCode(e.target.value)}
                id="code"
                name="code"
                required
              />
            </div>
          </CardContent>
          <CardFooter>
            <div className="grid w-full gap-y-4">
              <Button type="submit" disabled={!isLoaded}>
                Verify
              </Button>
            </div>
          </CardFooter>
        </Card>
      </form>
    </div>
  )
}

export default VerificationForm
```

## Registering the subscription in Stripe

Now that we've covered everything the user sees, let's break down what happens behind the scenes to make sure the user is successfully registered for the trial of their chosen tier.

### Clerk webhooks

We'll need a reliable way to signal that a user has been created and something needs to be done about it, and that's where webhooks come in.

Webhooks are HTTP requests that are automatically dispatched to an API endpoint of your choosing when an event happens in Clerk. One of these can be triggered when a user is created, using the `user.created` event. The dispatched request also contains various details about the user that was created, including the unsafe metadata. By configuring a webhook handler in our application to accept the request, we can read in the selected product and payment info, and create the subscription using the Stripe SDK.

![The Webhooks section of the Clerk dashboard](./dashboard-webhooks.png)

Using the `@brianmmdev/clerk-webhooks-handler` utility library, we can define functions that automatically validate the webhook signature and allow you to easily handle the payload, including pulling out the unsafe metadata that was set during the signup process.

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
import { createWebhooksHandler } from '@brianmmdev/clerk-webhooks-handler'
import { Stripe } from 'stripe'
import { clerkClient } from '@clerk/nextjs/server'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string)

const handler = createWebhooksHandler({
  onUserCreated: async (user) => {
    // 👉 Parse the unsafe_metadata from the user payload
    const { cardToken, priceId } = user.unsafe_metadata
    if (!cardToken || !priceId) {
      return
    }

    // 👉 Stripe operations will go here...
  },
})

export const POST = handler.POST
```

### Creating the Stripe entities

Creating a subscription in Stripe requires three separate entities:

- Customer
- Payment method
- Subscription

Since the card info was tokenized and stored in the user's unsafe metadata along with the selected product, we can take advantage of the info sent to our application to create these three entities and tie them to each other. The first thing is to create a payment method based on the tokenized card info:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
const pm = await stripe.paymentMethods.create({
  type: 'card',
  card: {
    token: cardToken as string,
  },
})
```

Next, we can use the captured email address to create a customer in Stripe and tie the payment method to them:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
const customer = await stripe.customers.create({
  email: user?.email_addresses[0].email_address,
  payment_method: pm.id,
})
```

Finally, we can create the subscription entity, attach it to the customer, set the payment method, AND set a trial period:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
const subscription = await stripe.subscriptions.create({
  customer: customer.id,
  default_payment_method: pm.id,
  trial_period_days: 14,
  items: [
    {
      price: priceId as string,
    },
  ],
})
```

### Syncing subscription state

Although the frontend and backend flows occur separately, we need a way to signal to the front end that processing has been completed on the backend. To do this, we can use metadata again (public metadata in this case) to set data from the Stripe operations to indicate that the process has been completed:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
await clerkClient.users.updateUser(user.id, {
  publicMetadata: {
    stripeCustomerId: customer.id,
    stripeSubscriptionId: subscription.id,
  },
})
```

On the front end, our redirect page actually just renders a loading indicator but also polls the user's info from Clerk to redirect them once that data is available. The following is the code that makes up the page for `/after-sign-up`, which is where the user was redirected after the OTP code was entered.

```tsx {{ filename: 'src/app/after-sign-up/page.tsx' }}
'use client'

import { Icons } from '@/components/ui/icons'
import { useUser } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import React, { useEffect } from 'react'

async function sleep(ms: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

function AfterSignUp() {
  const router = useRouter()
  const { user } = useUser()

  // 👉 Poll the user data until a stripeSubscriptionId is available
  useEffect(() => {
    async function init() {
      while (!user?.publicMetadata?.stripeSubscriptionId) {
        await sleep(2000)
        await user?.reload()
      }
      // 👉 Once available, redirect to /dashboard
      router.push('/dashboard')
    }
    init()
  }, [])

  return (
    <div className="mt-20 flex items-center justify-center">
      <Icons.spinner className="size-8 animate-spin" />
    </div>
  )
}

export default AfterSignUp
```

## Putting it all together

As you can see there are quite a lot of moving parts that allow for this simple form to do so much. To put everything into context, let's look at the entire flow step by step:

![An actor diagram that explains how the workflow operates](./flow.jpg)

1. When the user submits the form, the card details are sent to Stripe to tokenize the card. That token, and the selected product, are stored as `unsafeMetadata`.
2. The app will signal to Clerk that a user is trying to sign up.
3. Clerk sends the user an OTP to their email.
4. The user enters the code into the application.
5. The app signals to Clerk that the user completed the signup and the account should be created.
6. The `user.created` webhook is triggered and the payload is sent to an API route in the application.
7. The webhook handler uses the Stripe SDK to create a payment method, customer, and subscription.
8. Once done, the user record is updated from Next and the user is allowed to proceed

## Conclusion

Custom flows in Clerk open a world of opportunities, allowing you to create your own forms to handle sign-up and sign-in. By taking advantage of webhooks and using the various types of metadata, you can also build in complex and advanced automation, while creating a seamless experience for your users.

If you enjoyed this, share it on X and let us know by tagging [@clerk](https://x.com/clerk)!

---

# Welcoming Colin from Zod as our inaugural Open Source Fellow
URL: https://clerk.com/blog/zod-fellowship.md
Date: 2024-06-11
Category: Company
Description: Clerk is funding the development of Zod 4 with a new Open Source Fellowship program.

Today, we are delighted to announce the launch of our Open Source Fellowship program, created to foster continued innovation in the open source software community. Our inaugural recipient is [Colin McDonnell](https://x.com/colinhacks), the creator of [Zod](https://zod.dev).

With nearly 10 million weekly npm downloads, Zod is an extremely widely-used TypeScript schema declaration and validation library. It should come as no surprise that we use it to help build Clerk, and so we're thrilled to be playing a minor role in its continued evolution.

Specifically, Clerk is sponsoring the development of Zod 4, providing financial support for Colin to dedicate a few months to this next version. In connection with this funding, Colin has agreed to help increase visibility of Clerk to Zod's many users, by sharing our brand within its documentation, readme, RFCs, and his public profile on X.

For more details from Colin's perspective, including more details about the enhancements coming in Zod 4, we encourage reading [Zod's announcement](https://zod.dev/blog/clerk-fellowship). We are enthusiastically aligned with Colin's vision for new approaches to funding open source, including his transparency and candor about the terms of the fellowship.

---

# Build a modern authenticated chat application with Next.js, Ably, and Clerk
URL: https://clerk.com/blog/authenticated-next-chat-app-with-ably-and-clerk.md
Date: 2024-06-04
Category: Guides
Description: Learn how to build a modern, authenticated chat application using Next.js, Ably, and Clerk. This comprehensive guide covers everything from setting up real-time messaging and user authentication to implementing roles and message history.

This 6000-word mega-post teaches you how to code an authenticated chat application with roles and permissions from scratch!

You will learn how to implement message transmission over WebSockets and a dynamic "who's online?" list that updates in realtime.

We'll also implement a secure moderator role that enables select users to access restricted channels like "#moderators-only" and delete unwanted messages.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./1.png)

As for the technology stack, it's React on the frontend using Tailwind and shadcn/ui for styling. We'll integrate [Ably](https://ably.com) for serverless WebSockets and [Clerk](https://clerk.com) for authentication and user management.

## Just want the code?

I feel that! You can find the complete source code on my [GitHub](https://github.com/bookercodes/authenticated-next-chat-app-with-ably-and-clerk).

If you want to run the project locally, I've included the minimal necessary steps in the repository README for you to reference.

## Before you begin

Before delving in, it would be good if you had an understanding of the following technologies and concepts:

- React
  - Components and how to [think in React](https://react.dev/learn/thinking-in-react)
  - A grasp of `useState` and how to, for example, implement a form component from scratch
  - Familiarity with `useEffect`, `useRef`, and `useReducer`
- Next.js
  - How to define routes with App Router
  - Awareness of client components vs server components
- CSS
  - An awareness of Flexbox and Grid

## Start here

First, let's create a blank Next.js App Router project called `chat-tutorial`.

The easiest way is with `create-next-app`:

```bash {{ filename: 'terminal' }}
npx create-next-app chat-tutorial --javascript --tailwind --eslint --app --src-dir --no-import-alias
```

Run the above command then `cd` into the newly-created directory:

```bash {{ filename: 'terminal' }}
cd chat-tutorial
```

### Create the chat layout

Before we go too far, let's first create the layout for our chat application.

Open the `chat-tutorial` directory in your editor of choice and add a `nav` to `src/app/layout.js`:

```jsx {{ filename: 'src/app/layout.js', ins: [[15, 17]] }}
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <nav className="flex border-b border-gray-200 p-5">
          <h1 className="font-bold">Clover Corp</h1>
        </nav>
        {children}
      </body>
    </html>
  )
}
```

Next, create `src/app/chat/[[...channelName]]/page.js`:

```jsx {{ filename: 'src/app/chat/[[...channelName]]/page.js' }}
'use client'

const Page = ({ params }) => {
  return (
    <div className="grid h-[calc(100vh-72.8px)] grid-cols-4">
      <div className="border-r border-gray-200 p-5"></div>
      <div className="col-span-2"></div>
      <div className="border-l border-gray-200 p-5"></div>
    </div>
  )
}
export default Page
```

Run the development server with `npm run dev`, open your browser, then navigate to `/chat` to observe the three-column skeleton:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./skeleton.png)

In the upcoming steps, we'll populate each column with the channel list, chat, and online list components respectively.

Note that `/chat` is a dynamic route with an optional route segment called `channelName` .

We'll reference this segment by `params.channelName` and use the value do determine what channel the user is currently participating in.

| Route               | `params.channelName` |
| ------------------- | -------------------- |
| /chat/announcements | `"announcements"`    |
| /chat/general       | `"general"`          |
| /chat/random        | `"random"`           |
| /chat/mods-only     | `"mods-only"`        |
| /chat               | `null`               |

### Install shadcn/ui

If you haven't come across [shadcn/ui](https://ui.shadcn.com) before, this tool enables design-impaired JavaScript developers like me to pick and choose from an assortment of beautiful React components that are also accessible and easy to customize. 😅

Still inside the `chat-tutorial` directory, run:

```bash {{ filename: 'terminal' }}
npx shadcn-ui@latest init
```

You'll be presented with 3 prompts. Answer like this:

- **Which style would you like to use?** Default
- **Which color would you like to use as base color?** Slate
- **Would you like to use CSS variables for colors?** Yes

### Build the chat React components

In this section, we'll build the `MessageInput` and `MessageList` components.

To make them look sleek without writing much code, let's lean on the shadcn/ui's ready-made `Input` and `Avatar` components.

To download them, run the following commands:

```bash {{ filename: 'terminal' }}
npx shadcn-ui@latest add input
npx shadcn-ui@latest add avatar
```

Create `src/app/[[...channel]]/chat/message-input.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/message-input.js' }}
import { useState } from 'react'
import { Input } from '@/components/ui/input'

const MessageInput = ({ onSubmit, disabled }) => {
  const [input, setInput] = useState('')

  const handleChange = (e) => {
    setInput(e.target.value)
  }

  const handleSubmit = (e) => {
    e.preventDefault()
    onSubmit(input)
    setInput('')
  }

  return (
    <form onSubmit={handleSubmit}>
      <Input
        type="text"
        value={input}
        onChange={handleChange}
        disabled={disabled}
        placeholder={disabled ? 'This input has been disabled.' : 'Your message here'}
      />
    </form>
  )
}
export default MessageInput
```

Finally, create `src/app/[[...channel]]/chat/message-list.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/message-list.js' }}
import { Avatar, AvatarImage } from '@/components/ui/avatar'

const MessageList = ({ messages }) => {
  const createLi = (message) => (
    <li key={message.id} className="group my-2 flex justify-between bg-slate-50 p-3">
      <div className="flex items-center">
        <Avatar className="mr-2">
          <AvatarImage src={message.data.avatarUrl} />
        </Avatar>
        <p>{message.data.text}</p>
      </div>
    </li>
  )

  return <ul>{messages.map(createLi)}</ul>
}
export default MessageList
```

## Realtime messaging with Ably

To enable realtime messaging and store message history, we'll use a serverless WebSockets platform called Ably.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./ably.png)

Because Ably manages the WebSockets, the connection is highly reliable. Additionally, using a hosted WebSocket platform allows us to benefit from realtime communication, even when deploying to serverless platforms such as Vercel that don't typically support long-lived WebSocket connections.

With Ably, events are published to "topics" (named logical channels). Subscribers receive all messages published to the topics to which they subscribe.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./pub-sub.png)

Conceptually, this maps very tidily in chat application where we utilize an Ably topic per chat channel.

When the user navigates routes, the `channelName` segment is updated. We can reference `channelName` to connect to an Ably topic by the same name prefixed with `chat:`, like so:

| Route               | `params.channelName` | Ably topic             |
| ------------------- | -------------------- | ---------------------- |
| /chat/announcements | `"announcements"`    | `"chat:announcements"` |
| /chat/general       | `"general"`          | `"chat:general"`       |
| /chat/random        | `"random"`           | `"chat:random"`        |
| /chat/mods-only     | `"mods-only"`        | `"chat:mods-only"`     |
| /chat               | `null`               | `null`                 |

The prefix part of the Ably topic is called a [namespace](https://ably.com/docs/channels#namespaces). Namespaces allow us to logically group related channels. This grouping will come in handy later when we apply Ably access rules for all `chat:*` channels.

The prefix `chat:` is one I chose arbitrarily and doesn't mean anything specific in Ably.

### Implement basic Ably chat messaging

If you haven't already, sign-up to Ably then **Create new app** with the following options:

- **App name:** Call your app something meaningful
- **Select your preferred language(s):** JavaScript
- **What type of app are you building?** Live Chat

![Authenticated Next Chat App With Ably And Clerk guide illustration](./create-ably-app.png)

In the next screen, copy or otherwise note your Ably API key - we'll need it momentarily:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./copy-ably-key.png)

Back in the terminal, install the Ably React SDK:

```bash {{ filename: 'terminal' }}
npm install ably
```

Create `src/app/[[...channel]]/chat/chat.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/chat.js' }}
import MessageInput from './message-input'
import MessageList from './message-list'

import { useReducer } from 'react'
import { useChannel } from 'ably/react'

const ADD = 'ADD'

const reducer = (prev, event) => {
  switch (event.name) {
    // 👉 Append the message to messages
    case ADD:
      return [...prev, event]
  }
}

const Chat = ({ channelName }) => {
  // 👉 Placeholder user to be replaced with the authenticated user later
  const user = {
    imageUrl: 'https://ui-avatars.com/api/?name=Alex',
  }
  const [messages, dispatch] = useReducer(reducer, [])
  // 👉 useChannel accepts the channel name and a function to invoke when
  //    new messages are received. We pass dispatch.
  const { channel, publish } = useChannel(channelName, dispatch)

  const publishMessage = (text) => {
    // 👉 Publish event through Ably
    publish({
      name: ADD,
      data: {
        text,
        avatarUrl: user.imageUrl,
      },
    })
  }

  return (
    <>
      <div className="overflow-y-auto p-5">
        <MessageList messages={messages} />
      </div>
      <div className="mt-auto p-5">
        <MessageInput onSubmit={publishMessage} />
      </div>
    </>
  )
}
export default Chat
```

Finally, update `src/app/[[...channel]]/chat/page.js`.

Take care to replace `YOUR_ABLY_API_KEY` with the API key you noted a moment ago:

```jsx {{ filename: 'src/app/[[...channel]]/chat/page.js', ins: [[3, 5], [8, 17], [18, 19], 23, 27, 28] }}
'use client'

import Chat from './chat'
import { Realtime } from 'ably'
import { AblyProvider, ChannelProvider } from 'ably/react'

const Page = ({ params }) => {
  // 👉 Instantiate Ably client
  const client = new Realtime({
    key: 'YOUR_ABLY_API_KEY',
    clientId: 'Alex',
  })
  const channelName = `chat:${params.channel}`

  return (
    // 👉 Wrap chat app in AblyProvider and ChannelProvider necessary to
    // use Ably hooks
    <AblyProvider client={client}>
      <ChannelProvider channelName={channelName}>
        <div className="grid h-[calc(100vh-72.8px)] grid-cols-4">
          <div className="border-r border-gray-200 p-5"></div>
          <div className="col-span-2">
            <Chat channelName={channelName} />
          </div>
          <div className="border-l border-gray-200 p-5"></div>
        </div>
      </ChannelProvider>
    </AblyProvider>
  )
}
export default Page
```

With that, the basic chat is ready to test!

Open /chat/general in a couple of localhost browser windows to send and receive chat messages over WebSockets:

Before we go any further, there is something very important to note about the code above!

In the excerpt, we instantiate an Ably `Realtime` instance with the API key. We also hard-coded the `clientId`.

```jsx {{ filename: 'src/app/[[...channel]]/chat/page.js' }}
const client = new Realtime({
  key: 'SlyWSw.jiu7RA:A64U3y3ty-9QLGB4Tn4NbBtlYYsx60qCI0ooT4WhNq0',
  clientId: 'Alex',
})
const channelName = `chat:${params.channel}`
```

This is problematic:

- An Ably API key is top secret and should never (ever) go in production client-side code as a malicious actor could easily find it and exploit your application. I have done so here *only* for demonstration purposes.
- Every client should have a unique identifier based on the user's name.

In an upcoming section, we will address these concerns by implementing a secure backend endpoint to generate an Ably-compatible JWT token which includes verified information about the user's identity and their capabilities.

### Implement a chat channel-switcher

To help users discover channels and navigate them more easily, let's create a `ChannelList` component and slot it into the left-hand column of our layout.

First, run this command to install `clsx`, a handy module for conditionally applying Tailwind styles:

```bash {{ filename: 'terminal' }}
npm install clsx
```

Next, create `src/app/[[...channel]]/chat/channel-list.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/channel-list.js' }}
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { clsx } from 'clsx'

const ChannelList = ({ channels }) => {
  const currentPath = usePathname()

  const createLi = (channel) => {
    return (
      <li key={channel.path}>
        <Link
          href={channel.path}
          className={clsx('flex items-center', {
            'font-bold': currentPath === channel.path,
          })}
        >
          {channel.label}
        </Link>
      </li>
    )
  }

  return <ul>{channels.map(createLi)}</ul>
}

export default ChannelList
```

Finally, update `src/app/[[...channel]]/chat/page.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/page.js', ins: [3, [8, 13], 26] }}
'use client'
import Chat from './chat'
import ChannelList from './channel-list'
import { Realtime } from 'ably'
import { AblyProvider, ChannelProvider } from 'ably/react'

const Page = ({ params }) => {
  const channels = [
    { path: '/chat/announcements', label: '# Announcements' },
    { path: '/chat/general', label: '# General' },
    { path: '/chat/random', label: '# Random' },
    { path: '/chat/mods-only', label: '# Mods-only', modOnly: true },
  ]

  const client = new Realtime({
    key: 'SlyWSw.jiu7RA:A64U3y3ty-9QLGB4Tn4NbBtlYYsx60qCI0ooT4WhNq0',
    clientId: 'Alex',
  })
  const channelName = `chat:${params.channel}`

  return (
    <AblyProvider client={client}>
      <ChannelProvider channelName={channelName}>
        <div className="grid h-[calc(100vh-72.8px)] grid-cols-4">
          <div className="border-r border-gray-200 p-5">
            <ChannelList channels={channels} />
          </div>
          <div className="col-span-2">
            <Chat channelName={channelName} />
          </div>
          <div className="border-l border-gray-200 p-5"></div>
        </div>
      </ChannelProvider>
    </AblyProvider>
  )
}
export default Page
```

Using `clsx`, we conditionally bolden the current channel to convey the channel they're currently chatting in.

Here, I hardcoded a `channels` list but this same data structure could come from a database should you want to enable users to create channels dynamically.

Return to the browser and check it out - the user can now switch channels without fumbling with the URL in the address bar:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./channel-switcher.png)

## Authentication with Clerk

With basic realtime messaging in place, it's time to authenticate Next.js users with Clerk.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./clerk.png)

[Clerk](/) is an authentication and user management platform with readyade React components and seamless [Next.js authentication middleware](/nextjs-authentication).

With Clerk, you get an assortment of beautifully-designed React components. Some are UI components like `SignInButton` and `UserButton`, while others are "helper components" that conditionally render children based on some authentication or authorization state.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./clerk-feature-grid.png)

It's typical to use both kinds of components, and they make adding fully-featured authentication to Next.js a lot smoother than you might be expecting if you have experience rolling your own auth.

### User management

In addition to authentication, Clerk handles user management. This means when a user signs up to your application, Clerk stores and manages a user record with information such as the their full name, avatar, and metadata.

In the next sections, we'll utilize this information from Clerk to identify the Ably client by the user's unique identifier instead of the hardcoded value of "Alex" we have been relying on until now.

The Clerk dashboard UI provides a convenient user management backoffice to  manage user's information such as their role, without having to implement commands or a custom backend UI.

For the purposes of this tutorial, we'll update the user's public metadata with an `isMod` property that denotes the user's role in the chat application (more on that soon).

![Authenticated Next Chat App With Ably And Clerk guide illustration](./clerk-dashboard-preview.png)

### Configure Clerk

If you haven't already, [create a Clerk account](https://dashboard.clerk.com/sign-up). Clerk doesn't ask for a credit card and you get 10,000 monthly active users for free.

Next, create a Clerk application with **username**, **phone**, and **email** sign-in options:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./create-clerk-app.png)

Still in the Clerk dashboard, under **User & Authentication** and **Email, Phone, Username**, scroll to **Personal information** and enable **Name:**

![Authenticated Next Chat App With Ably And Clerk guide illustration](./enable-name.png)

Under **Customization** and **Avatars**, enable **Initials**:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./enable-initials.png)

If a user doesn't have an avatar, Clerk will generate one based on their initials and the styling options you choose. I set the background to **Solid** and chose the color **#BDBDBD** but you can make this your own!

> \[!NOTE]
> If you enabled social sign-in options like GitHub earlier and sign-in with them, Clerk will pull the avatar from the there by default.
>
> Users can also upload a custom avatar through the `<UserButton />`.

Next, under **API Keys**, take advantage of the handy **Quick Copy** box to copy your Clerk environment variables.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./quick-copy-envs.png)

Create a file called `./.env.local` and paste them in.

It'll look something like this:

```sh {{ filename: '.env.local' }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}}
CLERK_SECRET_KEY={{secret}}
```

### Implement authentication with Clerk

To start authenticating Next.js users with Clerk, we'll first need to install the Clerk Next SDK:

```bash {{ filename: 'terminal' }}
npm install @clerk/nextjs
```

Next, create `src/middleware.js`:

```jsx {{ filename: 'src/middleware.js' }}
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isChatRoute = createRouteMatcher(['/chat(.*)'])

export default clerkMiddleware((auth, req) => {
  if (isChatRoute(req)) auth().protect()
})

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
```

This middleware makes it so that only authenticated users can access routes starting with `/chat`.

If the user isn't authenticated, they'll be redirected to the [Clerk account portal](/docs/account-portal/overview) to sign-up or sign-in.

Next, update `src/app/layout.js`:

```jsx {{ filename: 'src/app/layout.js', ins: [2, 14, [19, 24], 28, 31, 34], del: [27] }}
import { Inter } from 'next/font/google'
import {
  ClerkLoaded,
  ClerkProvider,
  SignInButton,
  SignedIn,
  SignedOut,
  UserButton,
} from '@clerk/nextjs'

import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Comet',
}

export default function RootLayout({ children }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={inter.className}>
          <nav className="flex justify-between border-b border-gray-200 p-5">
            <h1 className="font-bold">Comet</h1>
            <SignedOut>
              <SignInButton mode="modal" />
            </SignedOut>
            <SignedIn>
              <UserButton showName afterSignOutUrl="/" />
            </SignedIn>
          </nav>

          {children}
          <ClerkLoaded>{children}</ClerkLoaded>
        </body>
      </html>
    </ClerkProvider>
  )
}
```

Here, we utilize some of those Clerk React components I mentioned before:

- `<ClerkProvider />` wraps your React app to provide session and user context.
- `<SignInButton />` links to the sign-in page or shows the sign-in modal.
- `<UserButton />` renders the user's avatar with a dropdown for account management.
- `<SignedIn />` renders children only if a user is signed in.
- `<SignedOut />` renders children only if no user is signed in.
- `<ClerkLoaded />` ensures the Clerk object is loaded and accessible.

Let's see it all in action.

Open /chat/general and you'll be redirected to the Clerk account portal to sign-in.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./sign-in.png)

Sign-in successfully and Clerk will redirect the user back to /chat/general where they'll have full access to the chat!

Note the `<UserButton />` in the top-right corner, designed to resemble the user button UI popularized by Google:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./user-btn.png)

## Using Ably and Clerk together

So far, we have a functioning chat application and we have a way to authenticate users but these two parts of the app aren't really connected:

- Even though the user's signed in under their own name, every message is still technically coming from "Alex" with the "AL" avatar we hard-coded earlier. Now that we know the identity of the user, we'll need to pass that on to Ably instead of the hard-coded value.
- Even though the Next.js pages are protected behind a sign-in form, the underlying Ably topic is technically accessible to anyone. That isn't necessarily a problem for public channels like "general", but the "mods-only" channel includes sensitive messages and access should be carefully restricted. Now that we have a means to authenticate users with Clerk, let's authorize their ability to connect to restricted channels.

### Understanding tokens

As a reminder, earlier, we hard-coded the Ably API key in the client-side code:

```jsx {{ filename: 'src/app/[[...channel]]/chat/page.js' }}
const client = new Realtime({
  key: 'SlyWSw.jiu7RA:A64U3y3ty-9QLGB4Tn4NbBtlYYsx60qCI0ooT4WhNq0',
  clientId: 'Alex',
})
```

This made it convenient to connect to Ably for the purposes of this tutorial, but it's neither secure nor flexible.

In the upcoming section, we'll remove the hard-coded values. Instead, we'll point Ably at an authentication backend endpoint poised to return an Ably-compatible JWT token.

Before we write any code on that front, let's take a moment to understand Ably-compatible JWT tokens and the token flow at high level. It will make the code in the next section a lot easier to understand.

An Ably-compatible JWT token contains three crucial pieces of information:

1. The identity (`clientId`) of the authenticated user (according to Clerk and the Clerk Next.js middleware).
2. The capabilities this user has in which Ably channels, dependent on their role.
3. Additional claims, such as `isMod`.

Instead of passing a hard-coded `clientId` and `key` to Ably, we'll point the Ably JavaScript SDK to the backend endpoint we're about to implement.

Equipped with the backend authentication endpoint, the Ably SDK will automatically request the token before using it to connect to the service.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./ably-token-flow.png)

Ably uses this token for two discrete purposes:

- **Capabilities:** A token contains information about the user's Ably capabilities such as their capability to subscribe to messages in a topic named `"chat:mods-only"`. If the user doesn't have the capability to a restricted topic like `"chat:mods-only"`, Ably rejects the connection and the Ably client propagates a connection error.
- **Claims:** A token also includes claims. When Ably learns of a client's claims, the service will automatically add those claims to the metadata of events published by the authenticated client. These claims can be read by the client to authorize actions such as deleting or editing a message.

While capabilities and claims can sound similar, they are two discrete ideas in the Ably world.

We use capabilities to allow or disallow native Ably operations like subscribing or publishing to/on a specific topic or set of topics.

Capabilities, on the other hand, are metadata encoded in the token and associated with the connected client and any events they publish. Capabilities are used to authorize bespoke functionality like message deletions or edits.

### Create an Ably token Next endpoint

First things first, copy the Ably API key from earlier into .env.local.

You can copy the key over from `src/app/[[...channel]]/chat/page.js` or fetch it from the Ably dashboard again:

```sh {{ filename: '.env.local', ins: [3] }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}}
CLERK_SECRET_KEY={{secret}}
ABLY_SECRET_KEY=XXXXXXX
```

Going forward, we'll only reference the environment variable (we'll remove it from the client-side code in due course).

Next, install `jose`, a JWT helper library:

```bash {{ filename: 'terminal' }}
npm install jose
```

Once `jose` installs, create `src/app/api/ably/route.js`:

```jsx {{ filename: 'src/app/api/ably/route.js' }}
import { currentUser } from '@clerk/nextjs/server'
import { SignJWT } from 'jose'

const createToken = (clientId, apiKey, claim, capability) => {
  const [appId, signingKey] = apiKey.split(':', 2)
  const enc = new TextEncoder()
  const token = new SignJWT({
    'x-ably-capability': JSON.stringify(capability),
    'x-ably-clientId': clientId,
    'ably.channel.*': JSON.stringify(claim),
    // 'ably.limits.publish.perAttachment.maxRate.chat': 0.1,
  })
    .setProtectedHeader({ kid: appId, alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('24h')
    .sign(enc.encode(signingKey))
  return token
}

const generateCapability = (claim) => {
  if (claim.isMod) {
    return { '*': ['*'] }
  } else {
    return {
      'chat:general': ['subscribe', 'publish', 'presence', 'history'],
      'chat:random': ['subscribe', 'publish', 'presence', 'history'],
      'chat:announcements': ['subscribe', 'presence', 'history'],
    }
  }
}

export const GET = async () => {
  const user = await currentUser()

  const userClaim = user.publicMetadata
  const userCapability = generateCapability(userClaim)

  const token = await createToken(user.id, process.env.ABLY_SECRET_KEY, userClaim, userCapability)

  return Response.json(token)
}
```

This is the backend authentication endpoint. It utilizes Clerk's `currentUser` function to access information about the [currently-authenticated user](https://clerk.com/blog/read-user-data-guide), including their ID. This user ID is used for the Ably `clientId`, ensuring every Ably client has a unique identifier.

If the user is a moderator according to the user's `publicMetadata`, we give them unrestricted Ably capabilities with `*`. Otherwise, they are limited to 3 channels:

```jsx {{ filename: 'src/app/api/ably/route.js' }}
if (claim.isMod) {
  return { '*': ['*'] }
} else {
  return {
    'chat:general': ['subscribe', 'publish', 'presence', 'history'],
    'chat:random': ['subscribe', 'publish', 'presence', 'history'],
    'chat:announcements': ['subscribe', 'presence', 'history'],
  }
}
```

What's more, we encode the `claim` in the token:

```jsx {{ prettier: false, filename: 'src/app/api/ably/route.js' }}
const token = new SignJWT({
  'ably.channel.*': JSON.stringify(claim),
})
```

Ably will include this claim in any events published by this client in topics with a name matching `*` . The `*` character is a wildcard, which Ably understands to mean the claim should be included in any event on any topic.

It's important to note that, after building up the token, we ultimately `sign` the token with `ABLY_SECRET_KEY` before returning it to the client.

Ably uses this same key to cryptographically verify the integrity of the token on the receiving end, ensuring the token was issued by a secure server (yours) and hasn't been tampered with. Coincidentally, this highlights just how important it is to keep your secret keys secret!

Next, update `src/app/chat/[[...channelName]]/page.js` to replace the hard-coded values with a link to the backend `authUrl`:

```jsx {{ filename: 'src/app/chat/[[...channelName]]/page.js', ins: [19, 20], del: [17, 18] }}
'use client'
import Chat from './chat'
import ChannelList from './channel-list'
import { Realtime } from 'ably'
import { AblyProvider, ChannelProvider } from 'ably/react'

const Page = ({ params }) => {
  const channels = [
    { path: '/chat/announcements', label: '# Announcements' },
    { path: '/chat/general', label: '# General' },
    { path: '/chat/random', label: '# Random' },
    { path: '/chat/mods-only', label: '# Mods-only', modOnly: true },
  ]

  const client = new Realtime({
    key: 'SlyWSw.jiu7RA:A64U3y3ty-9QLGB4Tn4NbBtlYYsx60qCI0ooT4WhNq0',
    clientId: 'Alex',
    authUrl: '/api/ably',
    autoConnect: typeof window !== 'undefined',
  })
  const channelName = `chat:${params.channel}`

  return (
    <AblyProvider client={client}>
      <ChannelProvider channelName={channelName}>
        <div className="grid h-[calc(100vh-72.8px)] grid-cols-4">
          <div className="border-r border-gray-200 p-5">
            <ChannelList channels={channels} />
          </div>
          <div className="col-span-2">
            <Chat channelName={channelName} />
          </div>
          <div className="border-l border-gray-200 p-5"></div>
        </div>
      </ChannelProvider>
    </AblyProvider>
  )
}
export default Page
```

Finally, update `src/app/[[...channel]]/chat/chat.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/chat.js', ins: [5, 20], del: [[17, 19]] }}
import MessageInput from './message-input'
import MessageList from './message-list'
import { useReducer } from 'react'
import { useChannel } from 'ably/react'
import { useUser } from '@clerk/nextjs'

const ADD = 'ADD'

const reducer = (prev, event) => {
  switch (event.name) {
    case ADD:
      return [...prev, event]
  }
}

const Chat = ({ channelName }) => {
  const user = {
    imageUrl: '',
  }
  const { user } = useUser()
  const [messages, dispatch] = useReducer(reducer, [])
  const { channel, publish } = useChannel(channelName, dispatch)

  const publishMessage = (text) => {
    publish({
      name: ADD,
      data: {
        text,
        avatarUrl: user.imageUrl,
      },
    })
  }

  return (
    <>
      <div className="overflow-y-auto p-5">
        <MessageList messages={messages} />
      </div>
      <div className="mt-auto p-5">
        <MessageInput onSubmit={publishMessage} />
      </div>
    </>
  )
}
export default Chat
```

Reload the page and instead of the "AB" initials I hard-coded earlier, you should see your own!

![Authenticated Next Chat App With Ably And Clerk guide illustration](./authenticated-chat.png)

## Lock moderator-only channels

The moderator-only channel isn't accessible to unauthorized users, yet, the user interface makes it look clickable.

It would be a confusing experience if your user clicks the channel only to get an Ably error because they don't have the necessary capabilities based on their role.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./err.png)

In this section, let's quickly disable moderator-only channels and show a lock icon if the user isn't authorized to participate.

First, install the `lucide-react` icon library by running the following command:

```bash {{ filename: 'terminal' }}
npm install lucide-react
```

This is where we'll get the lock icon from.

Next, update `src/app/[[...channel]]/chat/channel-list.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/channel-list.js', ins: [4, 5, 10, 11, 13, 20, 24] }}
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { clsx } from 'clsx'
import { useUser } from '@clerk/nextjs'
import { Lock } from 'lucide-react'

const ChannelList = ({ channels }) => {
  const currentPath = usePathname()
  const { user } = useUser()
  const userIsMod = user?.publicMetadata.isMod

  const createLi = (channel) => {
    const locked = channel.modOnly && !userIsMod
    return (
      <li key={channel.path}>
        <Link
          href={channel.path}
          className={clsx('flex items-center', {
            'font-bold': currentPath === channel.path,
            'pointer-events-none': locked,
          })}
        >
          {channel.label}
          {locked && <Lock className="m-1" size={16} />}
        </Link>
      </li>
    )
  }

  return <ul> {channels.map(createLi)} </ul>
}

export default ChannelList
```

Reload the page and you should see that the mods-only channel is "locked" and unclickable:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./mods-only.png)

To really test if this works, we'll need to grant our user moderator permissions. If all is working well, the channel should become "unlocked" and accessible.

## Managing moderator roles with Clerk

To promote your user to a moderator, open the Clerk **Users** tab in the dashboard and find your user.

If you've been making lots of test accounts like I sometimes do, a good tip is to sort by **Last signed in**:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./clerk-user-list.png)

Scroll to the bottom and **Edit** the public metadata to look like this:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./clerk-public-metadata.png)

Hit **Save**, then reload your app for good measure.

Your user and fellow moderators will now have access to the channel:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./mods-only-unlocked.png)

As an exercise, I like the idea of adding a `/promote {user}` command, similar to what you might find in Discord, say.

That would be totally doable on the backend using [`updateUserMetadata`](/docs/users/metadata), but I'll leave it as a stretch goal for you should you like the sound of this challenge!

## Implement message deleting

Next, let's enable users to delete messages.

Our requirements are as follows:

- Any user should be able to delete their own message (we all make typos).
- Meanwhile, moderators should have permission to delete anyone's messages.

Ably doesn't support deleting messages in the traditional sense, however, the service supports [message interactions](https://ably.com/docs/channels/messages?lang=javascript#interactions), which allow you to associate metadata such as "deleted" with a previously-sent message. This is known as a soft delete.

In the next section, we'll add a "delete message" button but, before implementing the code, I should explain how we plan to authorize message deletions. This will make the code in the next section a lot easier to understand.

At a high level, a message deletion is a special type of Ably event called a message interaction.

When the user click the "delete message" button, we'll publish an Ably message interaction called "delete" with an `extras` property that references the message they're trying to delete's identifier called a `timeserial.`

Here's a preview of code (you'll see where to slot it in the next section):

```jsx {{ title: 'Publishes a delete Ably message interaction' }}
const deleteMessage = (timeSerial) => {
  publish({
    name: DELETE,
    extras: {
      ref: {
        timeserial: timeSerial,
      },
    },
  })
}
```

Subscribers receive this event, at which point, we will execute some logic to process the message deletion:

```jsx {{ title: 'Subscribes to a delete Ably message interaction' }}
const reducer = (prev, event) => {
  switch (event.name) {
    case ADD:
      return [...prev, event]
    case DELETE:
      const isMod = JSON.parse(event.extras.userClaim).isMod
      return prev.filter((msg) => {
        const match = msg.extras.timeserial === event.extras.ref.timeserial
        const ownMsg = msg.clientId === event.clientId
        if (match && (ownMsg || isMod)) {
          return false
        }
        return true
      })
  }
}
```

- If the message identifier matches the deleted messages identifier, we move on to check if the user is allowed to delete it.
- If the message-to-delete was sent by the same client that published the delete event, we know they're allowed to delete it so we remove it from the `messages` state.
- Otherwise, they might be a moderator, so we check if the delete event has the `isMod` claim and, if so, proceed to remove the message from the messages state.

As a reminder, if the user is a moderator, Ably will include the `isMod` claim in any event they publish.

It's important to note that, while anyone can technically publish a delete message, we only process the deletion if the user owns the message or has the `isMod` claim. For anyone else who might publish a delete message, nothing happens, it has no effect.

With the theory out of the way (and a preview of the code), let's tie it all together in the next section.

### Implement a delete message button

First, create the shadcn/ui `menubar` component by running the following command, we'll need it in a moment:

```bash {{ filename: 'terminal' }}
npx shadcn-ui@latest add menubar
```

Next, enable message interactions for your Ably app.

To do this, open your app, click **Settings**, then **Add new rule**. Enter the namespace "chat", tick **Message interactions enabled**, then click **Create channel rule**.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./enable-message-interaction.png)

Once you've done that, update `src/app/[[...channel]]/chat/message-list.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/message-list.js', ins: [2, 3, 6, 7, 8, [21, 32]] }}
import { Avatar, AvatarImage } from '@/components/ui/avatar'
import { EllipsisVertical } from 'lucide-react'
import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarTrigger,
} from '@/components/ui/menubar'
import { useUser } from '@clerk/nextjs'

const userCanDelete = (message, user) => {
  return user.publicMetadata.isMod || message.clientId === user.id
}

const MessageList = ({ messages, onDelete }) => {
  const { user } = useUser()
  const createLi = (message) => (
    <li key={message.id} className="group my-2 flex justify-between bg-slate-50 p-3">
      <div className="flex items-center">
        <Avatar className="mr-2">
          <AvatarImage src={message.data.avatarUrl} />
        </Avatar>
        <p>{message.data.text}</p>
      </div>

      <Menubar>
        <MenubarMenu>
          <MenubarTrigger className="cursor-pointer">
            <EllipsisVertical size={16} />
          </MenubarTrigger>
          <MenubarContent>
            <MenubarItem
              disabled={!userCanDelete(message, user)}
              onClick={() => onDelete(message.extras.timeserial)}
            >
              Delete
            </MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
    </li>
  )

  return <ul> {messages.map(createLi)} </ul>
}
export default MessageList
```

Finally, update `src/app/[[...channel]]/chat/chat.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/chat.js', ins: [8, [14, 23], [43, 52], 58], del: [57] }}
import MessageInput from './message-input'
import MessageList from './message-list'
import { useReducer } from 'react'
import { useChannel } from 'ably/react'
import { useUser } from '@clerk/nextjs'

const ADD = 'ADD'
const DELETE = 'DELETE'

const reducer = (prev, event) => {
  switch (event.name) {
    case ADD:
      return [...prev, event]
    case DELETE:
      const isMod = JSON.parse(event.extras.userClaim).isMod
      return prev.filter((msg) => {
        const match = msg.extras.timeserial === event.extras.ref.timeserial
        const ownMsg = msg.clientId === event.clientId
        if (match && (ownMsg || isMod)) {
          return false
        }
        return true
      })
  }
}

const Chat = ({ channelName }) => {
  const { user } = useUser()
  const [messages, dispatch] = useReducer(reducer, [])
  const { channel, publish } = useChannel(channelName, dispatch)

  const publishMessage = (text) => {
    console.log('user', user)
    publish({
      name: ADD,
      data: {
        text,
        avatarUrl: user.imageUrl,
      },
    })
  }

  const deleteMessage = (timeSerial) => {
    publish({
      name: DELETE,
      extras: {
        ref: {
          timeserial: timeSerial,
        },
      },
    })
  }

  return (
    <>
      <div className="overflow-y-auto p-5">
        <MessageList messages={messages} />
        <MessageList messages={messages} onDelete={deleteMessage} />
      </div>
      <div className="mt-auto p-5">
        <MessageInput onSubmit={publishMessage} />
      </div>
    </>
  )
}
export default Chat
```

Reload the application. Assuming you're logged into the same account and still have `isMod` , you will have the ability to **Delete** any message.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./can-delete-messages.png)

Remove the `isMod` role from your user via the Clerk dashboard and reload the page for good measure, and the **Delete** button will now be greyed out:

![Authenticated Next Chat App With Ably And Clerk guide illustration](./cant-delete-messages.png)

## Implement chat message history

Ably is not a database, but it does hold on to events history for 24-72 hours (free accounts are limited to 24 hours).

You should ideally store chat messages in your own databases for long-term processing (either using an [Ably queue](https://ably.com/docs/general/queues) or by subscribing to events on your server), but Ably history allows us to conveniently populate the UI with recent messages so that users have some context about the conversation they're joining.

To enable message history, open your Ably app in the Ably dashboard, click **Settings**, spot the rule you created in the previous step for message interactions, click **Edit,** then tick **Persist all messages**. Finally, click **Save**.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./enable-message-hist.png)

Next, update `src/app/[[...channel]]/chat/chat.js` to fetch message history. While we're here, we'll also create a React hook that automatically scrolls messages into view:

```jsx {{ filename: 'src/app/[[...channel]]/chat/chat.js', ins: [4, 32, [55, 69], 75], del: [3] }}
import MessageInput from './message-input'
import MessageList from './message-list'
import { useReducer } from 'react'
import { useReducer, useEffect, useRef } from 'react'
import { useChannel } from 'ably/react'
import { useUser } from '@clerk/nextjs'

const ADD = 'ADD'
const DELETE = 'DELETE'

const reducer = (prev, event) => {
  switch (event.name) {
    case ADD:
      return [...prev, event]
    case DELETE:
      const isMod = JSON.parse(event.extras.userClaim).isMod
      return prev.filter((msg) => {
        const match = msg.extras.timeserial === event.extras.ref.timeserial
        const ownMsg = msg.clientId === event.clientId
        if (match && (ownMsg || isMod)) {
          return false
        }
        return true
      })
  }
}

const Chat = ({ channelName }) => {
  const { user } = useUser()
  const [messages, dispatch] = useReducer(reducer, [])
  const { channel, publish } = useChannel(channelName, dispatch)
  const scrollRef = useRef(null)

  const publishMessage = (text) => {
    console.log('user', user)
    publish({
      name: ADD,
      data: {
        text,
        avatarUrl: user.imageUrl,
      },
    })
  }

  const deleteMessage = (timeSerial) => {
    publish({
      name: DELETE,
      extras: {
        ref: {
          timeserial: timeSerial,
        },
      },
    })
  }
  useEffect(() => {
    let ignore = false
    const fetchHist = async () => {
      const history = await channel.history({ limit: 100, direction: 'forwards' })
      if (!ignore) history.items.forEach(dispatch)
    }
    fetchHist()
    return () => {
      ignore = true
    }
  }, [channel])

  useEffect(() => {
    scrollRef.current.scrollIntoView()
  }, [messages.length])

  return (
    <>
      <div className="overflow-y-auto p-5">
        <MessageList messages={messages} onDelete={deleteMessage} />
        <div ref={scrollRef} />
      </div>
      <div className="mt-auto p-5">
        <MessageInput onSubmit={publishMessage} />
      </div>
    </>
  )
}
export default Chat
```

Send a test message or two and reload the page. Whereas before, the chat start completely empty, the chat now loads with recent messages, allowing your users to find their place in the conversation.

## Implement an online list

An online list creates a sense of togetherness among your users and subtly communicates who's online and likely to respond.

![Authenticated Next Chat App With Ably And Clerk guide illustration](./u-up.png)

Using Ably, we can implement such functionality using a feature called presence.

Presence provides information and realtime updates about who's "present" on a particular topic.

First, create `/src/app/chat[[...channel]]/whos-online-list.js`:

```jsx {{ filename: '/src/app/chat[[...channel]]/whos-online-list.js' }}
'use client'
import { usePresence, usePresenceListener } from 'ably/react'
import { useUser } from '@clerk/nextjs'
import { Circle } from 'lucide-react'

const WhosOnlineList = ({ channelName }) => {
  const { user } = useUser()
  const { presenceData } = usePresenceListener(channelName)
  usePresence(channelName, { fullName: user.fullName })
  const users = presenceData
  const color = '#01FE19'

  const createLi = (user) => {
    return (
      <li key={user.id} className="flex items-center">
        <Circle className="mr-1" size={8} fill={color} color={color} />
        {user.data.fullName}
      </li>
    )
  }

  return (
    <div>
      <h2 className="mb-2.5">Present and together right now with you in {channelName}:</h2>
      <ul>{users.map(createLi)}</ul>
    </div>
  )
}
export default WhosOnlineList
```

Now, for the final time in this tutorial, update `src/app/[[...channel]]/chat/page.js`:

```jsx {{ filename: 'src/app/[[...channel]]/chat/page.js', ins: [4, 33] }}
'use client'
import Chat from './chat'
import ChannelList from './channel-list'
import WhosOnlineList from './whos-online-list'
import { Realtime } from 'ably'
import { AblyProvider, ChannelProvider } from 'ably/react'

const Page = ({ params }) => {
  const channels = [
    { path: '/chat/announcements', label: '# Announcements' },
    { path: '/chat/general', label: '# General' },
    { path: '/chat/random', label: '# Random' },
    { path: '/chat/mods-only', label: '# Mods-only', modOnly: true },
  ]

  const client = new Realtime({
    authUrl: '/api/ably',
    autoConnect: typeof window !== 'undefined',
  })
  const channelName = `chat:${params.channel}`

  return (
    <AblyProvider client={client}>
      <ChannelProvider channelName={channelName}>
        <div className="grid h-[calc(100vh-72.8px)] grid-cols-4">
          <div className="border-r border-gray-200 p-5">
            <ChannelList channels={channels} />
          </div>
          <div className="col-span-2">
            <Chat channelName={channelName} />
          </div>
          <div className="border-l border-gray-200 p-5">
            <WhosOnlineList channelName={channelName} />
          </div>
        </div>
      </ChannelProvider>
    </AblyProvider>
  )
}
export default Page
```

As users come and go, so does the green light that indicates their online status.

Here, we use presence on the topic for the user's current chat channel, which means the online status is on a per-channel basis.

Alternatively, you could introduce a new topic like "clover-corp" (or whatever meaningful name you gave your app) and enable presence on a per-app basis instead. Remember, whenever you introduce a new Ably topic, you'll need to introduce another `ChannelProvider`.

## Conclusion

In this tutorial, we fleshed out a highly-featured authenticated Next.js chat application, featuring message deletion, online presence, and a moderator role!

If you found this guide useful, tell us how you used it in your app on X, and be sure to tag @clerk to let us know!

---

# Build a waitlist with Clerk user metadata
URL: https://clerk.com/blog/build-a-waitlist-with-clerk-user-metadata.md
Date: 2024-05-28
Category: Guides
Description: Learn how to use Clerk user metadata to build a waitlist for your application, as well as an admin dashboard to toggle user access.

As of Nov 2024, Clerk now has a built-in waitlist feature that you can use instead of the one outlined in this article. [Learn more](/docs/guides/waitlist).

Fast feedback when building a software-as-a-service application is critical.

This is especially true in the early days of building. The quicker you can get a working version of your product in the hands of users, the faster you can collect input and make decisions based on that input. Doing so can make an incredible difference in the success of your online business. One option is to use a platform to collect emails and notify them that the application is ready to test, but wouldn't it be nice to have them sign up for the application directly first?

In this article, you'll learn how to configure Clerk to allow users to sign up for your application but restrict their access until you explicitly allow it. You'll also learn how to create a page to interact with the user's info in Clerk to grant them access to the application.

> 💡 To follow along with this article, you'll need a [free Clerk account](https://dashboard.clerk.com/sign-up), as well as Node.js installed on your computer.

## Follow along using the Cooking with Clerk repository

Cooking with Clerk is an open-source web application built with Clerk and Next.js that will be used to apply the techniques outlined in this article. The application is an AI-powered recipe generator that uses OpenAI's API as part of the generative process. During development, we don't want to allow anyone to use it since it can easily start increasing our cost to use the OpenAI API.

If you want to follow along, clone the repository to your computer and follow the steps outlined in the `readme.md` file to get your local environment set up. The source code can be found at [https://github.com/bmorrisondev/cooking-with-clerk](https://github.com/bmorrisondev/cooking-with-clerk).

The remainder of this article assumes you will be following along using the `waitlist-article` branch, however this is entirely optional. It also assumes you've already signed in with your own account.

To build the waitlist functionality, we'll be performing the following actions:

- Configure session tokens and user metadata to flag users in and out of the waitlist.
- Set up the Clerk middleware to redirect users based on those flags.
- Design an admin dashboard that allows administrators to enable/disable users.

## Configure session tokens and user metadata

Users in Clerk can be configured with [various types of metadata](/docs/users/metadata) that can store information about that user in JSON format.

We can take advantage of this storage mechanism to assign the various flags to users of our application:

- `isBetaUser` can be used to determine if the user has access to test the application while in early development.
- `isAdmin` can be used to determine if the user has access to the admin dashboard that will be created to allow users into the beta.

Let's start by setting the `isAdmin` flag on our own account. Open the Clerk dashboard and navigate to **"Users"** from the left navigation.

![The Clerk Dashboard Users section](./the-clerk-dashboard-users-section.png)

Select the user you want to allow admin access to, then scroll to the bottom and locate the **Metadata** section. Click the first **"Edit"** button to edit the users' public metadata.

![Editing the user's public metadata in the Clerk Dashboard](./the-clerk-dashboard-edit-public-metadata.png)

Paste the following into the JSON editor and click Save.

```json
{
  "isBetaUser": true
}
```

Now even though public metadata is accessible from the front end, we'll be modifying the Clerk middleware to determine where to redirect the user once they've signed in. This means we'll need to add the public metadata to the claims so we have access to it before the user is fully loaded in the front end.

To do this, select **"Sessions"** from the left navigation, then click **"Edit"** in the **Customize session token** section.

![The sessions tab of the Clerk Dashboard](./the-sessions-tab-of-the-clerk-dashboard.png)

Paste the following into the JSON editor and click Save.

```json
{
  "metadata": "{{user.public_metadata}}"
}
```

Every token minted from now on will contain the JSON that is saved to the user's public metadata within the claims of the token.

It's worth noting that the total size of the authentication object (including custom session claims) cannot exceed 4kb.

## Route users using Clerk middleware

The Clerk middleware runs on every page load to determine if the user is authenticated and is allowed to access the requested resource using the `isProtectedRoute` helper. For example, the following middleware configuration will protect every page that starts with the `/app` route and the `/api` route:

```tsx {{ filename: 'src/middleware.ts' }}
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/app(.*)', '/api(.*)'])

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) {
    auth().protect()
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}
```

Clerk will automatically parse the session claims (where the public metadata is) within the `auth()` function, which means it's accessible to us during this process like so:

```tsx
const { sessionClaims } = auth()
```

Using this, we can determine if the session claims contain our `isBetaUser` flag. Update the `src/middleware.ts` file to match the following:

```tsx {{ filename: 'src/middleware.ts', ins: [[11, 13], [20, 25]] }}
// 👉 Update the imports
import { ClerkMiddlewareAuth, clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

const isProtectedRoute = createRouteMatcher(['/app(.*)', '/api(.*)'])

// 👉 Create a type to define the metadata
type UserMetadata = {
  isBetaUser?: boolean
}

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) {
    auth().protect()

    // 👉 Use `auth()` to get the sessionClaims, which includes the public metadata
    const { sessionClaims } = auth()
    const { isBetaUser } = sessionClaims?.metadata as UserMetadata
    if (!isBetaUser) {
      // 👉 If the user is not a beta user, redirect them to the waitlist
      return NextResponse.redirect(new URL('/waitlist', req.url))
    }
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}
```

From now on, any user that does not have `isBetaUser` defined in their public metadata will instead be redirected to a page that simply tells them that they are on the waitlist. It's also worth noting that since this check is performed after `auth().protect()`, this function will only run if the user is logged in with a Clerk account, preventing it from running when not needed.

To see this in action, start the project on your computer by running `npm run dev` in your terminal and navigate to the URL displayed in the terminal (the default is `http://localhost:3000`, but may differ if another process is using port 3000).

![Cooking with Clerk homepage](./cooking-with-clerk-homepage.png)

Click **"Sign In"** in the upper right and log in with the user account you used during setup. You should be able to access and test the app with no issues.

![Cooking with Clerk with recipes generated](./cooking-with-clerk-recipes.png)

Now sign out using the user menu, and sign in again with a different account. You'll notice that instead of accessing the application, you are redirected to `/waitlist`. This is the middleware at work!

![Cooking with Clerk waitlist](./cooking-with-clerk-waitlist.png)

## Creating the admin area

Now that we've built the capability into the app to require the `isBetaUser` flag to be set, we need a way to set this for users interested in testing the app. Sure, it can be done from within the Clerk dashboard, but we can also take advantage of the Clerk SDK to create a page that allows us to perform this action within the app. Start by creating the `src/app/admin/page.tsx` file and paste the following code into it. This will create a page at `/admin` that displays an empty table.

```tsx {{ filename: 'src/app/admin/page.tsx' }}
import React from 'react'
import { Table, TableBody, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import UserRow from './UserRow'
import { clerkClient } from '@clerk/nextjs/server'

export const fetchCache = 'force-no-store'

async function Admin() {
  // 👉 Gets the users from the Clerk application
  let res = await clerkClient.users.getUserList()
  let users = res.data

  return (
    <main>
      <h1 className="my-2 text-2xl font-bold">Admin</h1>
      <h2 className="my-2 text-xl">Users</h2>
      <Table className="rounded-lg border border-gray-200">
        <TableHeader>
          <TableRow>
            <TableHead className="w-[100px]">Name</TableHead>
            <TableHead>Email</TableHead>
            <TableHead className="text-right">Beta enabled?</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>{/* 👉 User records will be displayed here */}</TableBody>
      </Table>
    </main>
  )
}

export default Admin
```

Next, we're going to create a client component that will display a row for each user within the table named `UserRow`. Before we do that, however, we need a server action that the `UserRow` component can use to interact with the Clerk Backend SDK to toggle the `isBetaUser` flag within a user's public metadata. Create the `src/app/admin/actions.ts` file and populate it with the following:

```tsx {{ filename: 'src/app/admin/actions.ts' }}
'use server'

import { clerkClient } from '@clerk/nextjs/server'

export async function setBetaStatus(userId: string, status: boolean) {
  await clerkClient.users.updateUserMetadata(userId, {
    publicMetadata: {
      isBetaUser: status,
    },
  })
}
```

Now create the `src/app/admin/UserRow.tsx` file with the following contents. This will be used to render each user in a row on the admin page.

```tsx {{ filename: 'src/app/admin/UserRow.tsx' }}
'use client'
import React, { useState } from 'react'
import { TableCell, TableRow } from '@/components/ui/table'
import { Switch } from '@/components/ui/switch'
import { setBetaStatus } from './actions'

// 👉 Define the necessary props we need to render the component
type Props = {
  name: string
  id: string
  emailAddress?: string
  metadata?: UserPublicMetadata
}

function UserRow({ name, id, metadata, emailAddress }: Props) {
  // 👉 Set the initial state of `isBetaUser` based on the metadata
  const [isBetaUser, setIsBetaUser] = useState(metadata?.isBetaUser || false)

  // 👉 Calls the server action defined earlier and sets the state on change
  async function onToggleBetaStatus() {
    try {
      await setBetaStatus(id, !isBetaUser)
      setIsBetaUser(!isBetaUser)
    } catch (err) {
      console.error(err)
    }
  }

  return (
    <TableRow>
      <TableCell className="flex flex-col">
        <span>{name}</span>
        <span className="text-xs text-gray-600 italic">{id}</span>
      </TableCell>

      <TableCell>{emailAddress}</TableCell>

      <TableCell className="text-right">
        <Switch onCheckedChange={onToggleBetaStatus} checked={isBetaUser} aria-readonly />
      </TableCell>
    </TableRow>
  )
}

export default UserRow
```

Finally, update `src/app/admin/page.tsx` by importing the new component and adding it to the table:

```tsx {{ filename: 'src/app/admin/page.tsx', ins: [10, [31, 37]] }}
import React from 'react'
import { Table, TableBody, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { clerkClient } from '@clerk/nextjs/server'
import UserRow from './UserRow'

export const fetchCache = 'force-no-store'

async function Admin() {
  let res = await clerkClient.users.getUserList()
  let users = res.data

  return (
    <main>
      <h1 className="my-2 text-2xl font-bold">Admin</h1>
      <h2 className="my-2 text-xl">Users</h2>
      <Table className="rounded-lg border border-gray-200">
        <TableHeader>
          <TableRow>
            <TableHead className="w-[100px]">Name</TableHead>
            <TableHead>Email</TableHead>
            <TableHead className="text-right">Beta enabled?</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {users?.map((u) => (
            <UserRow
              key={u.id}
              id={u.id}
              name={`${u.firstName} ${u.lastName}`}
              metadata={u.publicMetadata}
              emailAddress={u.emailAddresses[0]?.emailAddress}
            />
          ))}
        </TableBody>
      </Table>
    </main>
  )
}

export default Admin
```

Open the app in your browser again and navigate to `/admin`, you should see a list of the users from your Clerk application displayed in a table. Notice how only the account you manually added `isBetaUser` to during the first part of this guide has the toggle enabled under the Beta enabled? column.

![Cooking with Clerk admin panel](./cooking-with-clerk-admin-panel.png)

Now, if you toggle another user on and log in again with that account, you should be redirected to `/app` instead of `/waitlist`! Furthermore, if you open the application in the Clerk dashboard and review the user's public metadata, you should see that `isBetaUser` has been enabled via the dashboard.

## Securing the admin page

At this point, we've effectively built the waitlist functionality, as well as created a polished experience for controlling the flags enabled on the user account. The problem is that the middleware is set up only to protect `/app` and not `/admin`, so anyone with the beta flag could technically access the admin panel. With a few minor tweaks to `middleware.ts`, we can also prevent users from accessing the admin panel:

```tsx {{ filename: 'src/middleware.ts', ins: [7, 12, [21, 29]], del: [20] }}
import { ClerkMiddlewareAuth, clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

const isProtectedRoute = createRouteMatcher(['/app(.*)', '/api(.*)', '/admin(.*)'])

type UserMetadata = {
  isBetaUser?: boolean
  isAdmin?: boolean
}

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) {
    auth().protect()

    const { sessionClaims } = auth()
    const { isBetaUser } = sessionClaims?.metadata as UserMetadata
    const { isAdmin, isBetaUser } = sessionClaims?.metadata as UserMetadata
    if (isAdmin) {
      // 👉 If the user is an admin, let them proceed to anything
      return
    }
    if (!isAdmin && req.nextUrl.pathname.startsWith('/admin')) {
      // 👉 If the user is not an admin and they try to access the admin panel, return an error
      return NextResponse.error()
    }
    if (!isBetaUser) {
      return NextResponse.redirect(new URL('/waitlist', req.url))
    }
  }
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ],
}
```

Now whenever someone tries to access `/admin` without the `isAdmin` flag set in their Clerk user metadata, they'll get a 404 page instead of the admin panel.

## Conclusion

Clerk user metadata can be extremely useful for storing various information about the user.

This is simply one example of how to use metadata. If you need some more inspiration, we also have a blog post showing how to build an onboarding flow using a similar approach that I recommend reading!

Do you have an interesting way you've used user metadata in your application? Share it on X and let us know by tagging [@clerk](https://x.com/clerk)!

---

# How to use Clerk with PostHog Identify in Next.js
URL: https://clerk.com/blog/how-to-use-clerk-with-posthog-identify-in-nextjs.md
Date: 2024-05-09
Category: Guides
Description: Learn how to configure your Next.js applications to send Clerk user data to PostHog for analytics and data analysis.

The ability to gather analytics and trace actions on your website is important, but data analysis can be tricky.

[PostHog](https://posthog.com) is an open-source platform that enables developers to easily add features to their web apps such as product analysis, session replay, feature flags, and more. By default, PostHog will automatically start gathering data about your web application and how it's being used. When properly configured, you can also tie data about your Clerk users to the events that are captured, whether this is using default events like the `$pageview` event, or custom events you program directly into your code.

In this article, you'll learn how to link Clerk users with data in PostHog to confidently identify what specific users are doing in your Next.js application.

## How PostHog works

When PostHog is first installed, it will automatically start creating sessions for users who visit your web applications.

These sessions are identified by [universally unique identifiers](https://www.uuidtools.com/what-is-uuid) (UUIDs) and each ID is specific to the browser that is accessing the site. PostHog will then start gathering data about the usage of the web app such as page views, elements clicked, etc. While this functionality in itself is super powerful, we live in a world where users often interact with their favorite services from a number of different devices.

This is where the [`identify`](https://posthog.com/docs/product-analytics/identify#how-identify-works) function comes in.

### Identifying users with `identify`

The `identify` function in PostHog allows you to add your own user IDs to sessions, along with any custom metadata you'd like to include.

Doing so allows you to enrich the data gathered by PostHog, and allows the PostHog dashboard to display the captured events with the user profile along with any additional details you associated with the session. And one of the great outcomes of using `identify` is you can trace the actions of your users across different devices, as PostHog will link those sessions based on the user ID you configured.

This can be incredibly useful when it comes to debugging user issues or gathering intel about specific features.

## How to configure a Clerk and PostHog to work together

Now that you understand how `identify` can be used to associate individual users with PostHog events that span across devices, let's look at how to configure this in your Next.js application. The demo app is a relatively simple app that uses a single input and button to generate recipe suggestions using OpenAI's developer platform and render them in a grid. This project already has [Clerk installed and configured](/docs/quickstarts/nextjs), a PostHog project created, and the library configured as directed in the [Next.js guide in their docs](https://posthog.com/docs/libraries/next-js).

![The 'Cooking with Clerk' home page with a list of recipes based on 'Ground beef, garlic, asian themed' query](./cooking-with-clerk.png)

By default, PostHog will capture certain actions as they're performed in the web app, such as typing in a field or clicking a button.

![The PostHog dashboard with a list of events from an anonymous user](./posthog-dashboard-1.png)

Notice, however, that the Person column shows a UUID instead of a user name. This will be the case even if I am signed into my app using Clerk. This can be fixed by using the `identify` function from PostHog along with the information Clerks Next.js library makes available in a few hooks.

The first we'll use from Clerk is `useAuth`, which provides some basic information about the session such as the login status and user ID. The second hook that will be used is `useUser` which returns more detailed information about the user who's logged in. By adding another `useEffect` the `PostHogPageView` component created in their onboarding guide, we can check to make sure the user is logged in, that the info we need is available, and the `identify` function has not already been called before linking the user's data to the session. The `posthog._isIdentified()` function lets us know if `identify` has been run previously, so this can be used to prevent the function from running when it doesn't need to.

```tsx {{ filename: 'app/PostHogPageView.tsx' }}
'use client'

import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
import { usePostHog } from 'posthog-js/react'

// 👉 Import the necessary Clerk hooks
import { useAuth, useUser } from '@clerk/nextjs'

export default function PostHogPageView(): null {
  const pathname = usePathname()
  const searchParams = useSearchParams()
  const posthog = usePostHog()

  // 👉 Add the hooks into the component
  const { isSignedIn, userId } = useAuth()
  const { user } = useUser()

  // Track pageviews
  useEffect(() => {
    if (pathname && posthog) {
      let url = window.origin + pathname
      if (searchParams.toString()) {
        url = url + `?${searchParams.toString()}`
      }
      posthog.capture('$pageview', {
        $current_url: url,
      })
    }
  }, [pathname, searchParams, posthog])

  useEffect(() => {
    // 👉 Check the sign-in status and user info,
    //    and identify the user if they aren't already
    if (isSignedIn && userId && user && !posthog._isIdentified()) {
      // 👉 Identify the user
      posthog.identify(userId, {
        email: user.primaryEmailAddress?.emailAddress,
        username: user.username,
      })
    }
  }, [posthog, user])

  return null
}
```

Now by signing in and performing those same actions, my account info is also displayed in the PostHog dashboard!

![The PostHog dashboard with a list of events from an identified user](./posthog-dashboard-2.png)

Now the one thing that needs to be addressed is logging out. If the info passed into `identify` is not cleared out and the person is using a shared computer, there is a chance that you'd get some bad data since your app might think the previous user is still logged in. By adding another few lines of code to the `useEffect` created, you can configure your app to clear the data when the user logs out:

```tsx
useEffect(() => {
  // 👉 Check the sign in status and user info,
  //    and identify the user if they aren't already
  if (isSignedIn && userId && user && !posthog._isIdentified()) {
    // 👉 Identify the user
    posthog.identify(userId, {
      email: user.primaryEmailAddress?.emailAddress,
      username: user.username,
    })
  }

  // 👉 Reset the user if they sign out
  if (!isSignedIn && posthog._isIdentified()) {
    posthog.reset()
  }
}, [posthog, user])
```

## Conclusion

With a small modification to the default `PostHogPageView` component, you can dramatically increase user traceability in your web app analytics! This method enables you to not only trace user flows within a single session, but with every session they create over a number of devices.

If you found this guide useful, tell us how you used it in your app on X, and be sure to tag [@clerk](https://x.com/clerk) to let us know!

---

# How to secure API Gateway using JWT and Lambda Authorizers with Clerk
URL: https://clerk.com/blog/how-to-secure-api-gateway-using-jwt-and-lambda-authorizers-with-clerk.md
Date: 2024-04-29
Category: Guides
Description: Learn what API Gateway authorizers are, how they work, and how to use them with Clerk to secure your API endpoints using JWT and Lambda authorizers.

One of the common ways to access AWS services over HTTP is through API Gateway.

API Gateway acts as a centralized entry point for many of the services offered through AWS. For example, you can configure serverless Lambda functions that are capable of accepting HTTP events from API Gateway for processing, enabling you to build a completely serverless backend for your application or service. Allowing any traffic into an AWS account should be done securely, otherwise, you run the risk of services being taken advantage and causing issues such as data exfiltration or a surprise AWS bill. Luckily, API Gateway offers a feature called *authorizers* that can be used to secure your endpoints before traffic ever reaches the service on the other side.

In this article, you'll learn what API Gateway authorizers are, how they work, and [how to use them with Clerk](#using-clerk-with-api-gateway-authorizers).

## What are API Gateway Authorizers?

API Gateway authorizers are a feature of API Gateway that allows you to lock down your API endpoints so that only authorized requests are permitted.

API Gateway is compatible with a wide array of AWS services, allowing you to mix and match multiple services behind a single domain to precisely craft the service that your users need. While services such as Lambda or EC2 can have built-in logic to verify the request, something like DynamoDB does not have that capability. When an authorizer is attached to an endpoint, API Gateway will first use the authorizer to verify that the request being sent in is by an authorized party before passing it through to the service, or denying the request if it's unauthorized.

To better explain how an authorizer works, I'll use the example of the serverless API in the introduction of this article.

## How Authorizers Work

Let's assume you have a simple serverless API that combines API Gateway and a series of Lambda functions.

When a request comes into the API, the request will be proxied to one of the associated Lambda functions for processing before sending a response back to the caller. Without authorizers, the request is sent directly to the Lambda function, regardless of who sent it or where it comes from. This means that the code for each Lambda would need to individually verify that the request is valid.

![A diagram showing API Gateway passing through a request](./api-gateway-diagram.jpg)

Now let's look at the same example with an authorizer attached to each endpoint. When a request comes into an endpoint that is protected with an authorizer, API Gateway will first send the request to the authorizer to verify and deny the request depending on if the it passes the checks defined by the authorizer.

![A diagram showing API Gateway using an authorizer](./api-gateway-authorizer-diagram.jpg)

Authorizers enable you to centralize your authorization logic, protect services that would otherwise be difficult to protect, and utilise caching to reduce your AWS bill.

## Understanding JWT and Lambda Authorizers

There are several types of authorizers available depending on the type of API Gateway configuration you have, but we're going to focus on the two that are compatible with Clerk: JWT and Lambda authorizers

### JWT Authorizers

A JWT authorizer uses an [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) endpoint to validate tokens based on the included JSON Web Key Set (JWKS).

The OpenID Connect Discovery endpoint contains information about the identity provider (also known as the IdP) that can be used when configuring services to support authenticating with the IdP. This endpoint contains information such as who is issuing the JWT (or token), what authorization scopes are supported, what information is included in the tokens, etc.

One of the things that is often included in the Discovery endpoint is the URI of the JWKS, a collection of public keys that can be used to verify the signature on a token. If the signature of a token is valid, the information about the user that is included within it can be considered trustworthy. When a request comes in that is protected by a JWT authorizer, AWS will use the publically available JWKS, along with an audience (`aud`) value in the claims, to validate the token and pass the request on if it's valid.

JWT Authorizers are a simple way of verifying requests using the JWKS but are only available on the HTTP-type of API Gateway instances.

### Lambda Authorizers

Lambda authorizers allow you to create a custom Lambda function using the language of your choice to validate inbound requests.

They offer the most flexibility but are also relatively complex when compared to JWT authorizers. When a Lambda authorizer is executed, the configured authorization header is passed along to a Lambda function in the event parameter, but it's up to you to write the code that validates the event and responds with an IAM policy. However, because you are essentially writing code, you can even parse the claims and conditionally permit the request based on more than just an audience value, something we'll explore later in this article.

Lambda authorizers are compatible with both REST and HTTP API Gateway types.

## Using Clerk with API Gateway Authorizers

Clerk's use of [JWTs](/blog/how-we-roll-sessions) makes our service compatible with API Gateway authorizers when configured as explained in this article.

When a user signs into an application with Clerk, a short-lived token is created and stored in the browser cookies. Clerk's libraries allow you to easily extract this token and use it however you need, including adding it to the authorization header of an HTTP request:

```jsx
'use client'
import { useSession } from '@clerk/nextjs'

export default function Home() {
  const { session } = useSession()

  async function callApi() {
    const token = await session?.getToken()
    await fetch(url, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
  }

  // Code removed for brevity...
}
```

Now let's take a look at how to configure both a JWT and Lambda authorizer to work with Clerk.

### Prerequisites

To follow along with this portion of the article, you'll need the following configured:

- An AWS account, and familiarity with Lambda and API Gateway
- A [Clerk account](/sign-up)
- Node and NPM installed on your computer

> While everything discussed attempts to be covered as part of the AWS free tier, be aware that we will be creating resources that may cost real money.

### Using Clerk with JWT Authorizers

As mentioned earlier, JWT Authorizers require you to know the OpenID Connect Discovery endpoint, as well as an `aud` value in the claims of the token being checked, so let's start by gathering this info.

In the Clerk Dashboard, select **"API Keys"** from the navigation, then click **"Show API URLs"**. This will show you URLs for the Frontend and Backend API. Take note of the value value in the **Frontend API URL** field, this will be used as the OpenID Connect Discovery endpoint as Clerk automatically sets up this endpoint for each application created in your account.

Next, you'll need to configure the `aud` claim value since Clerk tokens do not contain this by default. The value can be any arbitrary string, it just needs to match what's specified in the authorizer configuration, which we'll do in the next step. This example uses "ClerkJwtAuthorizer" as the value, but you're free to use something else.

To add a static value to all tokens, select **"Sessions"** from the navigation, then the **"Edit"** button under **Customize session token**. In the modal that appears, modify the **Claims** to include an `aud` value. If you don't have any other custom claims defined, it should look like this:

```json
{
  "aud": "ClerkJwtAuthorizer"
}
```

In an HTTP-type API Gateway instance in AWS, you can create an authorizer by selecting **"Authorization"** in the left navigation, then the **"Manage authorizers"**, and finally the **"Create"** button.

![Create an HTTP JWT authorizer](./1-http-create-authorizer.png)

JWT Authorizer is selected by default, but you'll need to populate the following values:

- **Name** - A friendly name for the authorizer.
- **Identity source** - Where the token should be referenced in the request. This can be left with the default value of “*$request.header.Authorization*” which will use the `Authorization` header of the inbound request.
- **Issuer URL** - This should be set to the Frontend API URL value from earlier.
- **Audience** - The value set earlier in the `aud` field of the session claims (you may have to click **"Add Audience"** for this input to appear).

![The settings view when creating an HTTP JWT authorizer](./2-http-authorizer-settings.png)

Once you click **"Create"**, you'll be returned to the previous screen. From here, select the **"Attach authorizers to routes"** tab. This will display a list of the routes configured in your API. Choose a route from the list, then use the **"Select existing authorizer dropdown"** to select the authorizer you created earlier, then **"Attach authorizer"**.

![A screenshot of the AWS UI showing how to attach an authorizer to a route](./3-http-attach-authorizer.png)

Repeat this process for every route you want to protect, and any request that are executed against these routes will automatically be protected using the user's Clerk session token.

> One caveat to using JWT authorizers is that they are incompatible with the `ANY` method available in AWS if the API will be called from the browser. This is due to the fact that CORS preflight requests will not include the Authorization header, which will cause the authorizer to deny the preflight request.

### Using a Lambda Authorizer with Clerk

While Lambda authorizers are compatible with HTTP-type API Gateways, they are more common in REST types, so this section of the guide will move over to a REST API. Since Lambda authorizers are limited to a short execution window, we'll be using [Clerk networkless verification](/docs/references/nodejs/token-verification) to make sure the request is authorized. Essentially we'll be embedding the public key of the key set into the code to eliminate unnecessary network requests, making the code as efficient as possible.

Start in the Clerk dashboard and navigate back to the **"API Keys"** section. Now select **"Show JWT public key."** From the modal, copy the block of text under **PEM Public Key** for later use.

Now let's create a Lambda function in AWS that will serve as the authorizer. As stated earlier, you can use any supported language, but I'll be using JavaScript for this demo. Start a terminal session on your workstation and run the following commands to initialize a new Node project and install the `jsonwebtoken` library.

```bash
# Initialize a new NPM project
npm init -y

# Install the `jsonwebtoken` dependency
npm install jsonwebtoken
```

In the root of the project, create an `index.js` file and populate it with the following code, replacing the contents of the `publicKey` variable with the **PEM Public Key** string from earlier (it should be a multiline string in the code). Notice how we're also checking the user's metadata included in the claims to make sure they have the role of “admin”, something that is not possible with JWT authorizers.

```jsx
import jwt from 'jsonwebtoken'

const publicKey = `{PASTE YOUR PEM KEY HERE}`

export async function handler(event, context, callback) {
  // Extract the token from the Authorization header
  const token = event.authorizationToken.split(' ')[1]

  // Verifies and decodes the JWT
  const claims = jwt.verify(token, publicKey)

  // Check if the user is an admin
  if (claims.metadata.role === 'admin') {
    callback(null, generatePolicy(claims.metadata, claims.sub, 'Allow', event.methodArn))
  } else {
    callback(null, generatePolicy(null, claims.sub, 'Deny', event.methodArn))
  }
}

function generatePolicy(metadata, principalId, effect, resource) {
  const authResponse = {
    principalId: principalId,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [
        {
          Action: 'execute-api:Invoke',
          Effect: effect,
          Resource: resource,
        },
      ],
    },
  }
  if (metadata) {
    authResponse.context = metadata
  }
  return authResponse
}
```

> The above code is a modified version of the sample provided in the [AWS docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html).

Now create a zip folder that contains the following files and folders:

- The entire `node_modules` folder.
- `package.json`
- `index.js`

Now we can upload this zip folder into a new AWS Lambda function. Navigate to the Lambda section of AWS and create a new Lambda with the following settings:

- **Name**: ClerkLambdaAuthorizer
- **Runtime**: Node.js 20.x

The rest of the values can be left at their defaults. Scroll to the bottom and click **"Create function"**. After the function is created, scroll down to the **Code source section** and click the **"Upload from"** button, then **".zip file"**. Select the zip file you created and upload it to Lambda.

![The view of AWS Lambda that shows where to upload a .zip file to Lambda](./4-rest-upload-lambda.png)

Now navigate to a REST-type API Gateway instance. Authorizers can be created by selecting the **"Authorizers"** item from the left navigation, then clicking **"Create an authorizer"**.

![A screenshot of the AWS dashboard showing where where to create an authorizer for a REST API](./5-rest-create-authorizer.png)

Give the authorizer a name and select the Lambda function that was created in the previous section.

![The configuration view with in AWS when creating a Lambda authorizer](./6-rest-create-authorizer-view.png)

Scroll down a bit and enter “*Authorization*” in the **"Token source field"** then click **"Create authorizer"**.

![Where the Authorization header is configured for a Lambda authorizer](./7-rest-create-authorizer-view-2.png)

To attach the authorizer to a request, click **"Resources"** in the navigation, select the route and method you want to add the authorizer to, then scroll down and click **"Edit"** in the Method request settings section.

![The AWS UI showing where to edit a route to attach an authorizer](./8-rest-edit-route.png)

Use the dropdown under **"Authorization"** to select the authorizer you just created, then click **"Save"** at the bottom of the screen.

![The AWS UI showing where to attach a Lambda authorizer to a route](./9-rest-attach-authorizer.png)

At this point, the authorizer is configured, however, API Gateway will still deny the request if the Clerk session token hasn't been customized to include the user metadata. To do this, go to the Clerk dashboard and select **"Sessions"** from the left navigation. Then click **"Edit"** in the **Customize session token** and update the Claims text input to match the following:

```json
{
  "metadata": "{{user.public_metadata}}"
}
```

Now every user that has `"role": "admin"` in their public metadata will be allowed to make requests to endpoints secured with the authorizer we created. When this role is set in the public metadata, AWS will follow a flow that matches the original depiction of an authorizer:

![A diagram showing the Lambda authorizer allowing a request after checking a role](./10-diagram-showing-role-allowed.jpg)

1. A request is made to the API Gateway endpoint.
2. The token is sent to the Lambda authorizer first.
3. The Lambda authorizer checks the role of the user.
4. If the role is "admin", the request is allowed to pass through to the backend Lambda function.

Conversely, if the role is not "admin", the request will be denied:

![A diagram showing the Lambda authorizer denying a request after checking a role](./11-diagram-showing-role-denied.jpg)

1. A request is made to the API Gateway endpoint.
2. The token is sent to the Lambda authorizer first.
3. The Lambda authorizer checks the role of the user.
4. If the role is "admin", the request is allowed to pass through to the backend Lambda function.

## Conclusion

Authorizers act as a first line of defense for API Gateway endpoints. Thankfully, Clerk supports this approach to securing your API Gateway with a few simple steps. With the flexibilty of modifying session tokens in the platform, you can easily add additional claims and fine-tune the access control of your API Gateway endpoints.

In this guide, we covered how to use JWT Authorizers to protect endpoints using the public keys of a Clerk instance, as well as Lambda Authorizers for more fine-grained control of who can access your API Gateway endpoints.