# Clerk Blog

# Build a blog with tRPC, Drizzle, Next.js and Clerk
URL: https://clerk.com/blog/build-a-blog-with-trpc-drizzle-nextjs-clerk.md
Date: 2026-06-02
Category: Guides
Description: Learn how to work with tRPC, Drizzle, Next.js, and Clerk by building a secure blog application

In this tutorial, you'll build a blog app from scratch using many modern and popular technologies such as Next.js, Clerk, tRPC, Drizzle, and more. After reading this tutorial, you'll have a simple blog application that allows users to create and read posts.

The tech stack you'll use:

- Next.js App Router
- Clerk (Authentication)
- Drizzle (Database ORM)
- Vercel (Deploying your app and creating your database)
- Neon (Postgres database)
- tRPC (Type-safe API endpoint wrapper)
- Tanstack Query (Data fetching and caching)
- Zod (Schema validation)
- Tailwind (Styling your app)

First, you'll create a Next.js App Router app with Clerk. Then, you'll get your app up and running using Drizzle. To do this, you'll deploy your app to Vercel, where you'll create a Neon database that will be used by Drizzle to access and manipulate data. You can stop here, or you can continue on to add tRPC and zod to your app for enhanced type-safety. You'll set up your tRPC server and create endpoints/procedures for your queries and mutations. Then you'll set up your tRPC client and replace the Drizzle queries and mutations with the tRPC procedures using Tanstack Query. Lastly, you'll learn how to create protected procedures using Clerk's authentication context.

> \[!IMPORTANT]
> Check out the finished product in Clerk's demo repository:
> [https://github.com/clerk/clerk-nextjs-trpc-drizzle](https://github.com/clerk/clerk-nextjs-trpc-drizzle)

> \[!NOTE]
> **Last verified 2026-05-06** with Clerk v7 (Core 3) and Next.js 16 (React 19 + Turbopack). Pinned versions: `next` 16.2.5, `react` / `react-dom` 19.2.6, `typescript` 6.0.3, `@clerk/nextjs` 7.3.1, `drizzle-orm` 0.45.2, `drizzle-kit` 0.31.10, `@neondatabase/serverless` 1.1.0, `@trpc/*` 11.17.0, `@tanstack/react-query` 5.100.9, `zod` 4.4.3, `tailwindcss` / `@tailwindcss/postcss` 4.2.4, `dotenv` 17.2.0.

## Create a Next.js + Clerk app

To create a Next.js app with Clerk, follow the [quickstart in the Clerk Docs](/docs/quickstarts/nextjs).

Or, you can clone the [Clerk repository](https://github.com/clerk/clerk-nextjs-app-quickstart), which is the result of following the quickstart:

```bash
gh repo clone clerk/clerk-nextjs-app-quickstart
```

### Create a Clerk application

The Clerk quickstart gets you started with Clerk in keyless mode, which allows you to try Clerk's authentication features in your app without having to create a Clerk account. Keyless mode only works for local development, so you will want to create a Clerk account and an application in the [Clerk Dashboard](https://dashboard.clerk.com) to deploy your application to Vercel.

The Clerk Dashboard is where you, as the application owner, can manage your application's settings, users, and organizations. For example, if you want to enable phone number authentication, multi-factor authentication, social providers like Google, delete users, or create organizations, you can do all of this and more in the Clerk Dashboard.

### Set your Clerk API keys

You need to set your Clerk API keys in your app so that your app can use the configuration settings that you set in the Clerk Dashboard.

1. In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page.
2. In the **Quick Copy** section, copy your Clerk Publishable and Secret Keys.
3. In your `.env` file, set the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables to the values you copied from the Clerk Dashboard.

```env {{ filename: '.env' }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
CLERK_SECRET_KEY=YOUR_SECRET_KEY
```

### Verify the Clerk middleware (`proxy.ts`)

In Next.js 16, the Clerk middleware lives in `proxy.ts` at the project root (renamed from `middleware.ts` in Next.js 15). The Clerk quickstart creates this file for you; if you scaffolded a Next.js app yourself, create it now:

```ts {{ filename: 'proxy.ts' }}
import { clerkMiddleware } from '@clerk/nextjs/server'

export default clerkMiddleware()

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
  ],
}
```

This file does not protect any routes — it exists so that `auth()` can read the current Clerk session from Server Components, Route Handlers, and (later in this tutorial) the tRPC context. Authorization in this app happens at the tRPC procedure level via `protectedProcedure`, not in the middleware.

## Install dependencies and test your app

While developing, it's best practice to keep your project running so that you can test your changes as you work. So, let's make sure the app is working as expected.

1. Run the following commands to install the dependencies and start the development server:
   ```bash
   npm install
   npm run dev
   ```
2. Open your browser and navigate to the URL displayed in your terminal. The default is `http://localhost:3000` and will be used through the remainder of the tutorial. It should render a new Next.js app, but with a "Sign in" and "Sign up" button in the top right corner.
   ![The development instance running.](./one.png)
3. Select the "Sign in" button. You should be redirected to your Clerk [Account Portal sign-in](/docs/account-portal/overview#sign-in) page, which renders Clerk's [`<SignIn />`](/docs/components/sign-in) component. The `<SignIn />` component will look different depending on the configuration of your Clerk instance.
   ![A Clerk Account Portal sign-in page.](./two.png)
4. Sign in to your Clerk application.
5. You should be redirected back to your app, where you should see Clerk's [`<UserButton />`](/docs/components/user/user-button) component in the top right corner.

## Install Drizzle

Run the following commands to install Drizzle and the `dotenv` package to load environment variables:

```bash
npm install drizzle-orm drizzle-kit dotenv
```

## Install `@neondatabase/serverless`

You'll be using Neon to create your database. Run the following command to install the Neon serverless driver for connecting to your Neon database.

```bash
npm i @neondatabase/serverless
```

## Configure Drizzle

Now you need to configure Drizzle to work with your Neon database, and create a Drizzle configuration file, which essentially tells Drizzle where your database is and how to connect to it.

1. Create a `db` directory in the root of your project.
2. In the `db` directory, create a `drizzle.ts` file with the following code:
   ```ts {{ filename: 'db/drizzle.ts' }}
   import { neon } from '@neondatabase/serverless'
   import { drizzle } from 'drizzle-orm/neon-http'

   if (!process.env.DATABASE_URL) {
     throw new Error('DATABASE_URL is not defined in your .env file')
   }

   const sql = neon(process.env.DATABASE_URL)

   export const db = drizzle({ client: sql })
   ```
3. At the root of your app, create a `drizzle.config.ts` file with the following code:

   ```ts {{ filename: 'drizzle.config.ts' }}
   import 'dotenv/config'
   import { defineConfig } from 'drizzle-kit'

   if (!process.env.DATABASE_URL) {
     throw new Error('DATABASE_URL is not defined in your .env file')
   }

   export default defineConfig({
     schema: './db/schema.ts',
     out: './migrations',
     dialect: 'postgresql',
     dbCredentials: {
       url: process.env.DATABASE_URL,
     },
   })
   ```

   > Drizzle Kit doesn't auto-load `.env` files. The `dotenv/config` import makes the kit commands pick up `DATABASE_URL` automatically — you'll add that value to your `.env` after creating the Neon database in the next steps.

## Deploy to Vercel

> **Vercel + Neon UI:** This walk-through was last verified against the Vercel and Neon dashboards on 2026-05-08. Both dashboards rearrange labels periodically — if a panel name differs, look for the equivalent (e.g., **Storage** is where databases live; **Environment Variables Prefix** is under **Advanced options** when connecting a database).

To make things a little bit easier, you'll be using Vercel to create your Neon database. But before you can do that, you first need to deploy your app to Vercel.

1. Create a repository on GitHub for your app. If you're not sure how to do this, follow the [GitHub docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository).
2. Go to [Vercel](https://vercel.com) and add a new project. While going through the process, select the **Environment Variables** dropdown, and add your Clerk Publishable and Secret Keys.
   ![Vercel dashboard showing where to input environment variables](./three.png)
3. Select **Deploy** to deploy your app to Vercel.
4. Select the **Settings** tab.
5. In the left sidenav, select **Functions**.
6. Under **Function Region**, there should be a tag next to one of the continents. Select the continent where the tag is, and the dropdown will reveal what regions on Vercel's network that your Vercel Functions will execute in. **Take note of the region.** Keep the Vercel dashboard open.
   ![Vercel dashboard with an arrow pointing to a tag that says "iad1", and an arrow pointing to a highlighted element that says "Washington, D.C., USA (EAST) - us-east-1 - iad1"](./four.png)

## Spin up a Neon database

1. While still in Vercel's dashboard, select the **Storage** tab.
2. Select **Create Database**.
3. Select **Neon** as the database provider and select **Continue**.
4. Select the **Region** dropdown and select the region you noted earlier. You want your database's region to match your Vercel Functions region for optimal performance.
5. Select **Continue**.
6. When connecting to the database, select **Advanced options** and under **Environment Variables Prefix**, enter `DATABASE` so that the environment variable is `DATABASE_URL`. Then select **Connect**.
7. The dashboard exposes several connection-string variants (`DATABASE_URL_UNPOOLED`, `PGHOST`, `POSTGRES_*`, etc.) for different drivers and tooling. This app only uses `DATABASE_URL`, so copy that one line into your `.env`:

```env {{ filename: '.env' }}
DATABASE_URL=***
```

## Update your Vercel environment variables

When you add new environment variables to your `.env` file, don't forget to update your Vercel environment variables.

1. In Vercel's dashboard, select the **Settings** tab.
2. In the left sidenav, select **Environment Variables**.
3. Add the new `DATABASE_URL` environment variable to your Vercel environment variables.
4. Select **Save**.

## Create a database model

Now that your database is created and connected to your app, it's time to create a database model. This model will be used to create a table in your database.

In the `db` directory, create a `schema.ts` file with the following code:

```ts {{ filename: 'db/schema.ts' }}
import { pgTable, text, serial } from 'drizzle-orm/pg-core'

export const posts = pgTable('posts', {
  id: serial().primaryKey(),
  title: text().notNull(),
  content: text().notNull(),
  authorId: text('author_id').notNull(),
})
```

> When a column's JS name differs from the SQL convention (camelCase vs. snake\_case here), pass the SQL name explicitly to the column helper. Drizzle otherwise uses the JS property verbatim, producing quoted camelCase columns that don't match standard Postgres conventions.

## Generate and apply your database migration

Run the following command to generate a migration file:

```bash
npx drizzle-kit generate
```

Then, run the following command to apply the migrations to your database:

```bash
npx drizzle-kit migrate
```

> If this command fails with only `Command failed with exit code 1` and no Postgres error, the `@neondatabase/serverless` HTTP driver is swallowing the underlying error. Run the migrator directly through `drizzle-orm/neon-http/migrator` in a small script with `try`/`catch` to surface what actually went wrong.

Learn more about migrations in the [Drizzle docs](https://orm.drizzle.team/docs/migrations).

### Query your database

Now that all of the set up is complete, it's time to start building out your app!

Let's start with your homepage. Replace the contents of `app/page.tsx` with the following code:

```tsx {{ filename: 'app/page.tsx' }}
import Link from 'next/link'
import { posts as postsTable } from '@/db/schema'
import { db } from '@/db/drizzle'

export default async function Page() {
  // Use drizzle to query the database for all posts
  const posts = await db.select().from(postsTable)

  // Display the posts on the homepage
  return (
    <div className="-mt-16 flex min-h-screen flex-col items-center justify-center">
      <h1 className="mb-8 text-4xl font-bold">Posts</h1>
      {posts.length === 0 && <div>No posts found</div>}
      {posts.length > 0 && (
        <div className="mb-8 flex max-w-2xl flex-col space-y-4">
          {posts.map((post) => (
            <Link
              key={post.id}
              href={`/posts/${post.id}`}
              className="inline-block rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
            >
              <span className="text-lg font-semibold">{post.title}</span>
              <span className="text-sm">by {post.authorId}</span>
            </Link>
          ))}
        </div>
      )}
      <Link
        href="/posts/create"
        className="inline-block rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
      >
        Create New Post
      </Link>
    </div>
  )
}
```

This code fetches all posts from your database and displays them on the homepage, showing the title and author ID for each post. It uses Drizzle's [`select()`](https://orm.drizzle.team/docs/select#basic-select) method to select all rows from the `posts` table.

That shows how to query for all records, but how do you query for a single record?

### Query a single record

Let's add a page that displays a single post. This code uses the URL parameters to get the post's ID, and then fetches it from your database and displays it on the page, showing the title, author ID, and content. It uses the following methods from Drizzle:

- [`select()`](https://orm.drizzle.team/docs/select#basic-select) to select a single row from the `posts` table.
- [`where()`](https://orm.drizzle.team/docs/select#filters) to filter the query results.
  - [`eq()` filter operator](https://orm.drizzle.team/docs/operators#eq) to check if the first argument is equal to the second argument, which in this case compares the ID of the post to the ID in the URL.

Create the `app/posts/[id]/page.tsx` file and paste the following code:

```tsx {{ filename: 'app/posts/[id]/page.tsx' }}
import { eq } from 'drizzle-orm'
import { posts as postsTable } from '@/db/schema'
import { db } from '@/db/drizzle'

export default async function Post({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  // Use Drizzle to query the database for the post with an ID that matches the ID in the URL
  const response = await db
    .select()
    .from(postsTable)
    .where(eq(postsTable.id, parseInt(id)))
  const post = response[0]

  return (
    <div className="mx-auto mt-8 flex min-h-screen max-w-2xl flex-col">
      {!post && <div>No post found.</div>}
      {post && (
        <article className="w-full max-w-2xl">
          <h1 className="mb-2 text-2xl font-bold sm:text-3xl md:text-4xl">{post.title}</h1>
          <p className="text-sm sm:text-base">by {post.authorId}</p>
          <div className="prose prose-gray prose-sm sm:prose-base lg:prose-lg mt-4 sm:mt-8">
            {post.content || 'No content available.'}
          </div>
        </article>
      )}
    </div>
  )
}
```

Test the page by navigating to a post's URL. For example, `http://localhost:3000/posts/1`. For now, it should show a "No post found" message because you haven't created any posts yet. Let's add a way to create posts.

### Create a new post

Next, you'll add a page that allows users to create new posts. This page uses Clerk's [`auth()`](/docs/references/nextjs/auth) helper to get the user's ID. It is a helper that is specific to Next.js App Router, and it provides authentication information on the server side.

- If there is no user ID, the user is not signed in, so a sign in button is displayed.
- If the user is signed in, the "Create New Post" form is displayed. When the form is submitted, the `createPost()` function is called. This function creates a new post in the database using the [`db.insert()`](https://orm.drizzle.team/docs/insert) method, which is a Drizzle method that inserts a new row into a table.

Create the `app/posts/create/page.tsx` file and paste the following code:

```tsx {{ filename: 'app/posts/create/page.tsx' }}
import { redirect } from 'next/navigation'
import { SignInButton } from '@clerk/nextjs'
import { auth } from '@clerk/nextjs/server'
import { posts as postsTable } from '@/db/schema'
import { db } from '@/db/drizzle'

export default async function Page() {
  // Use Clerk's `auth()` hook to get the user's ID
  const { userId } = await auth()

  // Protect this page from unauthenticated users
  if (!userId) {
    return (
      <div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center space-y-4">
        <p>You must be signed in to create a post.</p>
        <SignInButton>
          <button
            type="submit"
            className="inline-block cursor-pointer rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
          >
            Sign in
          </button>
        </SignInButton>
      </div>
    )
  }

  // Handle form submission
  async function createPost(formData: FormData) {
    'use server'

    const title = formData.get('title') as string
    const content = formData.get('content') as string
    const authorId = formData.get('authorId') as string

    if (!title || !content || !authorId) {
      throw new Error('Missing required field')
    }

    await db.insert(postsTable).values({
      title,
      content,
      authorId,
    })

    redirect('/')
  }

  return (
    <div className="mx-auto max-w-2xl p-4">
      <h1 className="mb-6 text-2xl font-bold">Create New Post</h1>
      <form action={createPost} className="space-y-6">
        <input type="hidden" name="authorId" value={userId} />
        <div>
          <label htmlFor="title" className="mb-2 block text-lg">
            Title
          </label>
          <input
            type="text"
            id="title"
            name="title"
            required
            placeholder="Enter your post title"
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <div>
          <label htmlFor="content" className="mb-2 block text-lg">
            Content
          </label>
          <textarea
            id="content"
            name="content"
            required
            placeholder="Write your post content here..."
            rows={6}
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <button
          type="submit"
          className="inline-block w-full rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
        >
          Create Post
        </button>
      </form>
    </div>
  )
}
```

Test the page by navigating to `http://localhost:3000/posts/create` and creating a new post. You should be redirected to the homepage, where you should see the new post.

## Install tRPC, `@tanstack/react-query`, and `zod`

Now, you've got a Next.js, Clerk, and Drizzle app that can create and display posts. You could stop here and have a perfectly functional app that functions entirely server-side. But let's take it a step further and add tRPC to your app for type-safe API endpoints.

- tRPC is a wrapper around your API endpoints to make them type-safe and easier to use.
- `zod` is a schema validation library, also used to enhance your app's type safety.
- `@tanstack/react-query` is a library for data fetching and caching.

Run the following command to install tRPC, `@tanstack/react-query`, and `zod`:

```bash
npm i @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod
```

## Create a tRPC server

Now, you'll configure tRPC for your app. You'll start by [initializing a tRPC server](https://trpc.io/docs/server/routers#initialize-trpc) that creates a `router` and `publicProcedure` that you can use to create your API endpoints.

Create the `app/server/trpc.ts` file and paste the following code:

```ts {{ filename: 'app/server/trpc.ts' }}
import { initTRPC } from '@trpc/server'

const t = initTRPC.create()

export const router = t.router
export const publicProcedure = t.procedure
```

### Create a tRPC endpoint

Now, you'll create a router that's going to have your procedures on it. This code creates a router with a `getPosts` procedure that uses the tRPC `publicProcedure` you created in the previous step to make a query using [tRPC's `query()` method](https://trpc.io/docs/server/procedures). The query then uses Drizzle to query the `posts` table in your database. That part should look familiar, because you've used this same pattern in your app earlier!

Create the `app/server/routers/posts.ts` file and paste the following code:

```ts {{ filename: 'app/server/routers/posts.ts' }}
import { publicProcedure, router } from '../trpc'
import { db } from '@/db/drizzle'
import { posts as postsTable } from '@/db/schema'

export const postRouter = router({
  getPosts: publicProcedure.query(async () => {
    return await db.select().from(postsTable)
  }),
})

export type PostRouter = typeof postRouter
```

This is the file where you'll add all of your queries and mutations, so you'll probably update this file frequently as you build out your app.

### Connect the tRPC router to your App Router

Now you need to connect the tRPC router to your App Router. You'll use a Route Handler that uses [tRPC's `fetchRequestHandler()` method](https://trpc.io/docs/server/adapters/fetch#nextjs-edge-runtime) to pass requests from Next.js to the tRPC router.

1. In `app/`, create an `api` directory.
2. In `app/api/`, create a `trpc` directory.
3. In `app/api/trpc/`, create a `[trpc]` directory. This will capture whatever the user requests from the tRPC router, such as `getPosts`, and set it as one of the route parameters.
4. In `app/api/trpc/[trpc]`, create a `route.ts` file, which will be the route handler for your tRPC routers.
5. In `route.ts`, paste the following code:

```ts {{ filename: 'app/api/trpc/[trpc]/route.ts' }}
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { postRouter } from '@/app/server/routers/posts'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: postRouter,
    createContext: () => ({}),
  })

export { handler as GET, handler as POST }
```

At this point, your API endpoint should be working. You can test it by navigating to `http://localhost:3000/api/trpc/getPosts`. You should see a JSON response with the posts from your database.

## Create a tRPC client

So far, your app is entirely server-side and static. You need a way to mutate data, which is where `@tanstack/react-query` comes in. But to use tRPC with `@tanstack/react-query`, you need to create a tRPC client.

Create the `app/_trpc/client.ts` file and paste the following code:

```ts {{ filename: 'app/_trpc/client.ts' }}
'use client'

import { createTRPCReact } from '@trpc/react-query'

import type { PostRouter } from '@/app/server/routers/posts'

export const trpc = createTRPCReact<PostRouter>({})
```

> **Alternative:** tRPC v11 also ships `@trpc/tanstack-react-query`'s `createTRPCContext` + `useTRPC()` pattern, which returns query options factories and is the project's currently recommended path. This tutorial sticks with `createTRPCReact` to keep the setup compact, but either works.

## Create a Tanstack Query + tRPC provider

To use Tanstack Query and tRPC together, you need to create a provider that provides both the Tanstack Query client and the tRPC client to your app. This provider will make both the Tanstack Query client and the tRPC client available to your app, using the `<trpc.Provider>` and `<QueryClientProvider>` components.

Create the `app/_trpc/Provider.tsx` file and paste the following code:

```tsx {{ filename: 'app/_trpc/Provider.tsx' }}
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import React, { useState } from 'react'

import { trpc } from './client'

export default function Provider({ children }: { children: React.ReactNode }) {
  // Create a Tanstack Query client
  const [queryClient] = useState(() => new QueryClient({}))
  // Create a tRPC client
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: '/api/trpc',
        }),
      ],
    }),
  )
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </trpc.Provider>
  )
}
```

Now, wrap your app in the provider. Update the main layout to import the provider as `TRPCProvider` and wrap your app in it. It's very important that `<ClerkProvider>` is wrapped around `<TRPCProvider>`, and not the other way around, because the `<TRPCProvider>` needs to have access to the Clerk authentication context.

```tsx {{ filename: 'app/layout.tsx' }}
import type { Metadata } from 'next'
import { ClerkProvider, SignInButton, SignUpButton, UserButton, Show } from '@clerk/nextjs'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import TRPCProvider from '@/app/_trpc/Provider'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

export const metadata: Metadata = {
  title: 'Clerk Next.js Quickstart',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <TRPCProvider>
        <html lang="en">
          <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
            <header className="flex h-16 items-center justify-end gap-4 p-4">
              <Show when="signed-out">
                <SignInButton />
                <SignUpButton />
              </Show>
              <Show when="signed-in">
                <UserButton />
              </Show>
            </header>
            {children}
          </body>
        </html>
      </TRPCProvider>
    </ClerkProvider>
  )
}
```

> In Clerk v7 (Core 3), `<Show>` is the unified conditional component for rendering based on auth state. The older `<SignedIn>` and `<SignedOut>` wrappers still work for back-compat, but `<Show when="signed-in">` / `<Show when="signed-out">` is the recommended pattern.

## Use the tRPC client to fetch and mutate data

Now, you can use the `trpc` client to fetch and mutate data in your app! Let's update the functionality of your app to use the `trpc` client.

Let's start by updating the homepage where the list of posts is displayed. Since the page is still rendered server-side, you'll create a client component that uses the `trpc` client to fetch the posts.

Create the `app/components/Posts.tsx` file and paste the following code:

```tsx {{ filename: 'app/components/Posts.tsx' }}
'use client'

import Link from 'next/link'
import { trpc } from '../_trpc/client'

export default function Posts() {
  // Use the `getPosts` query from the TRPC client
  const getPosts = trpc.getPosts.useQuery()
  const { isLoading, data: posts } = getPosts

  return (
    <div className="mb-8 flex max-w-2xl flex-col space-y-4">
      {isLoading && <div>Loading...</div>}
      {!isLoading && posts?.length === 0 && <div>No posts found</div>}
      {!isLoading &&
        posts?.map((post) => (
          <Link
            key={post.id}
            href={`/posts/${post.id}`}
            className="flex flex-col rounded-lg px-2 py-4 transition-all hover:bg-neutral-100 hover:underline dark:hover:bg-neutral-800"
          >
            <span className="text-lg font-semibold">{post.title}</span>
            <span className="text-sm">by {post.authorId}</span>
          </Link>
        ))}
    </div>
  )
}
```

Then, update the homepage (`app/page.tsx`) to use the `<Posts />` component:

```tsx {{ filename: 'app/page.tsx' }}
import Link from 'next/link'
import Posts from './components/Posts'

export default function Page() {
  return (
    <div className="-mt-16 flex min-h-screen flex-col items-center justify-center">
      <h1 className="mb-8 text-4xl font-bold">Posts</h1>
      <Posts />
      <Link
        href="/posts/create"
        className="inline-block rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
      >
        Create New Post
      </Link>
    </div>
  )
}
```

Notice that the `db.select().from(postsTable)` function is removed from the homepage file. Instead, `trpc.getPosts.useQuery()` is used to fetch the posts, because remember, you created a tRPC `postRouter` with a `getPosts` procedure that uses `db.select().from(postsTable)`. So now, you don't need to use Drizzle directly; instead, you can use the tRPC `getPosts` procedure and Tanstack Query's `useQuery()` hook in order to have type safety, a better developer experience, and a more performant app.

Why couldn't `trpc.getPosts.useQuery()` get called in the homepage file? Hooks, like `useQuery()`, have to be used in a Client Component, and the homepage is a Server Component. To keep the homepage as a Server Component, this logic is moved to the `<Posts />` component, which is made a Client Component.

Also, because tRPC is using Tanstack Query to fetch the data, the query result includes not only the data, but also other states, such as loading and error states. You can learn more about in the [Tanstack Query docs](https://tanstack.com/query/v4/docs/framework/react/guides/queries#:~:text=throughout%20your%20application.-,The%20query%20result,-returned%20by%20useQuery).

> In TanStack Query v5, prefer `isPending` for initial-load checks. We use `isLoading` here because it behaves the same way on a query with no `initialData`.

Before we update the rest of your app to use tRPC and Tanstack Query, let's test and make sure the new logic is working. Navigate to the homepage and make sure you can see the posts. Once you've verified everything's working, go back to your `postRouter` and let's create more procedures to handle your other queries.

### Use tRPC to fetch a single post

In `app/server/routers/posts.ts`, update the code to add a `getPost` procedure to fetch a single post by ID:

```ts {{ filename: 'app/server/routers/posts.ts' }}
import { publicProcedure, router } from '../trpc'
import { db } from '@/db/drizzle'
import { eq } from 'drizzle-orm'
import { z } from 'zod'
import { posts as postsTable } from '@/db/schema'

export const postRouter = router({
  getPosts: publicProcedure.query(async () => {
    return await db.select().from(postsTable)
  }),
  getPost: publicProcedure
    .input(z.object({ id: z.coerce.number().int() }))
    .query(async ({ input }) => {
      const posts = await db.select().from(postsTable).where(eq(postsTable.id, input.id))
      return posts[0]
    }),
})

export type PostRouter = typeof postRouter
```

> The input uses Zod 4's `z.coerce.number().int()` to validate the URL param as an integer at the schema boundary. The URL string passes through coercion automatically, so no client change is needed. This replaces the older `z.string()` + `parseInt(input.id)` pattern, which would let `NaN` reach Postgres on bad input.

Then, update `app/posts/[id]/page.tsx` to use the `getPost` procedure by pasting the following code:

```tsx {{ filename: 'app/posts/[id]/page.tsx' }}
'use client'

import { trpc } from '@/app/_trpc/client'
import { use } from 'react'

export default function Post({ params }: { params: Promise<{ id: string }> }) {
  // Params are wrapped in a promise, so we need to unwrap them using React's `use()` hook
  const { id } = use(params)
  // Use the `getPost` query from the TRPC client
  const { data: post, isLoading } = trpc.getPost.useQuery({ id })

  return (
    <div className="mx-auto mt-8 flex min-h-screen max-w-2xl flex-col">
      {isLoading && <p>Loading...</p>}
      {!isLoading && !post && <p>No post found.</p>}
      {!isLoading && post && (
        <article className="w-full max-w-2xl">
          <h1 className="mb-2 text-2xl font-bold sm:text-3xl md:text-4xl">{post.title}</h1>
          <p className="text-sm sm:text-base">by {post.authorId}</p>
          <div className="prose prose-gray prose-sm sm:prose-base lg:prose-lg mt-4 sm:mt-8">
            {post.content || 'No content available.'}
          </div>
        </article>
      )}
    </div>
  )
}
```

This replaces `db.select().from().where(eq())` with `trpc.getPost.useQuery()`. It also replaces how you get the post ID from `params`. `params` are wrapped in a promise. Before, `await` was used to handle `params`, but because Client Components cannot be async, it was replaced with React's `use()` hook.

And before you go any further, test to make sure the new logic is working. Navigate to a post's URL, such as `http://localhost:3000/posts/1`, and make sure you can see the post.

If that's working, go back to your `postRouter` and let's add the last procedure you need to handle your create post functionality.

### Use tRPC to create a new post

In `app/server/routers/posts.ts`, add the following code:

```ts {{ filename: 'app/server/routers/posts.ts' }}
import { publicProcedure, router } from '../trpc'
import { db } from '@/db/drizzle'
import { eq } from 'drizzle-orm'
import { z } from 'zod'
import { posts as postsTable } from '@/db/schema'

const postSchema = z.object({
  title: z.string(),
  content: z.string(),
  authorId: z.string(),
})

export const postRouter = router({
  getPosts: publicProcedure.query(async () => {
    return await db.select().from(postsTable)
  }),
  getPost: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
    const posts = await db
      .select()
      .from(postsTable)
      .where(eq(postsTable.id, parseInt(input.id)))
    return posts[0]
  }),
  // Protected procedure that requires a user to be signed in
  createPosts: publicProcedure.input(postSchema).mutation(async ({ input }) => {
    return await db.insert(postsTable).values({
      title: input.title,
      content: input.content,
      authorId: input.authorId,
    })
  }),
})

export type PostRouter = typeof postRouter
```

This adds a `createPosts` procedure that creates a new post, and a `postSchema` that uses zod to validate the input.

Update `app/posts/create/page.tsx` to use this new procedure by pasting the following code:

```tsx {{ filename: 'app/posts/create/page.tsx' }}
'use client'

import { useRouter } from 'next/navigation'
import { SignInButton, useAuth } from '@clerk/nextjs'
import { trpc } from '@/app/_trpc/client'
import { useState } from 'react'

export default function NewPost() {
  const router = useRouter()
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')
  // Use Clerk's `useAuth()` hook to get the user's ID
  const { userId, isLoaded } = useAuth()
  // Use the `createPosts` mutation from the TRPC client
  const createPostMutation = trpc.createPosts.useMutation()

  // Check if Clerk is loaded
  if (!isLoaded) {
    return (
      <div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center space-y-4">
        <div>Loading...</div>
      </div>
    )
  }

  // Protect this page from unauthenticated users
  if (!userId) {
    return (
      <div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center space-y-4">
        <p>You must be signed in to create a post.</p>
        <SignInButton>
          <button
            type="submit"
            className="inline-block cursor-pointer rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
          >
            Sign in
          </button>
        </SignInButton>
      </div>
    )
  }

  // Handle form submission
  async function createPost(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    await createPostMutation.mutateAsync({
      title,
      content,
      authorId: userId as string,
    })
    router.push('/')
  }
  return (
    <div className="mx-auto max-w-2xl p-4">
      <h1 className="mb-6 text-2xl font-bold">Create New Post</h1>
      <form onSubmit={createPost} className="space-y-6">
        <div>
          <label htmlFor="title" className="mb-2 block text-lg">
            Title
          </label>
          <input
            type="text"
            id="title"
            name="title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Enter your post title"
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <div>
          <label htmlFor="content" className="mb-2 block text-lg">
            Content
          </label>
          <textarea
            id="content"
            name="content"
            value={content}
            onChange={(e) => setContent(e.target.value)}
            placeholder="Write your post content here..."
            rows={6}
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <button
          type="submit"
          className="inline-block w-full rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
        >
          Create Post
        </button>
      </form>
    </div>
  )
}
```

This updates a few things. First, it turns this page into a Client Component, because Tanstack Query and the tRPC client are client-side. So now, the Server Action that you created before can no longer be used. Instead, the form data is handled using state. When the form is submitted, the `createPost()` function no longer uses `db.insert()` explicitly, but instead uses `trpc.createPosts.useMutation()` from the tRPC client. Also, because the page is now a Client Component, Clerk's `auth()` helper no longer works, so it's replaced with Clerk's `useAuth()` hook. This introduces the benefit of having access to Clerk's loading state, so a loading UI is added.

Note the use of `await createPostMutation.mutateAsync(...)` followed by `router.push('/')` from `useRouter()`. The fire-and-forget `mutate(...)` would let the redirect run before the row is committed, so the homepage could render without the new post on first load. And `redirect()` from `next/navigation` is a server-context helper — calling it from a client event handler is unsupported. `mutateAsync` returns a promise we can `await`, and `useRouter().push('/')` is the conventional client-side navigation primitive.

And don't forget, test your changes. Navigate to `http://localhost:3000/posts/create` and make sure you can create a new post.

Once you've confirmed everything's working, you're almost done...

## Create protected procedures

In many applications, it's essential to restrict access to certain routes based on user authentication status. This ensures that sensitive data and functionality are only accessible to authorized users.

The benefit of using Clerk with tRPC is that you can create protected procedures using Clerk's authentication context. Clerk's [`Auth`](/docs/references/backend/types/auth-object) object includes important authentication information like the current user's session ID, user ID, and organization ID. It also contains methods to check for the current user's permissions and to retrieve their session token. You can use the `Auth` object to access the user's authentication information in your tRPC queries.

### Create the tRPC context

Create the `app/server/context.ts` file and paste the following code:

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

export const createContext = async () => {
  return { auth: await auth() }
}

export type Context = Awaited<ReturnType<typeof createContext>>
```

This creates a context that will be used to create the context for every tRPC query sent to the server. The context will use the [`auth()`](/docs/references/nextjs/auth) helper from Clerk to access the user's `Auth` object.

### Pass the context to the tRPC server

Then, in your tRPC server (`app/api/trpc/[trpc]/route.ts`), pass the context:

```ts {{ filename: 'app/api/trpc/[trpc]/route.ts' }}
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { postRouter } from '@/app/server/routers/posts'
import { createContext } from '@/app/server/context'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: postRouter,
    createContext,
  })

export { handler as GET, handler as POST }
```

### Access the context data in your procedures

The tRPC context, or `ctx`, should now have access to the Clerk `Auth` object.

In your `server/trpc.ts` file, create a protected procedure:

```ts {{ filename: 'app/server/trpc.ts' }}
import { initTRPC, TRPCError } from '@trpc/server'
import { Context } from './context'

const t = initTRPC.context<Context>().create()

// Check if the user is signed in
// Otherwise, throw an UNAUTHORIZED code
const isAuthed = t.middleware(({ next, ctx }) => {
  if (!ctx.auth.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED' })
  }
  return next({
    ctx: {
      auth: ctx.auth,
    },
  })
})

export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(isAuthed)
```

### Use your protected procedure

Once you have created your procedure, you can use it in any router. In this case, you don't want unauthenticated users to be able to create posts, so let's update the `createPosts` mutation to be protected by swapping the `publicProcedure` with the `protectedProcedure`:

```ts {{ filename: 'app/server/routers/posts.ts' }}
import { protectedProcedure, publicProcedure, router } from '../trpc'
// ...The rest of your code...

const postSchema = z.object({
  title: z.string(),
  content: z.string(),
})

export const postRouter = router({
  // ...The rest of your code...

  // Protected procedure that requires a user to be signed in
  createPosts: protectedProcedure.input(postSchema).mutation(async ({ input, ctx }) => {
    return await db.insert(postsTable).values({
      title: input.title,
      content: input.content,
      authorId: ctx.auth.userId,
    })
  }),
})

export type PostRouter = typeof postRouter
```

Notice that `authorId` is no longer in the input schema. A protected procedure guarantees that `ctx.auth.userId` exists, so the user ID is read from the server-side Clerk context instead of from the client — a client-supplied `authorId` would be untrusted and could be spoofed to impersonate another user.

Update the client-side create page (`app/posts/create/page.tsx`) to drop `authorId` from the `mutateAsync(...)` payload at the same time:

```tsx {{ filename: 'app/posts/create/page.tsx' }}
await createPostMutation.mutateAsync({ title, content })
```

`useAuth()` is still used on the page to gate rendering (loading state + signed-in check), but the user ID never crosses the wire.

## Finished!

At this point, you've got a fully functional app for creating and displaying posts. You can now add more features to your app, such as updating and deleting posts, adding comments, storing more author information from the Clerk [`User`](/docs/references/javascript/user) object, and more.

Before shipping this app to production, you'll likely want a custom in-app sign-in/sign-up page instead of the hosted Account Portal flow we relied on here. Clerk's [Build a custom sign-in-or-up page](/docs/references/nextjs/custom-signup-signin-pages) guide walks through creating `/sign-in/[[...sign-in]]/page.tsx`, the matching sign-up route, and the `NEXT_PUBLIC_CLERK_*` env vars that wire them up.

---

# Going to production with Clerk Deploy
URL: https://clerk.com/blog/clerk-deploy.md
Date: 2026-05-29
Category: Engineering
Description: Take a Clerk app to production with a single, resumable command, all from your terminal.

Your app's at a point where it's almost ready to go live.

Users can sign up, sign in, and manage their accounts. Organization owners can create workspaces and invite members. Billing works as expected; your plans are set up, and your users can subscribe to them. It feels *shippable*.

Not yet, though.

You still need to set up a production instance, which involves a few steps spread across different pages and external services. The dashboard handles this well, and on a focused day, the whole thing can be done in under 30 minutes.

But what if the final stretch from 'almost ready' to 'ready' had fewer clicks and less context switching?

Our new [CLI](/docs/cli) brings that flow directly to your terminal.

Meet `clerk deploy`.

## A different kind of deploy

`clerk deploy` doesn't ship code. Your hosting platform still takes care of that. Instead, it's a helpful wizard that walks you through everything Clerk needs to take your app to production.

In an interactive terminal session, you get the wizard. Without one (or with the `--mode agent` flag), `clerk deploy` instead returns a JSON snapshot of your deployment state so coding agents can check progress and suggest the next step. For agents, this command is purely informational and doesn't perform any changes.

If you need automated deployments for CI/CD pipelines or agent workflows, you can compose the same flow from a handful of lower-level Clerk commands. Stay tuned for a follow-up post.

Now let's see what the wizard can do.

## How it works

The wizard runs from your project root.

```bash {{ prompt: '$' }}
clerk deploy
```

It starts with a pre-flight pass over your linked app, confirming the app is ready to deploy, identifying which OAuth providers need production credentials, and checking for any deployment already in progress.

![A clerk deploy session showing the pre-flight checks, plan, and production domain prompt](./deploy-example.png)

On the first run, the wizard walks through the steps below. If a production instance already exists but isn't fully configured, it picks up from the next pending step.

### Production domain

The wizard begins by asking for your production domain. Confirming it creates a fresh production instance in your Clerk account and registers the domain to it.

The deploy is resumable from this point on.

### CNAME records

Next is DNS. The wizard prints the CNAME records to add at your DNS provider. Propagation runs in the background while OAuth setup continues, with verification happening at the end.

### OAuth credentials

For each provider detected, the wizard shows the exact redirect URI to register, then prompts for the Client ID and Client Secret. Google can also load OAuth credentials from a JSON file. Apple reads the `.p8` private key from a file path.

### Verification

After OAuth, the wizard verifies DNS, SSL, and email DNS together. If propagation is still in progress, `clerk deploy status` shows where each one stands, plus OAuth provider readiness and the next step to take. Rerunning `clerk deploy` resumes verification.

### Production summary

Once verification clears, a production summary prints with your live URL and production instance ID.

## Ready to ship?

With `clerk deploy`, the CLI now covers Clerk's full arc from development to production. And while the dashboard is always there when you need it, that's one less side-quest pulling you out of flow. The gap between 'almost ready' and 'live' just got smaller.

So this is your nudge. If your app's been on localhost too long, what's stopping you from going to production? Make today the day.

Open a terminal and give it a try:

```bash {{ prompt: '$' }}
clerk deploy
```

Happy shipping!

[Read the docs](/docs/cli)

---

# Clerk Init: The fastest way to start a new project
URL: https://clerk.com/blog/clerk-init.md
Date: 2026-05-11
Category: Engineering
Description: Bootstrap a new project in seconds using Clerk CLI`s `init` command

Clerk's been quietly building toward a moment where you can start a new app in a single command. That command is `clerk init`.

Shipped with the [Clerk CLI](/blog/introducing-clerk-cli), `clerk init` covers the essential scaffolding for some of the most popular frameworks. Run this command in an empty directory and Clerk delivers a fully bootstrapped project with auth pages and route protection ready to go.

This post demonstrates how quick and easy it is to get a fully working app with auth, organizations, and billing configured - all without leaving your terminal or agent.

Break out your terminal and follow along.

## Starting from zero

From an empty directory, run:

```bash {{ prompt: '$' }}
clerk init
```

Follow the interactive prompts for project name, framework, and package manager. Choose "Next.js" and Bun for this example.

Clerk scaffolds a new Next.js project with batteries included. Before `init` completes, the CLI offers to install [agent skills](https://github.com/clerk/skills) like `clerk`, `clerk-setup`, and `clerk-nextjs-patterns`. You accept, pick your agent, and select "Project" for the installation scope.

A few seconds later, the terminal prints the next steps:

```bash {{ prompt: '$' }}
cd my-clerk-next-app && bun dev
```

`localhost` loads. There's a sign-in page. There's a sign-up page. They just work. No Clerk account created, no API keys copied, no dashboard opened. Clerk development keys are generated on-the-fly so you can start building right away.

## Going beyond the bootstrap

Building a SaaS product with Clerk means tapping into some of our other key products like [Organizations](/docs/guides/organizations/overview) and [Billing](/docs/nextjs/guides/billing/for-b2b). The bootstrapped app gets you started, but you'll want to link your new project to your account for greater flexibility and control. Once your project is linked, you can configure your instance straight from the terminal.

Run `clerk link` from the project root:

```bash {{ prompt: '$' }}
clerk link
```

If you're not already authenticated, the CLI opens a browser tab for sign-in. From there, `clerk link` claims the app you've been building.

Next, pull the credentials from the dashboard into your local environment:

```bash {{ prompt: '$' }}
clerk env pull
```

> \[!WARNING]
> `clerk env pull` writes credentials into the env file Clerk picks based on your framework's conventions (for Next.js, `.env.local`). Any existing Clerk publishable or secret keys in that file will be overwritten. If you've already populated the file with your own keys for other purposes, those will be safely preserved.

Restart the dev server (if it doesn't auto-refresh), and you're connected to your Clerk instance.

## Enabling features

This used to be the part where you'd context-switch in and out of the dashboard to turn features on. With your project linked, that same workflow gets reduced to a single step: `clerk enable`.

Start with Organizations:

```bash {{ prompt: '$' }}
clerk enable orgs
```

Done. Organizations are now enabled. You didn't leave your terminal.

Want to get paid? Turn on Billing:

```bash {{ prompt: '$' }}
clerk enable billing --for orgs
```

The CLI offers to install the `clerk-billing` skill. You accept, and now your favorite coding agent just got more superpowers.

`clerk enable` is designed for incremental opt-in to Clerk's core services. Each feature you turn on ships with a matching skill, and that pairing wasn't an afterthought, it was the point. Enabling a feature on your instance should also enable your agent's understanding of how to use it.

Organizations and Billing are the start. Expect more features to land behind `clerk enable` over time.

## Pairing with your agent

Stacked on the `init` skills, the `clerk-billing` skill rounds out your agent's Clerk context. One prompt:

> "Add three subscription tiers to my clerk app: Free, Pro, and Enterprise."

Or:

> "Add a /pricing page where users can compare plans and subscribe."

Seconds later, your app has plans configured, a pricing table on the page, and a working checkout your customers can use.

If you're using shadcn/ui, you can now prompt the agent to build out your dashboard:

> "Build a /dashboard layout using a collapsible shadcn sidebar with an inset. The sidebar header should include clerk's org switcher and the footer should have the user button."

It scaffolds the layout, then drops in the right Clerk components for account management and organization switching. You follow up:

> "Add a /developers page where users can create and manage api keys using clerk's api keys component."

Done. Want to add a new login provider? No problem:

> "Add GitHub as a login option for my app."

The agent calls the CLI, runs `clerk config patch` and enables the GitHub provider on your instance.

This is the moment the rhythm starts to emerge. When the agent has the right tools and reference, the friction between what you ask for and what comes back keeps shrinking. There's still tuning - there always will be -  but the corrections get smaller, and the loop starts moving with you instead of against you.

## Adopting Clerk mid-project

Not every project starts with Clerk; `init` is built for those too. Run it inside an existing codebase and Clerk adapts. It detects your setup, installs the SDK, and scaffolds only what's missing. If you're migrating from NextAuth, Auth0, or Supabase Auth, the CLI detects that and links you to the relevant migration guide.

If you already have middleware, Clerk composes with it rather than overwriting it. After scaffolding, it scans for hardcoded keys and leftover imports from previous auth libraries, so you know exactly what to clean up.

That flexibility is the real benefit of the init workflow. It meets a project at whatever stage it's in.

## The sky's the limit

`clerk init`. `clerk link`. `clerk enable`. Three commands lay the foundation. Agent skills take you further so you can ship faster. Some of the most tedious parts of setting up a SaaS application? Gone. Now you can focus on the parts that matter.

This is how getting started should feel.

[Get started with Clerk CLI](/docs/cli)

---

# Introducing Clerk CLI
URL: https://clerk.com/blog/introducing-clerk-cli.md
Date: 2026-04-22
Category: Company
Description: The Clerk CLI gives your coding agents auth superpowers

As agentic workflows become central to how software gets built and shipped, developers are spending more time inside their terminals and IDEs. Setting up Auth, Organizations, and Billing should be no exception.

Today, as an accompaniment to the Clerk Dashboard, we're introducing the [Clerk CLI](/docs/cli), a command-line tool that brings all of your Clerk workflows into your terminal.

## Bootstrap new projects faster

```bash {{ prompt: '$' }}
clerk init
```

`clerk init` reduces that workflow into a single command.

Run the init command in an empty directory and Clerk creates the project for you — pick your framework, your package manager, name your app, and you have a running application with sign-in, sign-up, and route protection already in place.

Run the init command inside a directory with an existing project and Clerk adapts to what's already there - detecting your framework and package manager and implementing authentication. If you're migrating from NextAuth, Auth0, or Supabase Auth, the CLI detects it and links you to the relevant migration guide.

Eight frameworks are supported today: Next.js, React Router, Astro, Nuxt, TanStack Start, React, Vue, and JavaScript.

## Manage your instance from the terminal

Once your project is up and running, you'll inevitably want to customize the experience by updating your Clerk settings. Enabling [Organizations](/organizations) for B2B, turning on [Billing](/billing) so you can charge your users seamlessly, or requiring MFA for additional security is all now possible from your terminal or IDE.

```bash {{ prompt: '$' }}
clerk config pull
clerk config patch --json '{"session": {"lifetime": 604800}}'
```

For example, `clerk config` lets you pull your instance configuration as JSON, inspect the schema, and push changes with a diff preview before anything is applied. The `--dry-run` flag shows you exactly what would change before committing.

Note, not all settings are available through CLI commands at the time of our first release but for any not yet supported, `clerk open` gets you to the right Dashboard page.

```bash {{ prompt: '$' }}
clerk open webhooks
clerk open # opens the dashboard for your linked app
```

## Talk to the Clerk API directly

The Dashboard is still a great command center for managing your applications but sometimes you need to view or manage resources directly alongside your agents. Fetch all resources within the [Clerk Backend API](/bapi) by using the `clerk api` command.

```bash {{ prompt: '$' }}
clerk api ls
clerk api /users
clerk api /invitations -X POST -d '{"email_address": "john@example.com"}'
```

The CLI includes a lightweight client for the Clerk Backend API. It resolves your secret key from the linked project, so you can perform authenticated requests. Run `clerk api ls` to discover available endpoints, or run `clerk api` with no arguments for an interactive request builder that lets you browse endpoints and compose requests.

## Built for your agents

We built the CLI painstakingly with agents in mind. Whether its allowing token efficient context or reducing round trips by studying common agent workflows, its never been easier to build with Clerk alongside your agents.

And to supercharge them, a bundled skill ships with every release, pinned to the CLI version so agents always have accurate instructions for usage. The `init` command also offers to install framework-specific [skills](https://github.com/clerk/skills), so agents can reference the latest patterns and conventions.

You can install the bundled skill at any time by running:

```bash {{ prompt: '$' }}
clerk skill install
```

## Get started

Ready to try it? Tell your agent to use the [Clerk CLI](/cli) or install it yourself and spin up a new project:

```bash {{ prompt: '$' }}
npm install -g clerk
clerk init
```

```bash {{ prompt: '$' }}
pnpm install -g clerk
clerk init
```

```bash {{ prompt: '$' }}
yarn global add clerk
clerk init
```

```bash {{ prompt: '$' }}
bun add -g clerk
clerk init
```

```bash {{ prompt: '$' }}
brew install clerk/stable/clerk
clerk init
```

```bash {{ prompt: '$' }}
curl -fsSL https://clerk.com/install | bash
clerk init
```

Everything here is just the beginning, and we're continuing to invest in making the terminal a first-class way to work with Clerk. We'd love to hear what commands or workflows would make your life easier — [open an issue on GitHub](https://github.com/clerk/cli/issues).

[Read the docs](/docs/cli)

![Introducing Clerk CLI](./image.png)

---

# Middleware-based route protection bypass
URL: https://clerk.com/blog/middleware-based-route-protection-bypass.md
Date: 2026-04-15
Category: Company
Description: A CVE has been released for a middleware-based route protection bypass affecting applications using createRouteMatcher in middleware or proxy.

Today, we released a CVE for a middleware-based route protection bypass. If your application uses `createRouteMatcher` in middleware or proxy (for Next, Nuxt, or Astro), please upgrade immediately.

Upgrade instructions and more details are available in the [security advisory](https://github.com/clerk/javascript/security/advisories/GHSA-vqx2-fgx2-5wq9).

Please [contact support](https://clerk.com/contact) with any additional questions or concerns.

---

# Postmortem: Clerk System Outage (March 10, 2026)
URL: https://clerk.com/blog/2026-03-10-service-outage-postmortem.md
Date: 2026-03-13
Category: Company
Description: A detailed postmortem of the outage on March 10, 2026, including timeline, root cause analysis, and remediations.

*Clerk has faced several incidents recently and frustration is rightfully mounting. We have failed at our commitment to customers and we are deeply sorry.*

*In turn, this week we have shifted the majority of our engineering team to reliability-focused projects, and our top priority for the foreseeable future is restoring the reliability of our systems. We apologize for the delay in new product features that will result.*

## Incident timeline

- **15:57 UTC:** Our alerting systems notified us of increased latency and elevated 5xx errors across our APIs. This is when the incident was declared internally.
  - Our monitoring showed increased lock contention in the database, which had started about a minute earlier.
  - The team began investigating the cause, but there was no clear smoking gun.
  - As a mitigation step, we attempted to move all reads to the replicas to reduce pressure on the primary database. This did not help.
  - During this time, the database itself remained operational (CPU usage was within normal limits). However, queries and transactions were taking significantly longer to complete, which saturated our compute resources. With compute capacity exhausted, incoming requests began returning 429 responses.
  - Session outage resiliency was automatically triggered at origin, allowing us to continue serving incoming session token requests. However, because compute resources were already saturated and requests were taking too long to process, most session token requests also returned 429 responses.
- **16:10 UTC:** After failing to identify a clear root cause, we enabled Origin Outage Mode to keep session management operational. This worked as expected and sessions were again operational.
  - During the investigation we also observed something unusual: elevated IO wait on reads, without a corresponding increase in writes. At that point we suspected a potential infra issue and opened a ticket with GCP.
- **16:23 UTC:** The issue resolved on its own.
  - After confirming that the system had stabilized, we disabled Origin Outage Mode shortly afterward.
  - GCP informed us of the root cause (detailed below).

## Root cause analysis

The outage was triggered by a failure within our database provider. Though we generally prefer not to name our vendors, we are doing so today because the operation that failed is unique to their offering.

Specifically, the outage was triggered by a failed live migration of our Google Cloud SQL virtual machine. These are not documented within Cloud SQL, but are similar to [live migrations for Google Compute Engine](https://docs.cloud.google.com/compute/docs/instances/live-migration-process).

Google Cloud SQL performs live migrations on a routine basis, and Clerk does not receive advance notice of when they will occur. When they work as intended, they do not impact database availability or workload.

In this case, the live migration did not work as expected. Our database was subject to significantly increased disk latency, which in turn resulted in increased lock contention, and ultimately led to a complete service outage. When the operation completed, our database returned to normal:

![Disk latency spike during the incident](./image.png)

![Database metrics showing recovery after migration completed](./screenshot.png)

## Our philosophy on infrastructure choices

At Clerk, we take responsibility when failures in our upstream providers cause incidents. We understand that customers have an uptime dependency on Clerk, and so it's unacceptable to simply point fingers if our vendors trigger incidents. For every incident we face, we must have a clear line-of-sight towards mitigating that incident in the future.

As a result, our infrastructure choices tend to be very conservative. We prefer battle-tested solutions, and steer away from new or exotic technologies. This approach allows us to migrate and add redundancies to our workloads more readily.

## Additional background on Google Cloud SQL

We chose Google Cloud SQL in 2021 under this framework. We use the Enterprise Plus offering with high availability, which includes [a 99.99% availability SLA inclusive of maintenance](https://docs.cloud.google.com/sql/docs/postgres/editions-intro#edition-features).

The choice served us well for years, but our streak broke in September 2025, when a different live migration caused [a major incident](https://clerk.com/blog/2025-09-18-database-incident-postmortem). From that incident, we learned that the failure mode of live migrations is catastrophic. It causes downtime until the operation completes, and it's unsafe to trigger a replica failover while the migration is ongoing.

On the other hand, the failure of a live migration felt completely novel. We hadn't seen one before, and Google also reacted with extreme concern.

Specifically, Google escalated our ticket to P0 and administratively "pinned" our database to its datacenter, which prevented additional live migrations since they were assumed to be unsafe.

In the months following, Google and Clerk worked together to ensure that future live migrations would not cause an incident. This process included weekly phone calls and involved over 80 Google staff members. At Google's guidance, Clerk was focused on reducing our average queries per request, introducing Google's managed connection pooler, and increasing the Postgres version. Meanwhile, Google worked to identify the issue in their live migration process.

It wasn't until January that both teams were confident that our Cloud SQL database could safely be unpinned. Google and Clerk were on a call together for the first live migration following the unpinning, which succeeded without incident.

The multi-month process was exhaustive and left our team feeling confident that Google had addressed the root cause. We believed that our database would be back to the reliability it enjoyed previously.

Unfortunately, this incident revealed that live migrations are still unsafe.

## Remediations

The clarity around the root cause of this incident means that the remedies are also quite clear.

### Database pinning

We have requested that Google pins our database again to avoid additional live migrations. Unfortunately, at the time of writing, that request has not yet been accommodated. However, they have assured us another migration is unlikely in the near term, and that they are actively investigating what went wrong with this live migration.

### Eliminate live migrations going forward

Our core issue is that Google's live migrations have proven unreliable. While live migrations have the advantage of being zero-downtime when they work, the disadvantage is that they are opaque, and we are stuck trusting Google's word that they will be reliable in the future.

Instead, we believe it will be safer to depend on traditional replica promotion for ALL Postgres database maintenance going forward. Clerk already has an automated process for safe and fast replica promotion, which we used without incident for a Postgres major version upgrade on January 17, 2026.

Although this process requires more setup than a live migration, we believe the extra work is worthwhile compared to relying on the opaque live migration process.

With all that said, live migrations are currently an expectation of using Google Cloud SQL. Unless Google offers us a way to disable them permanently, we will need to migrate to another database provider or operate Postgres in-house to avoid this behavior. We are actively investigating our options.

## Additional concerns

### 429s instead of 500s

During this outage, our system returned 429s instead of 500s. The 429 is bubbled up directly from an internal service, instead of being transformed to a 500. This will be addressed so applications can accurately assess the state of Clerk's systems based on the error code.

### General reliability concerns

We understand the weight of this situation. Incidents like these are completely unacceptable for any service, much less for authentication infrastructure. We have let our customers down and are reacting in-kind.

As shared above, we have shifted the majority of our engineering team to reliability focused projects for the foreseeable future. With this change, we are confident the coming months will bring improved reliability.

## Closing

We understand the seriousness of this moment. Customers depend on Clerk for critical infrastructure, and we have not lived up to that responsibility. Our focus now is straightforward: improve reliability, address the risks made clear by these incidents, and earn back the trust we have damaged. We are sorry, and we will continue to communicate openly as we make progress.

---

# Clerk for the AI era
URL: https://clerk.com/blog/2026-03-03-clerk-for-the-ai-era.md
Date: 2026-03-03
Category: Engineering
Description: We're doubling down on making Clerk composable by default, with clear APIs and tools that work as well for human developers as they do for agents.

Since 2019, Clerk has focused on bringing a best-in-class developer experience to the auth and user management ecosystem. We built for [frontend developers](https://vercel.com/blog/authentication-for-the-frontend-cloud) newly empowered to ship full-stack applications, armed with React components and platforms like Vercel. [Clerk's prebuilt components](https://www.youtube.com/watch?v=enUuBY3HXh4) were a huge accelerant for this ecosystem. The ability for us to embed so much functionality and logic into so few lines of code felt magical at the time. It still does today.

Things are different now. The industry has undergone a radical shift over the last year. Components remain a powerful, token-efficient abstraction, but it's never been easier to prompt an app into existence, turning what was once days of work into minutes. This shift is reshaping how we build Clerk.

We're doubling down on making a best-in-class developer experience for however you choose to build. We've revamped our APIs for building your core authentication flows, shipped the first version of our agent skills and MCP server, and are continuing investment in tools and APIs that will help Clerk support whatever you're building next.

## Components that work with you

Our components remain a phenomenal way to use Clerk. We've iterated on and modernized the styling capabilities for our components. Our beloved `appearance` prop has gotten a refresh, we've introduced [more flexible theming primitives](https://clerk.com/changelog/2025-07-29-theme-simple), and invested in making sure our components can [match your existing design system](https://clerk.com/changelog/2025-07-23-shadcn-theme). We've also introduced a [dedicated changelog](/docs/reference/components/changelog) for our components, so we can share the iterative improvements you're getting out-of-the-box when you opt to build with Clerk's components.

Components are a powerful way to build with Clerk, and you have always been able to go a long way by bringing your own CSS. To explore our components and their theming capability, check out the [Theme Editor](https://clerk.com/components/theme-editor) our team recently shipped!

![Theme Editor](./theme-editor.png)

## Primitives built for agents

Components are great, but many users want total control over their UI, especially as AI has the ability to build so quickly. This is why we're reinvesting in making our React hooks and lower-level APIs even better.

Our `useSignUp()`, `useSignIn()`, `useCheckout()`, and `useWaitlist()` hooks have been redesigned to make building custom UIs with Clerk easier. We've simplified state management, redesigned the API surface, and removed the need for tedious boilerplate. We've also invested in making sure the updated APIs perform well when used with AI tools, and we're tipping the scales by embedding examples and the latest reference content into our [agent skills](https://github.com/clerk/skills). These primitives are the foundation for [building custom UIs with Clerk](https://clerk.com/docs/guides/development/custom-flows/overview).

```typescript
// signIn is stateful, updates will trigger re-renders
const { signIn, fetchStatus, errors } = useSignIn()

// Step methods map directly to the flow
await signIn.password({ emailAddress, password })
await signIn.emailCode.sendCode()
await signIn.emailCode.verifyCode({ code })

// Read the resource's status directly
signIn.status // 'needs_first_factor' | 'needs_second_factor' | 'complete'

// Built-in fetch state
fetchStatus // 'idle' | 'fetching'

// Structured field-level errors
errors.fields.identifier // "Couldn't find your account"
errors.fields.password // "Password is incorrect"
```

## More copy-to-install examples

Shadcn/ui has become the *lingua franca* of UI development over the last year, and both developers and AI tools have gravitated to the copy-to-install model. We've already launched a [Clerk shadcn/ui registry](https://clerk.com/docs/guides/development/shadcn-cli), with a growing library of copy-to-install examples for developers and AI tools. This model pairs really nicely with the improved primitives, and we're excited to continue building and sharing polished components that you can use as a starting point for your own custom UIs.

![Clerk themes](./themes.png)

## Codified best practices

Clerk's secret sauce expands beyond our components; we have years of experience building authentication and user management flows, processing billions of events, and we're committed to sharing those learnings with you. Auth flows can be deceptively complicated and riddled with edge cases. We want to help you tackle that complexity. The team is hard at work on expanding our [MCP server](https://clerk.com/docs/guides/ai/mcp/clerk-mcp-server), [agent skills](https://github.com/clerk/skills), and building tools that will help ensure your authentication UIs have best practices baked in, and account for edge cases. When you use our agent tooling, you'll get the latest docs and best practices straight from Clerk, whether your building with our components or rolling your own UI.

![Clerk agent skill](./clerk-skill.png)

### Looking forward

The choice to use Clerk doesn't hinge on committing to a specific component abstraction. Whether you're dropping in our components, crafting pixel-perfect UIs by hand, or letting your agents handle it for you, Clerk has your back. We're committed to making Clerk composable by default, with clear APIs that work as well for human developers as they do for agents.

This is just the beginning; we're excited to share our progress over the next few months as we continue to shape Clerk for however you build. Stay tuned...

![Clerk CLI](./clerk-cli.png)

If you want to learn more about recent improvements for a more composable Clerk, check out what we've shipped in [Core 3](/changelog/2026-03-03-core-3).

---

# Add API Key support to your SaaS in minutes
URL: https://clerk.com/blog/add-api-key-support-to-your-saas-with-clerk.md
Date: 2026-02-27
Category: Engineering
Description: Learn how to implement multi-tenant API Key functionality in your SaaS platform without the complexity.

As your SaaS grows, your customers may want to connect your product with the rest of their tools, whether that's running scheduled jobs, syncing data, triggering workflows, or building private internal tools. These integrations all require the same foundational capability: A secure, reliable way to programmatically access your API.

[API keys](/glossary/api-key) remain the simplest and most familiar way for customers to integrate with SaaS products. From a customer's perspective, API Keys should be easy to manage and use. But implementing API Keys correctly in a multi-tenant application takes real work:

- Where do you store keys? How do you rotate or revoke them?
- How do you ensure keys are scoped to the correct user or organization?
- How do you build a secure UI for users or organization members to manage their keys?

Most teams eventually take on the work of building and maintaining their own approach to key management, which often turns into a complex, error-prone, and time-consuming effort.

**Clerk removes that entire burden.**

With Clerk API Keys, you can securely expose your application's functionality through an API for individual users or entire organizations, without having to build the underlying infrastructure yourself.
In this guide, we'll introduce you to Clerk's new API Keys feature and show you how to integrate it into your SaaS product.

> \[!NOTE]
> API Keys are now available for use in public beta. Learn more in [the announcement on our changelog](/changelog/2025-12-11-api-keys-public-beta).

## Follow along

To make this tutorial practical and easy to apply, we'll be working with the AgentOps demo app — an open-source, multi-tenant Next.js application that showcases how to integrate **Clerk's new API Keys** system into a real SaaS product.

This demo includes a simple "Agents" concept (AI assistants) and uses organization-scoped API Keys to ensure that only authorized members of organizations can access resources.

The app features:

- Minimalistic dashboard UI using shadcn
- Combined "Sign-In" and "Sign-Up" flow page powered by Clerk
- An "Organization Settings" page that exposes Clerk's managed `<APIKeys/>` component
- Protected Next.js API CRUD routes to demonstrate organization-scoped token verification

You'll need the following before you get started:

- [A Clerk account](https://dashboard.clerk.com)
- Node and any package manager installed locally (e.g. npm, pnpm, yarn, bun)
- Familiarity with Next.js

If you want to follow along, clone the [API Keys Demo](https://github.com/clerk/demo-api-keys) repository on GitHub and navigate to the directory:

```bash {{ filename: 'terminal' }}
git clone https://github.com/clerk/demo-api-keys
cd demo-api-keys
```

Then, run the following commands to install the dependencies and start the development server:

```npm
npm install
npm run dev
```

## Enabling API Keys in Clerk

To follow along with the demo project, you'll need to enable a few features in your app instance.
But before turning on API Key support, you'll need to decide how keys should be scoped.

Clerk supports two types of keys:

- **User API Keys** - Keys tied to an individual user. Ideal for personal scripts, CLI tools, or user-initiated automations.
- **Organization API Keys** - Keys tied to an organization. Better suited for multi-tenant SaaS apps where members of organizations (or workspaces) need keys to access shared data.

This tutorial will focus on **Organization API Keys**. Before you can use API Keys, you'll need to enable Organizations first.

### Enable Organizations and Roles

To get started, open your app instance in the Clerk dashboard and navigate to **Organizations** > **[Settings](https://dashboard.clerk.com/~/organizations-settings)** from the sidebar on the left.
Toggle **Enable organizations** on if the feature is not already enabled. From here, make sure personal accounts are disabled.

Next, switch to the **[Roles & Permissions](https://dashboard.clerk.com/~/organizations-settings/roles)** tab and grant the following system permissions to your test user:

- `org:sys_api_keys:read`
- `org:sys_api_keys:manage`

These permissions determine who can view, generate, and revoke API Keys within your organization.
In the following sections, you'll integrate API Keys into your application and start protecting your backend routes using Clerk's token verification layer.

Finally, navigate to **Configure** > **Developer** > **[API Keys](https://dashboard.clerk.com/~/platform/api-keys)**.
Toggle the **Organization API keys** option to enable it and click **Save**. Make sure to leave the **User API keys** option disabled.

## Adding API Key management to your SaaS dashboard

Once API Keys are enabled in your Clerk instance, the next step is to expose a place in your product where your users can view, create, and manage their own API Keys. In our [**AgentOps example**](https://github.com/clerk/demo-api-keys), this component is integrated directly into the existing shadcn dashboard layout.

### Using `<OrganizationProfile/>` component

If your application already includes an `<OrganizationProfile/>` or `<OrganizationSwitcher/>` component, Clerk automatically adds an **API Keys** tab when the minimum permissions (`org:sys_api_keys:read`) are present. These permissions are not set by default, so you'll need to apply them to both admin and member roles in the Clerk dashboard.

In the demo app, you can try this by:

1. Switching to an organization using the `<OrganizationSwitcher/>` in the dashboard header
2. Clicking **Manage Organization**
3. Opening the **API Keys** tab

From here, organization members with `org:sys_api_keys:read` can view all active Organization API Keys, and members with `org:sys_api_keys:manage` can create new keys with one click, revoke any key immediately, and copy keys securely for external usage.

Here's the header layout used in the demo:

```tsx {{ mark: [1, 14, 15], filename: 'app/dashboard/layout.tsx' }}
import { OrganizationSwitcher, UserButton } from '@clerk/nextjs'

export default function DashboardLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset className="overflow-hidden">
        <header className="flex h-16 shrink-0 items-center gap-2">
          <div className="flex w-full items-center justify-between gap-2 px-4">
            <OrganizationSwitcher />
            <UserButton />
          </div>
        </header>
        {children}
      </SidebarInset>
    </SidebarProvider>
  )
}
```

When you click **Manage Organization** in the `<OrganizationSwitcher/>` component, you'll see the **API Keys** tab appear automatically if permissions are configured correctly.

### Using the `<APIKeys/>` component

If you'd prefer to have more control over where API Keys appear in your application, Clerk also provides a dedicated [`<APIKeys/>`](/docs/reference/components/api-keys) component that you can use.

[Our demo app](https://github.com/clerk/demo-api-keys/blob/main/src/app/dashboard/layout.tsx) shows how to embed API Key management directly inside your own dashboard pages, which is useful if you want a dedicated “Developer Settings” or “Integrations” section.

Here's the example from the demo:

```tsx {{ mark: [1, 9], filename: 'app/dashboard/settings/api-keys/page.tsx' }}
import { APIKeys } from '@clerk/nextjs'
import { RequestTester } from '@/components/request-tester'

export default function SettingsPage() {
  return (
    <div className="flex flex-col gap-4 p-8 pt-6">
      <h1 className="text-lg font-semibold">API keys</h1>
      {/* Clerk-managed API Keys UI component */}
      <APIKeys />
      <RequestTester />
    </div>
  )
}
```

This version gives you full control over where API Keys appear in your dashboard while Clerk handles security and key management.

## Creating protected backend routes

With key generation enabled, the next step is to allow your API to accept API Keys alongside normal user session tokens. Clerk makes this extremely simple with a single configuration change.

### Using multi-token verification

In server-side functions and API routes, Clerk's [`auth()`](/docs/reference/nextjs/app-router/auth) helper can accept more than one token type.
In our demo app, we've configured our `/api/agents/route.ts` file to accept both session tokens and API Keys.
Below is a simplified example:

```tsx {{ mark: [1, [5, 7]], filename: 'app/api/agents/route.ts' }}
import { auth } from '@clerk/nextjs'

export async function GET() {
  // Needs to have the `acceptsToken: 'api_key'` or it will only accept session tokens
  const res = await auth({
    acceptsToken: ['session_token', 'api_key'],
  })

  if (!res.isAuthenticated) {
    return new Response('Unauthorized', { status: 401 })
  }
  if (!res.orgId) {
    return new Response('Unauthorized: please use a valid org key', { status: 401 })
  }
  // Continue handling request...
}
```

This line tells Clerk to accept:

- Browser-based user sessions (*from logged-in users*)
- API Keys (*passed as a Bearer token in request headers*)

```tsx
acceptsToken: ['session_token', 'api_key']
```

Clerk automatically extracts and verifies the token, determines the organization, and ensures the key has not been revoked.

### Rejecting unauthenticated or cross-org access

Because Clerk includes `orgId` directly in the token, your backend logic becomes extremely straightforward:

- If no `orgId` → reject
- If `orgId` does not match the requested resource → reject
- Otherwise → process the request

This ensures strong multi-tenant isolation and prevents customers from ever accessing resources outside their organization.

## Testing your API Keys

Now that API Keys are enabled and visible in your application’s dashboard, it’s time to test them against real API endpoints in the demo app.
You can use cURL or tools like [Insomnia](https://insomnia.rest/) or [Postman](https://www.postman.com/) to test your API Keys.

Go ahead and create an API Key on the `/dashboard/settings/api-keys` page using the `<APIKeys/>` component from the previous section.

> \[!IMPORTANT]
> After you've generated a key, copy it and store it somewhere safe — **it will only be shown once.**

With your key in hand, you can start making real requests.

### Creating an agent

Let's start by creating a new agent using the `POST /api/agents` endpoint. This endpoint requires a valid **Organization API Key**.

```bash {{ filename: 'terminal' }}
curl http://localhost:3000/api/agents \
 -X POST \
 -H "Authorization: Bearer <ORG_API_KEY>" \
 -H "Content-Type: application/json" \
 -d '{"name": "Support Assistant", "description": "Handles inbound support tickets.", "model": "gpt-5.1"}'
```

If successful, you'll see the newly created agent returned in the response.

```json
{
  "id": "agent_tURoMlenCly4sL2cJXiUN",
  "name": "Support Assistant",
  "description": "Handles inbound support tickets.",
  "model": "gpt-5.1"
}
```

Since every request is tied to your Organization ID, the new agent is automatically scoped to your organization.

Now, try to create an agent with an invalid API Key.
If the key is invalid (or revoked / expired), the request will be rejected with a `401 Unauthorized` response.

You can test the same functionality from a logged-in user context by creating an agent on the `/dashboard` page.

### Listing agents

Next, fetch all agents that belong to your organization.

```bash {{ filename: 'terminal' }}
curl http://localhost:3000/api/agents \
  -H "Authorization: Bearer <ORG_API_KEY>"
```

You should get a response with an array containing the recently created agent:

```json
[
  {
    "id": "agent_tURoMlenCly4sL2cJXiUN",
    "name": "Support Assistant",
    "description": "Handles inbound support tickets.",
    "model": "gpt-5.1"
  }
]
```

### Deleting an agent

Finally, delete an existing agent by using the `DELETE /api/agents` endpoint and sending its `agentId` in the request body:

```bash {{ filename: 'terminal' }}
curl http://localhost:3000/api/agents \
  -X DELETE \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ORG_API_KEY>" \
  -d '{"agentId": "<AGENT_ID>"}'
```

Clerk validates the API Key, resolves the organization it belongs to, and ensures the deletion request only affects resources owned by that org.

You can test the same functionality from a logged-in user context by deleting an agent on the `/dashboard` page.

### What you've verified

By this point, you've tested that:

- Organization API Keys work alongside user session tokens for backend access
- All requests are scoped to the correct organization
- Requests are rejected if the API Key is invalid

Your platform can support external integrations and every API request passes through Clerk's verification. This mirrors how your real users will interact with your API.

## Conclusion

Adding API Key support to a SaaS product is often a significant engineering effort, especially in a multi-tenant environment where every request must be scoped to the correct organization. Clerk removes that complexity by managing key creation, access controls, token validation, and providing ready-made UI components for your customers.

In just a few steps, you've put all the essential pieces in place:

- Enabling API Keys in your Clerk app instance
- Adding managed UI to your application
- Protecting backend routes with multi-token verification
- Enforcing organization-level data isolation
- Testing endpoints with organization-scoped keys

Taken together, these steps give you a fast, secure foundation for customer-facing integrations—one that you can adapt to your product's needs. With this setup in place, your customers can connect your SaaS to the rest of their workflow with ease.

To explore the complete setup for this guide, check out the [**AgentOps API Keys Demo**](https://github.com/clerk/demo-api-keys).
For more advanced use cases, check out the examples in this [repository](https://github.com/clerk/demos).

Other resources:

- [API keys guide](/docs/guides/development/machine-auth/api-keys) — Complete walkthrough of enabling and using API keys
- [Backend SDK reference](/docs/reference/backend/api-keys/list) — Full API for creating, listing, verifying, and revoking keys
- [Dashboard](https://dashboard.clerk.com/~/configure/api-keys) — Enable API keys for your application

---

# Postmortem: Clerk System Outage (February 19, 2026)
URL: https://clerk.com/blog/2026-02-19-system-outage-postmortem.md
Date: 2026-02-19
Category: Company
Description: A detailed post-mortem of the system outage that occurred on February 19, 2026, including root cause analysis and planned remediations.


On February 19, 2026 at 8:11 AM PST, Clerk experienced a service outage caused by an inefficient query plan. A routine auto analyze led to a query plan flip, which immediately reduced database performance. Slow database queries led to queuing in our request handlers, and ultimately over 95% of traffic returning 429 without being handled. The small portion of requests that reached our database returned 200, but extremely slowly.

Service was restored approximately 90 minutes later when the `ANALYZE` command was re-run manually, and the query returned to its prior plan. Shortly after, the database performance returned to normal levels.

## Recovery timeline

- **16:15 UTC** — Incident reported and escalated internally. Our team initiated a group call and began working to identify the root cause.
- **16:32 UTC** — We identified a customer with an anomalous traffic spike coincident with the outage, and we believed the increased load may have triggered widespread database issues.
- **16:35 UTC** - We added manual blocks for this customer's traffic in an attempt to relieve pressure on the database.
- **16:37 UTC** - Manual blocks failed to restore the system and we continued investigation.
- **16:50 UTC** - We determined that the suspected customer had an overly aggressive retry mechanism that triggered their anomalous spike, but the initial failures that required retry were within Clerk's infrastructure.
- **16:55 UTC** - We determined that our automatic failover for our Session API failed to trigger because the database was technically still online, just degraded. We began investigating alternative failover approaches.
- **17:08 UTC** - A new failover mechanism was enabled to handle session token generation outside of the core Session API, reducing backend load. This mechanism was built and tested over the last few months, but hadn't been instrumented with triggers in production yet.
- **17:10 UTC** — The failover succeeded and allowed many users to access customer applications again, while sign in and account management APIs remained inaccessible.
- **17:25 UTC** - We determined the root cause to be an automatic `ANALYZE` that resulted in an inefficient query plan flip.
- **17:27 UTC** — We manually re-executed `ANALYZE` on the affected table and the query returned to its prior plan. The database began to recover.
- **17:43 UTC** — We observed systems stabilizing and the database back to healthy levels. The failover mechanism was disabled.
- **18:06 UTC** — Stable performance confirmed across the entire system. Incident moved to monitoring status.

## Root cause analysis

In short, the root cause was Postgres' automatic analyze function triggering a "query plan flip."

For more context, before running a particular query, Postgres relies on a system called a query planner to decide how it will efficiently find the requested data. Postgres' query planner is dynamic, so as more data is inserted, it regularly re-analyzes the database to ensure its query plans are still optimal.

For the impacted query, the planner was leveraging an unreliable statistic. Specifically, the planner was trying to determine the percent of values in a column that were NULL. For this column, the true answer was very high (99.9996%).

However, to avoid re-reading all database rows repeatedly, the planner uses a sampling system to estimate data distribution.

When the automatic analyze function ran, the planner's sample size was too low and it only found NULL values, which led it to erroneously conclude that 100% of the values were NULL. It then planned the query assuming that part of it would return zero non-nulls, when in fact it returned over 17,000. This new plan is considered the "query plan flip."

The impacted query is run frequently enough that the unexpected traversal of 17,000 rows caused nearly all database resources to go toward this query. The reduced performance ultimately led to significant queueing, and shedding the majority of requests with status 429.

## Planned remediations

### Alerting improvements

We are adding dedicated alerting for database query plan flips. This class of issue can cause sudden, severe degradation, and we need to detect it immediately rather than relying on downstream symptoms.

This would have helped us avoid the mistake of believing a customer's traffic pattern caused the issue.

### Hardening session token failover

Our previous Session API failover mechanism was designed to trigger during Postgres outages, while the new failover is designed to trigger during any type of failure at origin.

This extra resilience will ensure users stay signed in for a wider variety of failures.

While this new failover proved to be ready during this incident, our reliability team had not yet been fully trained how to enable it, and we have not yet instrumented automatic triggers to enable it. We expect this work to be completed within the next few weeks so it can be used confidently during any future incidents.

### Query plan stability

Immediately following the incident, we increased the statistics target on the relevant table so the query planner can track a larger, more accurate sample. Later that evening, we refactored the query so the planner could take a deterministic approach in query planning.

Now, we are auditing all of our queries to determine if others are at risk for inefficient planning from unstable statistics.

### Incident communication

We heard clearly from our customers that our communication during this incident was not good enough. Status page updates were too infrequent, the initial severity label did not accurately reflect the impact many customers were experiencing, and too much time passed before the first update was posted. We take this feedback seriously.

We are formalizing our incident communication process. This includes designating a dedicated communications lead during incidents, committing to status page updates at a regular cadence even when the situation is unchanged, and ensuring that severity labels on the status page accurately reflect customer impact. We are also improving our incident tooling so that updates are cross-posted to social channels to reach customers faster.

## Closing

We are deeply sorry for the disruption this incident caused to your team, your business, and your users. We understand that you depend on Clerk to be available, and we failed to meet that expectation yesterday, and too many times in recent months.

This is unacceptable, and we will be bringing increased attention to proactively adding monitors, redundancies, and failovers to our overall system. While we've added many throughout the past year, recent events have made it clear that we are not moving fast enough.

Thank you for your patience and continued partnership.

---

# Using Clerk in a React Native app
URL: https://clerk.com/blog/using-clerk-in-a-react-native-app.md
Date: 2026-02-12
Category: Guides
Description: Build a cross-platform time tracking app from scratch with React Native and Expo, implement secure authentication with Clerk, and set up data storage using Supabase.

The cross-platform capabilities of React Native combined with Expo's development tools have transformed how developers approach mobile app creation. Add Clerk's user management and Supabase's powerful backend, and you have a complete toolkit for building production-ready apps that deploy everywhere.

In this tutorial, you'll learn how to create a time tracking app called Aika from scratch. You'll create this app with React Native and Expo, implement [secure authentication with Clerk](/expo-authentication), and set up data storage using Supabase. By the end of this guide, you'll have built a fully functional, cross-platform time tracking app that works on both iOS and Android devices.

## Why Clerk for authentication?

When building any application, you'll face the challenge of implementing secure authentication. Clerk goes beyond just authentication and provides a complete user management solution that's both secure and easy to implement.

### Comprehensive user management

With Clerk, you'll save hours of development time as it handles everything from sign-up and sign-in flows to [session management](/docs/how-clerk-works/overview) and user profiles. You can easily implement multiple authentication methods including email/password, OAuth providers like Google and Apple, [email codes, passkeys](/docs/authentication/configuration/sign-up-sign-in-options), and more.

In this guide, you'll implement email/password and [Google authentication](/docs/authentication/social-connections/google), giving your users flexibility in how they access the application.

### Multi-tenancy and role-based access control

If you are building an application that requires teams and collaboration, you'll appreciate Clerk's multi-tenancy features and role-based access control. Our [B2B tools](/b2b-saas) allow developers to enable users to create their own teams, invite others to join, and assign roles to users within their teams.

> \[!NOTE]
> While we won't fully utilize these features in our initial build, you'll learn how to add [multi-tenancy](/docs/organizations/overview) in the next article in this series.

### Built-in security protections

Clerk has a number of additional security features built into every application created on our platform. These include features like [device management](/docs/expo/components/user/user-profile) and [bot detection](/docs/security/bot-protection), which are crucial for maintaining the security of user accounts without requiring you to implement these protections yourself. You can also easily protect against platform abuse by preventing the use of email subaddresses, such as `user+tag@example.com`, which are often used to bypass free tier limits. Finally, Clerk automatically checks all credentials against a list of known [breached credentials](/docs/security/password-protection) to prevent account takeover attacks.

This is not a complete list of the security features we offer, and you can learn more on our [Authentication page](/user-authentication).

## Why choose Supabase for your backend

For data storage, you'll use Supabase, an open-source Firebase alternative that provides a powerful Postgres database with a simple API.

### Postgres-based backend as a service

With Supabase, you'll harness the power of a full PostgreSQL database without setting up and maintaining your own server infrastructure. While you can execute SQL queries against the database just like any other, Supabase also provides the Data API, which allows you to access the database from your application using a simple API protected by Row Level Security (RLS), ensuring data is only accessible to the correct users.

### Seamless integration with Clerk

One of the most compelling reasons to use Supabase with Clerk is our seamless integration. You'll configure RLS policies to work with Clerk's JWT tokens, requiring users to be authenticated before accessing data belonging to those users. This creates a secure, scalable system with minimal backend code, saving you time and reducing potential security vulnerabilities. Learn more in our [Supabase integration guide](/docs/integrations/databases/supabase).

### Developer-friendly experience

When working with Supabase, you'll appreciate its clean, intuitive dashboard for managing your database, along with comprehensive documentation. Supabase also has excellent client libraries and developer tooling, making it easy to apply schema changes and deploy changes to your Supabase project from your workstation. This allows you to focus on building features rather than configuring infrastructure.

## Build Aika: step-by-step instructions

Now that you understand why we're using Clerk and Supabase, let's build the Aika time tracking app from scratch. Follow these steps carefully to create your own fully functional time tracking application.

Before following along, make sure you have the following:

- A Clerk account
- A Supabase account
- The Supabase CLI installed

> \[!NOTE]
> As you build this project, be sure to check the comments in each code snippet for additional context on relevant code blocks.

A completed version of this project can be [found on GitHub](https://github.com/bmorrisondev/aika).

### 1. Configure Clerk and Supabase

Start by creating a new Clerk application in the [Clerk dashboard](https://dashboard.clerk.com). Name your application "Aika" and leave the sign in options as default.

![Clerk dashboard](./image1.png)

The next page will show the onboarding screen for Clerk, along with a selectable list of the most popular frameworks. Select **Expo** from the list, copy the API keys from step 2, and set them aside for later use.

![Clerk onboarding](./image2.png)

Now head over to the [Supabase dashboard](https://supabase.com/dashboard/organizations) and create a new project. Name it "Aika", select **Generate a password** in the Database Password section, and then **Create new project**. Make sure to copy the password for later use.

![Supabase dashboard](./image3.png)

Navigate to the **Authentication** section in the left sidebar, and then select **Sign in / Providers**, then **Third Party Auth**. Select **Clerk** from the list of providers to open a modal to configure the integration.

![Configure the Clerk integration in the Supabase dashboard](./image4.png)

In the modal, select **Clerk's Connect with Supabase page** to open the integration wizard in the Clerk dashboard.

![Clerk's Connect with Supabase page](./image5.png)

In the wizard, ensure the correct organization, application, and instance are selected. Select **Activate Supabase integration**, then copy the value in the **Clerk domain** field. This will be used in the next step.

![Clerk integration wizard](./image6.png)

Once copied, go back to the Supabase dashboard, paste the value into the modal, and select **Create connection**.

Next, navigate to **Project Settings** > **Data API** and note the **Project URL** for later.

![Supabase Data API](./image7.png)

Then navigate to the **API Keys** node and copy the **anon** key. This will be used in the next step.

![Supabase API Keys](./image8.png)

Both Clerk and Supabase are now configured, and you can start building Aika.

### 2. Initialize your React Native app with Expo

Now, you'll create a new Expo project using the latest version of the Expo SDK. Expo provides an excellent development experience for React Native with features like hot reloading, easy access to native APIs, and simplified deployment.

Open your terminal and run these commands:

```bash
pnpx create-expo-app@latest aika
cd aika
pnpm start
```

You should see the Expo development server running and options to open your app on iOS, Android, or web. For this guide, you'll be using the web version of the app, which can be accessed by tapping `w` in the terminal.

![Expo development server](./image9.png)

Now stop the development server by pressing `Ctrl + C` in the terminal and run the following command to install the Clerk and Supabase SDKs along with their peer dependencies:

```bash
pnpm install @clerk/clerk-expo @supabase/supabase-js expo-secure-store expo-auth-session react-native-url-polyfill
```

Create a `.env` file in your project root to store the Clerk and Supabase API keys:

```{{ filename: '.env' }}
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key_here
EXPO_PUBLIC_SUPABASE_URL=your_supabase_url_here
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here
```

### 3. Adding authentication with Clerk

To use the Clerk SDK, you need to wrap the entire application in the [`ClerkProvider`](/docs/components/clerk-provider) component, which provides access to all of the necessary control components and helper functions. Update `app/_layout.tsx` to wrap the application in a `ClerkProvider`:

```tsx {{ filename: 'app/_layout.tsx', ins: [8, 9, 23, 31] }}
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { useFonts } from 'expo-font'
import { Stack } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import 'react-native-reanimated'

import { useColorScheme } from '@/hooks/useColorScheme'
import { ClerkProvider } from '@clerk/clerk-expo'
import { tokenCache } from '@clerk/clerk-expo/token-cache'

export default function RootLayout() {
  const colorScheme = useColorScheme()
  const [loaded] = useFonts({
    SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
  })

  if (!loaded) {
    // Async font loading only occurs in development.
    return null
  }

  return (
    <ClerkProvider tokenCache={tokenCache}>
      <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
        <Stack>
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="+not-found" />
        </Stack>
        <StatusBar style="auto" />
      </ThemeProvider>
    </ClerkProvider>
  )
}
```

Now you'll need to configure the authentication pages as well as a protected area of the application that requires users to be signed in before they can access it. Start by creating a new folder called `app/(tabs)/protected` and move all of the files from `app/(tabs)` into it. The updated file structure should look like this:

```
# Before
app/
└── (tabs)/
    ├── _layout.tsx
    ├── explore.tsx
    └── index.tsx

# After
app/
└── (tabs)/
    └── protected/
        ├── _layout.tsx
        ├── explore.tsx
        └── index.tsx
```

Create a new layout file at `app/(tabs)/_layout.tsx` to wrap the protected routes in a `Stack.Protected` component. The Clerk SDK will be used to determine if the user is signed in and redirect them to the appropriate screen:

```tsx {{ filename: 'app/(tabs)/_layout.tsx' }}
import { useAuth } from '@clerk/clerk-expo'
import { Stack } from 'expo-router'

export default function AppLayout() {
  // useAuth hook from Clerk SDK
  const { isSignedIn } = useAuth()

  return (
    <Stack screenOptions={{ headerShown: false }}>
      {/* Public routes */}
      <Stack.Protected guard={!isSignedIn}>
        <Stack.Screen name="index" />
        <Stack.Screen name="sign-up" />
      </Stack.Protected>

      {/* Protected routes */}
      <Stack.Protected guard={isSignedIn!}>
        <Stack.Screen name="protected" />
      </Stack.Protected>
    </Stack>
  )
}
```

Create the `constants/AuthStyles.ts` file to export styles that will be shared across the authentication pages, making the code cleaner:

```ts {{ filename: 'constants/AuthStyles.ts', collapsible: true }}
import { StyleSheet } from 'react-native'

export const styles = StyleSheet.create({
  formContainer: {
    marginTop: 72,
    width: '100%',
    maxWidth: 420,
    alignSelf: 'center',
    backgroundColor: '#ffffff',
    borderRadius: 16,
    padding: 24,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.08,
    shadowRadius: 12,
    elevation: 4,
  },
  headerContainer: {
    alignItems: 'center',
    marginBottom: 32,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#0F172A', // Slate 900
  },
  subtitle: {
    fontSize: 15,
    color: '#64748B', // Slate 500
    marginTop: 10,
    lineHeight: 22,
  },
  form: {
    width: '100%',
  },
  inputGroup: {
    marginBottom: 20,
  },
  label: {
    fontSize: 15,
    fontWeight: '600',
    color: '#334155', // Slate 700
    marginBottom: 8,
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#E2E8F0', // Slate 200
    borderRadius: 12,
    paddingHorizontal: 16,
    fontSize: 16,
    backgroundColor: '#F8FAFC', // Slate 50
    color: '#1E293B', // Slate 800
  },
  button: {
    backgroundColor: '#6366F1', // Indigo 500
    borderRadius: 12,
    height: 50,
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: 28,
    shadowColor: '#6366F1', // Indigo 500
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.2,
    shadowRadius: 4,
    elevation: 3,
  },
  buttonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '600',
  },
  textButton: {
    marginTop: 20,
    alignItems: 'center',
    padding: 8,
  },
  textButtonText: {
    fontSize: 15,
    fontWeight: '600',
    color: '#6366F1', // Indigo 500
  },
})
```

To support OAuth, you'll use a dedicated `OAuthButton` component that can accept one of the many providers that Clerk supports. Create `components/OAuthButton.tsx`:

```tsx {{ filename: 'components/OAuthButton.tsx' }}
import { styles } from '@/constants/AuthStyles'
import { useSSO } from '@clerk/clerk-expo'
import { OAuthStrategy } from '@clerk/types'
import * as AuthSession from 'expo-auth-session'
import * as WebBrowser from 'expo-web-browser'
import React, { useCallback, useEffect } from 'react'
import { Platform, Text, TouchableOpacity } from 'react-native'

export const useWarmUpBrowser = () => {
  useEffect(() => {
    if (Platform.OS === 'web') return
    void WebBrowser.warmUpAsync()
    return () => {
      void WebBrowser.coolDownAsync()
    }
  }, [])
}
WebBrowser.maybeCompleteAuthSession()

interface Props {
  // The OAuthStrategy type from Clerk allows you to specify the provider you want to use in this specific instance of the OAuthButton component
  strategy: OAuthStrategy
  children: React.ReactNode
}

export default function OAuthButton({ strategy, children }: Props) {
  useWarmUpBrowser()
  // useSSO hook from Clerk SDK to support various SSO providers
  const { startSSOFlow } = useSSO()

  const onPress = useCallback(async () => {
    try {
      const { createdSessionId, setActive } = await startSSOFlow({
        strategy,
        redirectUrl: AuthSession.makeRedirectUri(),
      })

      if (createdSessionId) {
        setActive!({ session: createdSessionId })
      } else {
        throw new Error('Failed to create session')
      }
    } catch (err) {
      console.error(JSON.stringify(err, null, 2))
    }
  }, [startSSOFlow, strategy])

  return (
    <TouchableOpacity onPress={onPress} style={styles.button}>
      <Text style={styles.buttonText}>{children}</Text>
    </TouchableOpacity>
  )
}
```

Now you'll configure the sign-in and sign-up screens at `app/(tabs)/index.tsx` and `app/(tabs)/sign-up.tsx` respectively. This will ensure that the first screen a user sees when they access the application is the sign-in form.

Create `app/(tabs)/index.tsx`:

```tsx {{ filename: 'app/(tabs)/index.tsx' }}
import OAuthButton from '@/components/OAuthButton'
import { styles } from '@/constants/AuthStyles'
import { useSignIn } from '@clerk/clerk-expo'
import { useRouter } from 'expo-router'
import { useState } from 'react'
import { Text, TextInput, TouchableOpacity, View } from 'react-native'

function SignInScreen() {
  const router = useRouter()
  // [useSignIn hook](/docs/hooks/use-sign-in) from Clerk SDK to handle sign-in logic
  const { signIn, isLoaded, setActive } = useSignIn()
  const [emailAddress, setEmailAddress] = useState('')
  const [password, setPassword] = useState('')

  const onSignInPress = async () => {
    if (!isLoaded || !setActive) return

    try {
      // signIn.create() method from Clerk SDK to handle sign-in logic
      const signInAttempt = await signIn.create({
        identifier: emailAddress,
        password,
      })

      if (signInAttempt.status === 'complete') {
        await setActive({
          session: signInAttempt.createdSessionId,
        })
        // Navigate to protected screen once the session is created
        router.replace('/(tabs)/protected')
      } else {
        console.error(JSON.stringify(signInAttempt, null, 2))
      }
    } catch (err: any) {
      console.error(JSON.stringify(err, null, 2))
    }
  }

  return (
    <View style={styles.formContainer}>
      <View style={styles.headerContainer}>
        <Text style={styles.title}>Sign In</Text>
        <Text style={styles.subtitle}>Enter your credentials to access your account</Text>
      </View>

      {/* OAuthButton component to handle OAuth sign-in */}
      <View style={{ marginBottom: 24 }}>
        <OAuthButton strategy="oauth_google">Sign in with Google</OAuthButton>
      </View>

      <View style={styles.form}>
        <View style={styles.inputGroup}>
          <Text style={styles.label}>Email address</Text>
          <TextInput
            style={styles.input}
            placeholder="Enter your email address"
            value={emailAddress}
            onChangeText={(text) => setEmailAddress(text)}
            autoCapitalize="none"
          />
        </View>

        <View style={styles.inputGroup}>
          <Text style={styles.label}>Password</Text>
          <TextInput
            style={styles.input}
            placeholder="Enter your password"
            value={password}
            onChangeText={(text) => setPassword(text)}
            secureTextEntry
          />
        </View>

        <TouchableOpacity style={styles.button} onPress={onSignInPress} activeOpacity={0.8}>
          <Text style={styles.buttonText}>Sign In</Text>
        </TouchableOpacity>

        {/* Link to sign-up screen */}
        <TouchableOpacity
          style={styles.textButton}
          onPress={() => router.push('/sign-up')}
          activeOpacity={0.8}
        >
          <Text style={styles.textButtonText}>Don&apos;t have an account? Sign up.</Text>
        </TouchableOpacity>
      </View>
    </View>
  )
}

export default SignInScreen
```

The sign-up screen is similar to the sign-in screen but contains additional logic to handle verifying a new user account by requesting the six-digit code that is sent to their email address.

Create the `app/(tabs)/sign-up.tsx` file:

```tsx {{ filename: 'app/(tabs)/sign-up.tsx' }}
import { styles } from '@/constants/AuthStyles'
import { useSignUp } from '@clerk/clerk-expo'
import { useRouter } from 'expo-router'
import { useState } from 'react'
import { Text, TextInput, TouchableOpacity, View } from 'react-native'
import OAuthButton from '@/components/OAuthButton'

function SignUpScreen() {
  const router = useRouter()
  const { signUp, isLoaded, setActive } = useSignUp()
  const [emailAddress, setEmailAddress] = useState('')
  const [password, setPassword] = useState('')
  const [pendingVerification, setPendingVerification] = useState(false)
  const [code, setCode] = useState('')

  // [useSignUp hook](/docs/hooks/use-sign-up) from Clerk SDK to handle sign-up logic
  const onSignUpPress = async () => {
    if (!isLoaded || !signUp) {
      return
    }

    try {
      // Start by creating a new temporary user record
      await signUp.create({
        emailAddress,
        password,
      })

      // Prepare the email address verification, which will send the email a code
      await signUp.prepareEmailAddressVerification({
        strategy: 'email_code',
      })

      setPendingVerification(true)
    } catch (err: any) {
      console.error(JSON.stringify(err, null, 2))
    }
  }

  const onVerifyPress = async () => {
    if (!isLoaded || !signUp) {
      return
    }

    try {
      // Attempt to verify the email address using the provided code
      const completeSignUp = await signUp.attemptEmailAddressVerification({
        code,
      })

      if (completeSignUp.status === 'complete') {
        // If the sign-up is complete, set the active session and navigate to the protected screen
        await setActive({ session: completeSignUp.createdSessionId })
        router.replace('/(tabs)/protected')
      } else {
        console.error(JSON.stringify(completeSignUp, null, 2))
      }
    } catch (err: any) {
      console.error(JSON.stringify(err, null, 2))
    }
  }

  // Email verification screen
  if (pendingVerification) {
    return (
      <View style={styles.formContainer}>
        <View style={styles.headerContainer}>
          <Text style={styles.title}>Verify your email</Text>
          <Text style={styles.subtitle}>
            Enter the verification code sent to your email address
          </Text>
        </View>

        <View style={styles.form}>
          <View style={styles.inputGroup}>
            <Text style={styles.label}>Verification code</Text>
            <TextInput
              style={styles.input}
              placeholder="Enter the verification code"
              value={code}
              onChangeText={(text) => setCode(text)}
            />
          </View>

          <TouchableOpacity style={styles.button} onPress={onVerifyPress} activeOpacity={0.8}>
            <Text style={styles.buttonText}>Verify</Text>
          </TouchableOpacity>
        </View>
      </View>
    )
  }

  // Sign up screen
  return (
    <View style={styles.formContainer}>
      <View style={styles.headerContainer}>
        <Text style={styles.title}>Sign Up</Text>
        <Text style={styles.subtitle}>Create your account to get started</Text>
      </View>

      {/* OAuthButton component can also be used to create accounts */}
      <View style={{ marginBottom: 24 }}>
        <OAuthButton strategy="oauth_google">Sign up with Google</OAuthButton>
      </View>

      <View style={styles.form}>
        <View style={styles.inputGroup}>
          <Text style={styles.label}>Email address</Text>
          <TextInput
            style={styles.input}
            placeholder="Enter your email address"
            value={emailAddress}
            onChangeText={(text) => setEmailAddress(text)}
            autoCapitalize="none"
            keyboardType="email-address"
          />
        </View>

        <View style={styles.inputGroup}>
          <Text style={styles.label}>Password</Text>
          <TextInput
            style={styles.input}
            placeholder="Create a password"
            value={password}
            onChangeText={(text) => setPassword(text)}
            secureTextEntry
          />
        </View>

        <TouchableOpacity style={styles.button} onPress={onSignUpPress} activeOpacity={0.8}>
          <Text style={styles.buttonText}>Sign Up</Text>
        </TouchableOpacity>

        <TouchableOpacity
          style={styles.textButton}
          onPress={() => router.push('/')}
          activeOpacity={0.8}
        >
          <Text style={styles.textButtonText}>Already have an account? Sign in.</Text>
        </TouchableOpacity>
      </View>
    </View>
  )
}

export default SignUpScreen
```

Authentication should be fully configured at this point. Start the development server by running the following command in your terminal:

```bash
pnpm start
```

Press `w` with the terminal focused to open the web version of your app again and test creating an account and signing in. Once signed in, you should be redirected to the protected screen, which is the same as the home screen from before since we moved those screens into the `protected` folder.

### 4. Adding Supabase and integrating Clerk

Now that Clerk is configured, you'll need to configure Supabase to work with Clerk. Start by running the following commands in your terminal to initialize Supabase in the repository and link it to the application you created earlier:

```bash
supabase init
supabase link # Select your project when prompted and enter the database password from when you created your Supabase project
```

Now run the following command to create a new database migration which will be used to store timer history:

```bash
supabase migration new timer_entries
```

A new file will be created at `supabase/migrations` with a timestamp and the name `timer_entries`. In the migration file, you'll define the table, enable RLS, and configure the necessary RLS policies to parse the JWT token and extract the Clerk user ID from the `sub` claim, associating that record to the user who created it.

Open the file and add the following SQL:

```sql {{ filename: 'supabase/migrations/{TIMESTAMP}-timer_entries.sql' }}
-- Create time_entries table for tracking time
CREATE TABLE time_entries (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  start_time TIMESTAMP WITH TIME ZONE NOT NULL,
  end_time TIMESTAMP WITH TIME ZONE,
  description TEXT,
  owner_id TEXT NOT NULL
);

-- Enable Row Level Security on time_entries table
ALTER TABLE time_entries ENABLE ROW LEVEL SECURITY;

-- Create policy for selecting time entries
CREATE POLICY select_own_time_entries ON time_entries
  FOR SELECT
  USING (owner_id = (auth.jwt() ->> 'sub'::text));

-- Create policy for inserting time entries
CREATE POLICY insert_own_time_entries ON time_entries
  FOR INSERT
  WITH CHECK (owner_id = (auth.jwt() ->> 'sub'::text));

-- Create policy for updating time entries
CREATE POLICY update_own_time_entries ON time_entries
  FOR UPDATE
  USING (owner_id = (auth.jwt() ->> 'sub'::text));

-- Create policy for deleting time entries
CREATE POLICY delete_own_time_entries ON time_entries
  FOR DELETE
  USING (owner_id = (auth.jwt() ->> 'sub'::text));
```

Now run the following command to apply the migration. Paste in the database password from when you created your Supabase project if prompted.

```bash
supabase db push
```

Next you'll configure a custom Supabase client that accepts a `Promise<string | null>` as an argument, which will be used to set the access token for the Supabase client allowing Clerk to manage the session. Create the `utils/supabase.ts` file which exports this client:

```ts {{ filename: 'utils/supabase.ts' }}
import { createClient } from '@supabase/supabase-js'
import 'react-native-url-polyfill/auto'

const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!

export function createSupabaseClerkClient(accessToken: Promise<string | null>) {
  return createClient(supabaseUrl!, supabaseAnonKey!, {
    accessToken: () => accessToken,
  })
}
```

Now you can start implementing the core logic for the timer and history.

### 5. Implementing the timer and history

There are a few components that will be needed to implement this functionality. The below image contains annotated screenshots of the final implementation of the home screen, with the left image showing the timer running and the right image showing it stopped:

![The home screen annotated](./image10.png)

The timer component is shown when the user has an active timer and lets them stop the timer when they are finished doing the work. Create `components/TimerCounter.tsx` and paste in the following code:

```tsx {{ filename: 'components/TimerCounter.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { StyleSheet, TouchableOpacity } from 'react-native'

import { ThemedText } from './ThemedText'
import { ThemedView } from './ThemedView'

interface TimerCounterProps {
  isRunning: boolean
  description: string
  initialStartTime?: Date | null
  onStart: () => void
  onStop: () => void
}

export function TimerCounter({
  isRunning,
  description,
  initialStartTime,
  onStart,
  onStop,
}: TimerCounterProps) {
  const [elapsedTime, setElapsedTime] = useState(0)

  // We track the start time to calculate elapsed time
  const startTimeRef = useRef<Date | null>(null)

  // Track the last time we updated the elapsed time
  const lastUpdateRef = useRef<number>(0)

  // Timer interval reference
  const timerIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)

  // Format elapsed time in HH:MM:SS format
  const formatElapsedTime = (seconds: number) => {
    const hours = Math.floor(seconds / 3600)
    const minutes = Math.floor((seconds % 3600) / 60)
    const secs = seconds % 60
    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
  }

  // Start the timer counter
  const startTimerCounter = useCallback((startTime?: Date) => {
    const start = startTime || new Date()
    startTimeRef.current = start
    lastUpdateRef.current = Date.now()

    // Calculate initial elapsed time
    const now = new Date()
    const initialDiffInSeconds = Math.floor((now.getTime() - start.getTime()) / 1000)
    setElapsedTime(initialDiffInSeconds)

    // Clear any existing interval
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current)
      timerIntervalRef.current = null
    }

    // Set up the interval to update elapsed time every second
    const intervalId = setInterval(() => {
      if (startTimeRef.current) {
        // Use the current time for accurate timing
        const now = new Date()
        const diffInSeconds = Math.floor((now.getTime() - startTimeRef.current.getTime()) / 1000)

        setElapsedTime(diffInSeconds)
      }
    }, 500) // Update more frequently for better accuracy

    timerIntervalRef.current = intervalId
  }, [])

  // Stop the timer counter
  const stopTimerCounter = useCallback(() => {
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current)
      timerIntervalRef.current = null
    }
    setElapsedTime(0)
    startTimeRef.current = null
  }, [])

  // Initialize timer when component mounts or when isRunning changes
  useEffect(() => {
    if (isRunning && initialStartTime) {
      startTimerCounter(initialStartTime)
    } else if (!isRunning) {
      stopTimerCounter()
    }

    // Clean up timer interval when component unmounts
    return () => {
      if (timerIntervalRef.current) {
        clearInterval(timerIntervalRef.current)
        timerIntervalRef.current = null
      }
    }
  }, [isRunning, initialStartTime, startTimerCounter, stopTimerCounter])

  // Animated style for the progress indicator
  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.card}>
        <ThemedView style={styles.content}>
          {/* Timer Description */}
          <ThemedText
            type="subtitle"
            style={styles.description}
            numberOfLines={1}
            ellipsizeMode="tail"
          >
            {description}
          </ThemedText>

          {/* Timer Display */}
          <ThemedView style={styles.timerDisplay}>
            {/* Time Display */}
            <ThemedText type="defaultSemiBold" style={styles.timeText}>
              {formatElapsedTime(elapsedTime)}
            </ThemedText>
          </ThemedView>
        </ThemedView>

        {/* Control Button */}
        <TouchableOpacity
          style={styles.button}
          onPress={() => {
            if (isRunning) {
              stopTimerCounter()
              onStop()
            } else {
              onStart()
            }
          }}
        >
          <ThemedView style={styles.buttonContent}>
            <Ionicons name="stop" size={24} color="#fff" />
          </ThemedView>
        </TouchableOpacity>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    marginVertical: 16,
    backgroundColor: 'transparent',
    width: '100%',
  },
  card: {
    width: '100%',
    padding: 16,
    borderRadius: 8,
    backgroundColor: '#000',
    alignItems: 'center',
    flexDirection: 'row',
    justifyContent: 'space-between',
    gap: 2,
  },
  content: {
    alignItems: 'flex-start',
    justifyContent: 'center',
    backgroundColor: 'transparent',
    flex: 1,
  },
  description: {
    textAlign: 'left',
    fontSize: 18,
    fontWeight: '600',
    color: '#fff',
  },
  timerDisplay: {
    alignItems: 'flex-start',
    justifyContent: 'center',
    marginTop: 4,
    backgroundColor: 'transparent',
  },
  timeText: {
    fontSize: 28,
    fontFamily: 'monospace',
    fontWeight: '700',
    color: '#fff',
  },
  button: {
    padding: 12,
    borderRadius: 100,
    alignItems: 'center',
    justifyContent: 'center',
    aspectRatio: 1,
    backgroundColor: '#DC2626',
  },
  buttonContent: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'transparent',
    color: '#fff',
  },
})
```

The `TimerInputForm.tsx` component lets the user input a description for the timer and start it. Create `components/TimerInputForm.tsx` and paste in the following code:

```tsx {{ filename: 'components/TimerInputForm.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import React, { useState } from 'react'
import { StyleSheet, TextInput, TouchableOpacity } from 'react-native'
import { ThemedText } from './ThemedText'
import { ThemedView } from './ThemedView'

interface TimerInputFormProps {
  description: string
  onDescriptionChange: (text: string) => void
  onStartTimer: () => void
}

export function TimerInputForm({
  description,
  onDescriptionChange,
  onStartTimer,
}: TimerInputFormProps) {
  const [isInputFocused, setIsInputFocused] = useState(false)

  return (
    <ThemedView style={styles.formContent}>
      <ThemedText type="defaultSemiBold" style={styles.formLabel}>
        What are you working on?
      </ThemedText>
      <ThemedView style={styles.inputContainer}>
        <TextInput
          style={[styles.input, isInputFocused && styles.inputFocused]}
          value={description}
          onChangeText={onDescriptionChange}
          placeholder="Enter task description"
          placeholderTextColor="#aaa"
          selectionColor="#4f83cc"
          cursorColor="#4f83cc"
          onFocus={() => setIsInputFocused(true)}
          onBlur={() => setIsInputFocused(false)}
        />
        <TouchableOpacity style={styles.button} onPress={onStartTimer}>
          <ThemedView style={styles.buttonContent}>
            <Ionicons name="play" size={24} color="#fff" />
          </ThemedView>
        </TouchableOpacity>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  formContent: {
    gap: 8,
    alignItems: 'flex-start',
    justifyContent: 'center',
    width: '100%',
    backgroundColor: '#000',
    padding: 16,
    borderRadius: 8,
  },
  inputContainer: {
    gap: 8,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    width: '100%',
    backgroundColor: 'transparent',
  },
  input: {
    height: 48,
    borderWidth: 1,
    borderColor: '#333',
    borderRadius: 6,
    paddingHorizontal: 12,
    fontSize: 12,
    backgroundColor: '#222',
    color: '#fff',
    flex: 1,
  },
  inputFocused: {
    borderColor: '#4f83cc',
    borderWidth: 1,
  },
  buttonContent: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#2563EB',
    color: '#fff',
  },
  button: {
    padding: 12,
    alignItems: 'center',
    backgroundColor: '#2563EB',
    borderRadius: 100,
  },
  formLabel: {
    color: '#fff',
    marginBottom: 8,
  },
})
```

The `TimeEntryItem.tsx` component displays a single time entry and is used in a `FlatList` component which allows rendering a list of time entries and making it scrollable. This component also has a modal that allows the user to edit or delete the time entry. Create `components/TimeEntryItem.tsx` and paste in the following code:

```tsx {{ filename: 'components/TimeEntryItem.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import React, { useEffect, useRef, useState } from 'react'
import {
  Animated,
  Easing,
  Modal,
  StyleSheet,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native'
import { ThemedText } from './ThemedText'
import { ThemedView } from './ThemedView'

interface TimeEntry {
  id: string
  description: string
  start_time: string
  end_time: string | null
  created_at: string
}

interface TimeEntryItemProps {
  item: TimeEntry
  onUpdate?: (id: string, updates: Partial<TimeEntry>) => Promise<void>
  onDelete?: (id: string) => Promise<void>
}

// Custom slow spinner component
function SlowSpinner() {
  const spinValue = useRef(new Animated.Value(0)).current

  useEffect(() => {
    // Create a continuous rotation animation
    const startRotation = () => {
      // Reset the value to 0 when starting
      spinValue.setValue(0)

      // Create the animation
      Animated.timing(spinValue, {
        toValue: 1,
        duration: 3000, // Slower animation (3 seconds per rotation)
        easing: Easing.linear,
        useNativeDriver: true,
      }).start(() => startRotation()) // When complete, run again
    }

    // Start the animation loop
    startRotation()

    // Cleanup function
    return () => {
      // This will stop any pending animations when component unmounts
      spinValue.stopAnimation()
    }
  }, [spinValue])

  const spin = spinValue.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  })

  return (
    <Animated.View
      style={{
        transform: [{ rotate: spin }],
        width: 16,
        height: 16,
        borderWidth: 2,
        borderColor: '#2563EB',
        borderTopColor: 'transparent',
        borderRadius: 8,
      }}
    />
  )
}

export function TimeEntryItem({ item, onUpdate, onDelete }: TimeEntryItemProps) {
  const [isModalVisible, setIsModalVisible] = useState(false)
  const [editedDescription, setEditedDescription] = useState(item.description)
  const [startDate, setStartDate] = useState(new Date(item.start_time))
  const [endDate, setEndDate] = useState(item.end_time ? new Date(item.end_time) : null)
  const [isSubmitting, setIsSubmitting] = useState(false)

  // Track the text inputs separately from the actual date objects
  const [startDateText, setStartDateText] = useState('')
  const [endDateText, setEndDateText] = useState('')

  // Reset form state when modal is opened
  useEffect(() => {
    if (isModalVisible) {
      setEditedDescription(item.description)
      setStartDate(new Date(item.start_time))
      setEndDate(item.end_time ? new Date(item.end_time) : null)

      // Initialize text fields with formatted dates
      const formattedStartDate = formatDate(new Date(item.start_time).toISOString())
      const formattedStartTime = formatTime(new Date(item.start_time))
      setStartDateText(`${formattedStartDate} ${formattedStartTime}`)

      if (item.end_time) {
        const formattedEndDate = formatDate(new Date(item.end_time).toISOString())
        const formattedEndTime = formatTime(new Date(item.end_time))
        setEndDateText(`${formattedEndDate} ${formattedEndTime}`)
      } else {
        setEndDateText('')
      }
    }
  }, [isModalVisible, item.description, item.start_time, item.end_time])

  // Format date to display in a readable format
  const formatDate = (dateString: string) => {
    const date = new Date(dateString)
    return date.toLocaleDateString()
  }

  // Format time to display in a readable format
  const formatTime = (date: Date) => {
    return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
  }

  // Initialize date text fields on component mount
  useEffect(() => {
    if (startDate) {
      setStartDateText(`${formatDate(startDate.toISOString())} ${formatTime(startDate)}`)
    }
    if (endDate) {
      setEndDateText(`${formatDate(endDate.toISOString())} ${formatTime(endDate)}`)
    }
  }, [startDate, endDate])

  // Calculate duration between start and end time
  const calculateDuration = (start: string, end: string | null) => {
    if (!end) return 'In progress'

    const startDate = new Date(start)
    const endDate = new Date(end)
    const diffMs = endDate.getTime() - startDate.getTime()

    const diffMins = Math.floor(diffMs / 60000)
    const hours = Math.floor(diffMins / 60)
    const mins = diffMins % 60

    return `${hours}h${mins}m`
  }

  // Handle saving changes
  const handleSave = async () => {
    if (!onUpdate) return

    // Try to parse dates from text inputs before saving
    let validStartDate = startDate
    let validEndDate = endDate

    // Parse start date text
    try {
      const [datePart, timePart] = startDateText.split(' ')
      if (datePart && timePart) {
        const [month, day, year] = datePart.split('/')
        const [hours, minutes] = timePart.replace('AM', '').replace('PM', '').trim().split(':')

        if (month && day && year && hours && minutes) {
          const newDate = new Date()
          newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

          let hrs = parseInt(hours)
          if (timePart.includes('PM') && hrs < 12) hrs += 12
          if (timePart.includes('AM') && hrs === 12) hrs = 0

          newDate.setHours(hrs, parseInt(minutes))
          validStartDate = newDate
        }
      }
    } catch {
      // Use the existing startDate if parsing fails
    }

    // Parse end date text if it exists
    if (endDateText && endDate) {
      try {
        const [datePart, timePart] = endDateText.split(' ')
        if (datePart && timePart) {
          const [month, day, year] = datePart.split('/')
          const [hours, minutes] = timePart.replace('AM', '').replace('PM', '').trim().split(':')

          if (month && day && year && hours && minutes) {
            const newDate = new Date()
            newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

            let hrs = parseInt(hours)
            if (timePart.includes('PM') && hrs < 12) hrs += 12
            if (timePart.includes('AM') && hrs === 12) hrs = 0

            newDate.setHours(hrs, parseInt(minutes))
            validEndDate = newDate
          }
        }
      } catch {
        // Use the existing endDate if parsing fails
        console.error('Failed to parse end date')
      }
    }

    setIsSubmitting(true)
    try {
      await onUpdate(item.id, {
        description: editedDescription,
        start_time: validStartDate.toISOString(),
        end_time: validEndDate ? validEndDate.toISOString() : null,
      })
      setIsModalVisible(false)
    } catch (error) {
      console.error('Failed to update time entry:', error)
    } finally {
      setIsSubmitting(false)
    }
  }

  // Handle deleting the entry
  const handleDelete = async () => {
    if (!onDelete) return

    setIsSubmitting(true)
    try {
      await onDelete(item.id)
      setIsModalVisible(false)
    } catch (error) {
      console.error('Error deleting time entry:', error)
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <ThemedView style={styles.container}>
      {/* Row item */}
      <TouchableOpacity
        style={styles.entryItem}
        onPress={() => setIsModalVisible(true)}
        activeOpacity={0.7}
      >
        <ThemedView style={styles.entryContent}>
          <ThemedText type="defaultSemiBold" numberOfLines={1}>
            {item.description}
          </ThemedText>
          {item.end_time ? (
            <ThemedText style={styles.durationText}>
              {calculateDuration(item.start_time, item.end_time)}
            </ThemedText>
          ) : (
            <View style={styles.inProgressContainer}>
              <SlowSpinner />
              <ThemedText style={styles.inProgressText}>In progress</ThemedText>
            </View>
          )}
        </ThemedView>
        <View style={styles.rightSection}>
          <ThemedText style={styles.dateText}>{formatDate(item.start_time)}</ThemedText>
          <Ionicons name="chevron-forward" size={16} color="#888" />
        </View>
      </TouchableOpacity>

      {/* Edit modal */}
      <Modal
        visible={isModalVisible}
        onRequestClose={() => setIsModalVisible(false)}
        animationType="fade"
        transparent
      >
        <View style={styles.modalBackdrop}>
          <View style={styles.modalContainer}>
            <ThemedView style={styles.modalContent}>
              <ThemedText style={styles.modalTitle}>Edit Time Entry</ThemedText>

              <View style={styles.formGroup}>
                <ThemedText style={styles.label}>Description</ThemedText>
                <TextInput
                  style={styles.input}
                  value={editedDescription}
                  onChangeText={setEditedDescription}
                  placeholder="What were you working on?"
                  placeholderTextColor="#aaa"
                />
              </View>

              <View style={styles.formGroup}>
                <ThemedText style={styles.label}>Start Time</ThemedText>
                <TextInput
                  style={styles.input}
                  value={startDateText}
                  onChangeText={(text) => {
                    // Just update the text field without validation
                    setStartDateText(text)

                    // Try to parse the date but don't throw errors
                    try {
                      const [datePart, timePart] = text.split(' ')
                      if (datePart && timePart) {
                        const [month, day, year] = datePart.split('/')
                        const [hours, minutes] = timePart
                          .replace('AM', '')
                          .replace('PM', '')
                          .trim()
                          .split(':')

                        if (month && day && year && hours && minutes) {
                          const newDate = new Date(startDate)
                          newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

                          let hrs = parseInt(hours)
                          if (timePart.includes('PM') && hrs < 12) hrs += 12
                          if (timePart.includes('AM') && hrs === 12) hrs = 0

                          newDate.setHours(hrs, parseInt(minutes))
                          setStartDate(newDate)
                        }
                      }
                    } catch {
                      // Silently fail - we'll use the previous valid date if parsing fails
                    }
                  }}
                  placeholder="MM/DD/YYYY HH:MM AM/PM"
                  placeholderTextColor="#aaa"
                />
              </View>

              <View style={styles.formGroup}>
                <ThemedText style={styles.label}>End Time</ThemedText>
                {endDate ? (
                  <TextInput
                    style={styles.input}
                    value={endDateText}
                    onChangeText={(text) => {
                      // Just update the text field without validation
                      setEndDateText(text)

                      // Try to parse the date but don't throw errors
                      try {
                        const [datePart, timePart] = text.split(' ')
                        if (datePart && timePart) {
                          const [month, day, year] = datePart.split('/')
                          const [hours, minutes] = timePart
                            .replace('AM', '')
                            .replace('PM', '')
                            .trim()
                            .split(':')

                          if (month && day && year && hours && minutes) {
                            const newDate = new Date(endDate)
                            newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

                            let hrs = parseInt(hours)
                            if (timePart.includes('PM') && hrs < 12) hrs += 12
                            if (timePart.includes('AM') && hrs === 12) hrs = 0

                            newDate.setHours(hrs, parseInt(minutes))
                            setEndDate(newDate)
                          }
                        }
                      } catch {
                        // Silently fail - we'll use the previous valid date if parsing fails
                      }
                    }}
                    placeholder="MM/DD/YYYY HH:MM AM/PM"
                    placeholderTextColor="#aaa"
                  />
                ) : (
                  <View style={styles.inProgressContainer}>
                    <SlowSpinner />
                    <ThemedText style={styles.inProgressText}>In progress</ThemedText>
                  </View>
                )}
              </View>

              <View style={styles.buttonContainer}>
                <TouchableOpacity
                  style={styles.cancelButton}
                  onPress={() => setIsModalVisible(false)}
                  disabled={isSubmitting}
                >
                  <ThemedText style={styles.buttonText}>Cancel</ThemedText>
                </TouchableOpacity>
                <TouchableOpacity
                  style={[styles.button, styles.deleteButton]}
                  onPress={handleDelete}
                  disabled={isSubmitting}
                >
                  <ThemedText style={styles.buttonText}>Delete</ThemedText>
                </TouchableOpacity>
                <TouchableOpacity
                  style={[styles.button, styles.saveButton]}
                  onPress={handleSave}
                  disabled={isSubmitting}
                >
                  <ThemedText style={styles.buttonText}>Save</ThemedText>
                </TouchableOpacity>
              </View>
            </ThemedView>
          </View>
        </View>
      </Modal>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    marginVertical: 4,
  },
  modalContent: {
    padding: 16,
  },
  modalTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  entryItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    borderBottomWidth: 1,
    borderBottomColor: 'transparent',
    backgroundColor: '#f7f7f7',
    padding: 8,
    marginVertical: 4,
    borderRadius: 6,
    gap: 3,
    alignItems: 'center',
  },
  entryContent: {
    gap: 4,
    alignItems: 'flex-start',
    justifyContent: 'center',
    backgroundColor: 'transparent',
    flex: 1,
  },
  dateText: {
    fontSize: 12,
    color: '#888',
  },
  rightSection: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 4,
  },
  durationText: {
    fontWeight: 'bold',
    fontFamily: 'monospace',
  },
  inProgressContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 6,
    backgroundColor: '#EBF4FF', // Light blue background
    paddingVertical: 1,
    paddingHorizontal: 6,
    borderRadius: 100,
  },
  inProgressText: {
    color: '#2563EB',
    fontWeight: '600',
    fontSize: 12,
  },
  formGroup: {
    marginBottom: 16,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 6,
  },
  input: {
    borderWidth: 1,
    borderRadius: 6,
    padding: 10,
    borderColor: '#ccc',
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    gap: 10,
    marginTop: 20,
  },
  cancelButton: {
    backgroundColor: '#555',
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderRadius: 6,
  },
  button: {
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderRadius: 6,
    alignItems: 'center',
    justifyContent: 'center',
  },
  saveButton: {
    backgroundColor: '#2563EB',
  },
  deleteButton: {
    backgroundColor: '#DC2626',
  },
  buttonText: {
    color: 'white',
    fontWeight: '600',
  },
  modalBackdrop: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalContainer: {
    width: '90%',
    maxWidth: 500,
    borderRadius: 8,
    overflow: 'hidden',
  },
})
```

All three of these components are then used in `app/(tabs)/protected/index.tsx` to display the timer, history, and settings. The logic to run operations against Supabase is also implemented in that file and leveraged from the child components.

Update `app/(tabs)/protected/index.tsx` and replace all of the code with what's below:

```tsx {{ filename: 'app/(tabs)/protected/index.tsx', collapsible: true }}
import React, { useEffect, useRef, useState } from 'react'
import { FlatList, StyleSheet } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { TimeEntryItem } from '@/components/TimeEntryItem'
import { TimerCounter } from '@/components/TimerCounter'
import { TimerInputForm } from '@/components/TimerInputForm'
import { createSupabaseClerkClient } from '@/utils/supabase'
import { useAuth, useUser } from '@clerk/clerk-expo'

interface TimeEntry {
  id: string
  description: string
  start_time: string
  end_time: string | null
  created_at: string
}

export default function HomeScreen() {
  const { user } = useUser()
  const { getToken } = useAuth()
  const [description, setDescription] = useState('')
  const [isTimerRunning, setIsTimerRunning] = useState(false)
  const [currentEntryId, setCurrentEntryId] = useState<string | null>(null)
  const [timeEntries, setTimeEntries] = useState<TimeEntry[]>([])
  const [elapsedTime, setElapsedTime] = useState(0)
  // We track the start time to calculate elapsed time
  const startTimeRef = useRef<Date | null>(null)
  // Track the last time we updated the elapsed time
  const lastUpdateRef = useRef<number>(0)

  // Timer interval reference
  const timerIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)

  const supabase = createSupabaseClerkClient(getToken())

  // Stop the timer counter
  const stopTimerCounter = () => {
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current)
      timerIntervalRef.current = null
    }
    setElapsedTime(0)
    startTimeRef.current = null
  }

  // Start the timer counter
  const startTimerCounter = (initialStartTime?: Date) => {
    const start = initialStartTime || new Date()
    startTimeRef.current = start
    lastUpdateRef.current = Date.now()

    // Calculate initial elapsed time
    const now = new Date()
    const initialDiffInSeconds = Math.floor((now.getTime() - start.getTime()) / 1000)
    setElapsedTime(initialDiffInSeconds)

    // Clear any existing interval
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current)
      timerIntervalRef.current = null
    }

    // Set up the interval to update elapsed time every second
    const intervalId = setInterval(() => {
      if (startTimeRef.current) {
        // Use the current time for accurate timing
        const now = new Date()
        const diffInSeconds = Math.floor((now.getTime() - startTimeRef.current.getTime()) / 1000)

        // Only update if the time has actually changed
        if (diffInSeconds !== elapsedTime) {
          setElapsedTime(diffInSeconds)
        }
      }
    }, 500) // Update more frequently for better accuracy

    timerIntervalRef.current = intervalId
  }

  // Function to fetch time entries from Supabase
  const fetchTimeEntries = async () => {
    try {
      const { data, error } = await supabase
        .from('time_entries')
        .select('*')
        .order('start_time', { ascending: false })

      if (error) {
        console.error('Error fetching time entries:', error)
        return
      }

      setTimeEntries(data || [])

      // Check if there's an active timer (entry without end_time)
      const activeEntry = data?.find((entry) => !entry.end_time)
      if (activeEntry) {
        setIsTimerRunning(true)
        setCurrentEntryId(activeEntry.id)
        setDescription(activeEntry.description)

        // Start the timer counter with the saved start time
        const startDate = new Date(activeEntry.start_time)
        startTimerCounter(startDate)
      }
    } catch (error) {
      console.error('Error fetching time entries:', error)
    }
  }

  // Function to update a time entry
  const updateTimeEntry = async (id: string, updates: Partial<TimeEntry>) => {
    try {
      const { error } = await supabase.from('time_entries').update(updates).eq('id', id)

      if (error) {
        console.error('Error updating time entry:', error)
        return
      }

      // Refresh the time entries list
      fetchTimeEntries()
    } catch (error) {
      console.error('Error updating time entry:', error)
    }
  }

  // Function to delete a time entry
  const deleteTimeEntry = async (id: string) => {
    try {
      const { error } = await supabase.from('time_entries').delete().eq('id', id)

      if (error) {
        console.error('Error deleting time entry:', error)
        return
      }

      // Refresh the time entries list
      fetchTimeEntries()
    } catch (error) {
      console.error('Error deleting time entry:', error)
    }
  }

  // Start a new timer
  const startTimer = async () => {
    if (!description.trim()) {
      alert('Please enter what you are working on')
      return
    }

    try {
      const startDate = new Date()
      const { data, error } = await supabase
        .from('time_entries')
        .insert({
          description: description.trim(),
          start_time: startDate.toISOString(),
          owner_id: user?.id,
        })
        .select()

      if (error) {
        console.error('Error starting timer:', error)
        return
      }

      if (data && data[0]) {
        setIsTimerRunning(true)
        setCurrentEntryId(data[0].id)
        startTimerCounter(startDate)
        fetchTimeEntries()
      }
    } catch (error) {
      console.error('Error in startTimer:', error)
    }
  }

  // Stop the current timer
  const stopTimer = async () => {
    if (!currentEntryId) return

    try {
      const { error } = await supabase
        .from('time_entries')
        .update({ end_time: new Date().toISOString() })
        .eq('id', currentEntryId)

      if (error) {
        console.error('Error stopping timer:', error)
        return
      }

      setIsTimerRunning(false)
      setCurrentEntryId(null)
      setDescription('')
      stopTimerCounter()
      fetchTimeEntries()
    } catch (error) {
      console.error('Error in stopTimer:', error)
    }
  }

  // Update the elapsed time even when the app is in background
  useEffect(() => {
    if (isTimerRunning && startTimeRef.current) {
      const now = new Date()
      const diffInSeconds = Math.floor((now.getTime() - startTimeRef.current.getTime()) / 1000)
      setElapsedTime(diffInSeconds)
    }
  }, [isTimerRunning])

  // Fetch time entries when component mounts
  useEffect(() => {
    fetchTimeEntries()

    // Clean up timer interval when component unmounts
    return () => {
      if (timerIntervalRef.current) {
        clearInterval(timerIntervalRef.current)
        timerIntervalRef.current = null
      }
    }
  }, [])

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.titleContainer}>
        <ThemedText type="title">⏳ Aika Timer</ThemedText>
      </ThemedView>

      {/* Timer Form */}
      <ThemedView style={styles.formContainer}>
        {!isTimerRunning ? (
          <TimerInputForm
            description={description}
            onDescriptionChange={setDescription}
            onStartTimer={startTimer}
          />
        ) : (
          <TimerCounter
            isRunning={isTimerRunning}
            description={description}
            initialStartTime={startTimeRef.current}
            onStart={startTimer}
            onStop={stopTimer}
          />
        )}
      </ThemedView>

      {/* Time Entries List */}
      <ThemedView style={styles.entriesContainer}>
        <ThemedText type="subtitle" style={styles.entriesTitle}>
          Previous Work Logs
        </ThemedText>
        {timeEntries.length === 0 ? (
          <ThemedText style={styles.emptyText}>
            No work logs yet. Start tracking your time!
          </ThemedText>
        ) : (
          <FlatList
            data={timeEntries}
            keyExtractor={(item) => item.id}
            renderItem={({ item }) => (
              <TimeEntryItem item={item} onUpdate={updateTimeEntry} onDelete={deleteTimeEntry} />
            )}
            style={styles.list}
            scrollEnabled={true}
            showsVerticalScrollIndicator={true}
            contentContainerStyle={styles.listContentContainer}
          />
        )}
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
    marginTop: 16,
    paddingHorizontal: 16,
  },
  formContainer: {
    gap: 12,
    marginBottom: 24,
    borderRadius: 8,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    width: '100%',
    height: 150,
    paddingHorizontal: 16,
  },
  disabledInput: {
    backgroundColor: '#f0f0f0',
    color: '#666',
  },
  entriesContainer: {
    gap: 12,
    flex: 1,
  },
  list: {
    paddingHorizontal: 16,
  },
  listContentContainer: {
    flexGrow: 1,
    paddingBottom: 16,
  },
  entriesTitle: {
    paddingHorizontal: 16,
  },
  emptyText: {
    fontStyle: 'italic',
    color: '#888',
    marginTop: 8,
    paddingHorizontal: 16,
  },
  signOutButton: {
    backgroundColor: '#64748B',
    padding: 12,
    borderRadius: 6,
    alignItems: 'center',
    marginTop: 8,
  },
  container: {
    flex: 1,
    paddingTop: 16,
  },
  contentContainer: {
    padding: 16,
  },
})
```

Now access the application in your browser again and test out the timer functionality! You can also check the database to see the time entries being added:

![The Supabase dashboard with entries shown in the table](./image11.png)

### 6. Implementing the settings screen

The last thing you'll build in this demo is a simple settings screen that will display information about the currently logged in user and provide a method of signing out. Two helper functions from the Clerk SDK will be used: the `useUser` hook to get the currently logged in user and the `useAuth` hook to access the function required to sign out.

Create the `app/(tabs)/protected/settings.tsx` file and add the following code:

```typescript {{ filename: 'app/(tabs)/protected/settings.tsx' }}
import React from 'react'
import { Image, StyleSheet, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { useAuth, useUser } from '@clerk/clerk-expo'

export default function SettingsScreen() {
  const { user } = useUser()
  const { signOut } = useAuth()

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.titleContainer}>
        <ThemedText type="title">Settings</ThemedText>
      </ThemedView>

      <ThemedView style={styles.contentContainer}>
        {/* User Information */}
        <ThemedText type="subtitle">User Information</ThemedText>
        <ThemedView style={styles.userContainer}>
          <ThemedView style={styles.userImageContainer}>
            <Image source={{ uri: user?.imageUrl || '' }} style={styles.userImage} />
          </ThemedView>
          <ThemedView style={styles.userInfoContainer}>
            <ThemedText type="defaultSemiBold">
              {user?.firstName} {user?.lastName}
            </ThemedText>
            <ThemedText>{user?.emailAddresses[0].emailAddress}</ThemedText>
          </ThemedView>
        </ThemedView>

        {/* Sign Out Button */}
        <ThemedView style={styles.signOutButtonContainer}>
          <TouchableOpacity onPress={() => signOut()}>
            <ThemedText style={{ color: 'red' }}>Sign Out</ThemedText>
          </TouchableOpacity>
        </ThemedView>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 16,
  },
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
    marginTop: 16,
    paddingHorizontal: 16,
  },
  contentContainer: {
    gap: 12,
    marginBottom: 24,
    borderRadius: 8,
    padding: 16,
    width: '100%',
    height: 150,
    paddingHorizontal: 16,
  },
  userContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f7f7f7',
    padding: 8,
    borderRadius: 8,
  },
  userInfoContainer: {
    marginLeft: 12,
    flex: 1,
    backgroundColor: 'transparent',
  },
  userImageContainer: {
    width: 50,
    height: 50,
    borderRadius: 100,
    backgroundColor: 'transparent',
  },
  userImage: {
    width: 50,
    height: 50,
    borderRadius: 100,
    backgroundColor: 'transparent',
  },
  signOutButtonContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    borderRadius: 8,
    padding: 8,
    backgroundColor: '#ffebee',
    justifyContent: 'center',
  },
})
```

Expo will automatically add the new screen to the bottom tab bar so you can access it but the icon will not be present since it needs to be set in the layout file for the protected routes. Update `app/(tabs)/protected/_layout.tsx` to specify the configuration for that bar:

```tsx {{ filename: 'app/(tabs)/protected/_layout.tsx', ins: [[44, 50]], del: [[37, 43]] }}
import { Tabs } from 'expo-router'
import React from 'react'
import { Platform } from 'react-native'

import { HapticTab } from '@/components/HapticTab'
import { IconSymbol } from '@/components/ui/IconSymbol'
import TabBarBackground from '@/components/ui/TabBarBackground'
import { Colors } from '@/constants/Colors'
import { useColorScheme } from '@/hooks/useColorScheme'

export default function TabLayout() {
  const colorScheme = useColorScheme()

  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
        headerShown: false,
        tabBarButton: HapticTab,
        tabBarBackground: TabBarBackground,
        tabBarStyle: Platform.select({
          ios: {
            // Use a transparent background on iOS to show the blur effect
            position: 'absolute',
          },
          default: {},
        }),
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
        }}
      />
      <Tabs.Screen
        name="settings"
        options={{
          title: 'Settings',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="gear" color={color} />,
        }}
      />
    </Tabs>
  )
}
```

You'll also need to map the `gear` icon in the `IconSymbol` component so it displays in the tab bar:

```tsx {{ filename: 'components/ui/IconSymbol.tsx', ins: [21] }}
// Fallback for using MaterialIcons on Android and web.

import MaterialIcons from '@expo/vector-icons/MaterialIcons'
import { SymbolViewProps, SymbolWeight } from 'expo-symbols'
import { ComponentProps } from 'react'
import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native'

type IconMapping = Record<SymbolViewProps['name'], ComponentProps<typeof MaterialIcons>['name']>
type IconSymbolName = keyof typeof MAPPING

/**
 * Add your SF Symbols to Material Icons mappings here.
 * - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
 * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
 */
const MAPPING = {
  'house.fill': 'home',
  'paperplane.fill': 'send',
  'chevron.left.forwardslash.chevron.right': 'code',
  'chevron.right': 'chevron-right',
  gear: 'settings',
} as IconMapping

/**
 * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
 * This ensures a consistent look across platforms, and optimal resource usage.
 * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
 */
export function IconSymbol({
  name,
  size = 24,
  color,
  style,
}: {
  name: IconSymbolName
  size?: number
  color: string | OpaqueColorValue
  style?: StyleProp<TextStyle>
  weight?: SymbolWeight
}) {
  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />
}
```

Finally, delete the `app/(tabs)/protected/explore.tsx` screen since it's no longer used.

## Conclusion

You've successfully built a fully functional time tracking app using React Native, Expo, Clerk, and Supabase. Through this article, you've learned how to implement secure authentication with Clerk, set up a Supabase database with proper security using Row Level Security that integrates with Clerk, and build a timer interface that allows users to track their time.

The combination of React Native, Expo, Clerk, and Supabase provides a solid foundation for building secure, scalable applications that can grow with your needs. In the next article, we'll explore how to easily add multitenancy with Clerk's B2B tools, making Aika a suitable option for teams and organizations looking to track time for their employees!

*Updated February 12, 2026*

---

# Postmortem: DNS Provider Outage (February 10, 2026)
URL: https://clerk.com/blog/2026-02-10-dns-outage-postmortem.md
Date: 2026-02-10
Category: Company
Description: A detailed post-mortem of the DNS provider outage that occurred on February 10, 2026, including impact analysis and planned remediations.


On February 10, 2026 at 17:06 UTC, Clerk experienced an outage caused by a failure at our DNS provider. During this period, authoritative DNS resolution for Clerk's APIs was unavailable, which prevented some traffic from reaching our infrastructure despite our underlying services remaining healthy.

Although authoritative DNS resolution was unavailable for 2 hours and 32 minutes, successful request volume to our APIs remained above 95% of the expected volume during this period. This was due to a combination of DNS caching, fallback DNS servers, and the emergency guidance issued to customers.

## Impact

- For applications and end users that were unable to resolve our APIs, the Clerk service faced a complete outage.
- For Clerk customers that were unable to resolve our clerk.com, our Dashboard was inaccessible.
- For mail clients that were unable to resolve DKIM and SPF records, authentication emails were undelivered or went to spam.

## Planned remediations

### Failover preparation

Unfortunately, Clerk did not have a failover prepared for the event of a complete outage at our DNS provider. Although DNS provider redundancy had been planned for 2026, it sits behind other infrastructure improvements that we believed were higher risk to the service. At present, we are working to add redundancy to delivery of authentication emails.

We determined DNS to be low risk based on the inherent resilience of DNS's design (e.g. multiple nameservers and long TTLs), and based on our provider's stated approach to resiliency. They operate 4 nameservers across 2 ASNs, with different architectures on each ASN. They hadn't faced a meaningful incident in Clerk's lifetime until Tuesday.

Although we are glad our assessment was accurate about the DNS system design (which we believe was the primary reason this outage impacted less than 5% of our expected traffic), we were disappointed that our provider's strategy for resilience failed. We were aware that their architecture could readily lead to 2/4 nameservers failing, but it was unexpected that all 4 would fail at once.

Immediately following the incident, we began preparing a truly independent DNS provider for failover, which we expect to be complete within a few weeks.

### Decoupling DNS provider and domain registrar

Our DNS provider also offers domain registrar services, and Clerk has registered several domains with them over the years. During the incident, we discovered that one of our critical domains uses this registrar, and that meant that we could not make emergency adjustments to its nameservers.

Going forward, we will ensure that our DNS provider is completely decoupled from our registrar, so that our ability to perform emergency nameserver changes will be unimpacted by an outage at our DNS provider.

### Improved alerting

We were not alerted to this incident promptly because our alerting tools do not explicitly check the uptime of our authoritative nameservers. We believe our uptime checks during the incident were failing to find an issue because they benefited from the long TTLs of DNS records.

For the future, we are improving our alerts to explicitly check the health of our authoritative nameservers. This will allow us to initiate failover processes more quickly.

## Closing

We are deeply sorry for the disruption this incident caused to you and your users. We understand that you depend on Clerk to be available, and we failed to meet that expectation. While we are fortunate the impact was limited in this case, we know that a partial outage erodes the trust you place in us.

We are committed to earning back that trust through action, not words. The remediations outlined above are already underway, and we will continue to invest in making Clerk's infrastructure more resilient. Thank you for your patience and continued partnership.

---

# How do I implement passkeys in Next.js?
URL: https://clerk.com/blog/how-do-i-implement-passkeys-in-nextjs.md
Date: 2025-12-09
Category: Guides
Description: Learn how passkeys enable passwordless authentication with phishing-resistant cryptography. This tutorial walks through a complete Next.js WebAuthn implementation, covering registration and authentication flows.

Passkeys promise a future where you never have to remember a password again and where phishing emails and [credential stuffing attacks](https://en.wikipedia.org/wiki/Credential_stuffing) stop working.

If you're building or scaling a product, they reduce risk, lower support load, and ship a sign-in experience that doesn't feel like a tax on conversion. This article unpacks what [passkeys](/docs/guides/configure/auth-strategies/sign-up-sign-in-options#passkeys) are, why they're more secure than traditional authentication methods, and how they fit into today's browser and device ecosystem.

We'll then walk through a concrete Next.js implementation, focusing on both the registration flow (creating a passkey) and the authentication flow (signing in with it). You'll see how the browser, authenticator, and server coordinate using WebAuthn challenges, and how to wire it into a practical, end-to-end passkey experience.

**TL;DR**

- Passkeys replace passwords with public-key cryptography: the device generates a key pair, keeps the private key locked behind biometrics or a PIN, and the server stores only the public key.
- During sign-in, the server issues a one-time challenge that the device signs, proving possession without transmitting secrets.
- Passkeys are phishing-resistant (private key only responds to the original domain) and remove reusable credentials from your database.
- In Next.js, you can implement passkeys manually using the [SimpleWebAuthn library](https://github.com/MasterKale/SimpleWebAuthn) with two round-trips each for registration and authentication.
- Or skip the complexity: [Clerk supports passkeys](#clerk-makes-passkeys-simple) with a single dashboard toggle and one component.

## What are passkeys?

Passkeys are a modern, phishing-resistant form of passwordless authentication that replaces traditional passwords with cryptographic credentials tied to a user's device and unlocked locally via biometric authentication (for example, Face ID, Touch ID, or Windows Hello) or a device PIN. When a user registers, the device generates a public/private key pair. The server stores only the public key and challenges the device to prove possession of the private key during sign-in, so the private key never leaves the device. Passkeys provide multi-factor assurance because they combine something-you-have (the device) with something-you-are or something-you-know (biometric or PIN that unlocks the key), and they reduce server-side risk since attackers have no reusable secrets to steal.

### Key benefits of passkey authentication

- **Passwordless authentication**: No passwords to create, remember, or leak.
- **MFA built in by default**: Device possession + local biometric or PIN unlock (or security key).
- **Phishing-resistant**: Private keys only sign challenges for the registering origin.
- **Reduced server liability**: Servers store only public keys, not reusable secrets.
- **Simpler user experience**: Faster sign-in flows and fewer account recovery calls.
- **Cross-device sync**: Platforms often let users sync passkeys between their devices for seamless access (implementation and privacy depend on the vendor).
- **Attestation & device validation**: Attestation is cryptographic proof that an authenticator provides about its identity and security properties (e.g., make, model, certification level). This optional feature helps servers verify authenticator properties during registration.
- **Interoperability**: Major browsers and platforms support passkeys via WebAuthn and CTAP standards.

> \[!NOTE]
> WebAuthn is a W3C standard and part of the [FIDO2 specification](https://fidoalliance.org/passkeys/) that enables websites to authenticate users via public-key cryptography, allowing secure, phishing-resistant sign-ins through biometrics, security keys, or platform authenticators. You can learn more about WebAuthn on the [World Wide Web Consortium website](https://www.w3.org/TR/webauthn/).

## Implementing passkeys in Next.js with WebAuthn

Building a complete passkeys implementation in Next.js from scratch would require many steps. Instead, we'll dive into a pre-built implementation and cover the important parts of the application as they pertain to passkeys to provide a clear understanding of how they work. The goal is to give you enough detail to evaluate effort and risk for your team, not to turn you into a security engineer.

The code for this article is available [on GitHub](https://github.com/clerk/nextjs-passkeys-demo). Feel free to review it in your browser or clone the repo to your machine to explore it. We'll cover the code in two sections:

- **Registration** will explain how a new user gets signed up with an application using a passkey.
- **Authentication** explains how a returning user signs in with their passkey.

This guide assumes familiarity with Next.js App Router, basic TypeScript, and a database for storing user credentials. The demo uses Postgres, but any database works.

> \[!NOTE]
> For developers looking to understand [Next.js authentication](/blog/nextjs-authentication) more broadly, we have a comprehensive guide covering multiple authentication strategies.

### Passkey registration flow

Before looking at the code, let's cover the workflow at a high level. The following diagram visually shows how areas of the application communicate with each other. Additional details for each step of the flow can be found below the image.

![Passkeys registration sequence diagram](./passkeys-registration-diagram.png)

1. **User enters username** - The user types a username (and other data required for sign-up) into the registration form in the browser.
2. **Browser requests registration options** - The browser sends a request to the server asking for WebAuthn registration options for this user.
3. **Server prepares options** - The server creates the user record, generates a fresh random challenge, configures passkey settings, and returns the registration options to the browser.
4. **Browser shows native registration prompt** - The browser or OS shows a built-in prompt such as "Create a passkey" or "Use Face ID to register".
5. **User approves with biometrics or a security key** - The user completes the biometric check or interacts with their security key so the authenticator can proceed.
6. **Authenticator creates a new key pair** - The authenticator creates a new public/private key pair for this site. The private key stays on the device and is never sent to the server.
7. **Browser sends credential to the server** - The browser sends the new credential data (including the public key and associated identifiers) back to the server.
8. **Server validates and stores the credential** - The server validates the challenge and signature, then stores the credential ID, public key, and metadata so this passkey can be used for future logins.
9. **Registration completes** - The server responds with success, and the browser updates the UI to indicate that passkey registration is complete.

#### The client-side registration component

Now that you understand how the workflow operates, let's dive into the code, starting with the `PasskeyRegister` component. The demo application mounts this component on the homepage and allows the user to enter their desired username and a display name. When the user submits the form, the component first checks the current browser for WebAuthn support, then sends the data to the `/api/register/options` endpoint.

The code uses the [SimpleWebAuthn library](https://github.com/MasterKale/SimpleWebAuthn) to handle the WebAuthn protocol details:

```tsx {{ filename: 'src/components/PasskeyRegister.tsx', fold: [[1, 17], [83, 140]] }}
'use client'

import { useState } from 'react'
import { startRegistration } from '@simplewebauthn/browser'
import type { PublicKeyCredentialCreationOptionsJSON } from '@simplewebauthn/browser'

interface Props {
  onSuccess?: () => void
}

export default function PasskeyRegister({ onSuccess }: Props) {
  const [username, setUsername] = useState('')
  const [displayName, setDisplayName] = useState('')
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  const [success, setSuccess] = useState(false)

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setError('')
    setSuccess(false)
    setLoading(true)

    try {
      // Check if the browser supports WebAuthn
      // This API is required for passkey operations
      if (!window.PublicKeyCredential) {
        throw new Error('WebAuthn is not supported in this browser')
      }

      // Request registration options from the server
      // The server generates a cryptographic challenge and configuration
      // that defines how the passkey should be created
      const optionsRes = await fetch('/api/register/options', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, displayName }),
      })

      if (!optionsRes.ok) {
        const errorData = await optionsRes.json()
        throw new Error(errorData.error || 'Failed to get registration options')
      }

      const options: PublicKeyCredentialCreationOptionsJSON = await optionsRes.json()

      // Trigger the browser's passkey creation flow
      // This prompts the user to authenticate (e.g., fingerprint, Face ID)
      // and creates a new public-private key pair
      const credential = await startRegistration({ optionsJSON: options })

      // Send the new credential to the server for verification
      // The server validates the response and stores the public key
      const verifyPayload = {
        ...credential,
        username,
      }

      const verifyRes = await fetch('/api/register/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(verifyPayload),
      })

      if (!verifyRes.ok) {
        const errorData = await verifyRes.json()
        throw new Error(errorData.error || 'Registration verification failed')
      }

      // Step 5: Registration complete - reset form and notify parent
      setSuccess(true)
      setUsername('')
      setDisplayName('')
      onSuccess?.()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Registration failed')
    } finally {
      setLoading(false)
    }
  }

  // Expand to see the UI code
  return (
    <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-md">
      <h2 className="mb-4 text-2xl font-bold">Register Passkey</h2>

      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label htmlFor="register-username" className="mb-1 block text-sm font-medium">
            Username
          </label>
          <input
            id="register-username"
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            required
            disabled={loading}
            className="w-full rounded-md border border-gray-300 px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:outline-none"
          />
        </div>

        <div>
          <label htmlFor="register-displayname" className="mb-1 block text-sm font-medium">
            Display Name
          </label>
          <input
            id="register-displayname"
            type="text"
            value={displayName}
            onChange={(e) => setDisplayName(e.target.value)}
            required
            disabled={loading}
            className="w-full rounded-md border border-gray-300 px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:outline-none"
          />
        </div>

        <button
          type="submit"
          disabled={loading}
          className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-400"
        >
          {loading ? 'Registering...' : 'Register'}
        </button>
      </form>

      {error && (
        <div className="mt-4 rounded border border-red-400 bg-red-100 p-3 text-red-700">
          {error}
        </div>
      )}

      {success && (
        <div className="mt-4 rounded border border-green-400 bg-green-100 p-3 text-green-700">
          Passkey registered successfully!
        </div>
      )}
    </div>
  )
}
```

#### Generating passkey registration options on the server

When the server receives the form data, it creates the user record in the database. Using the `@simplewebauthn/server` package, the server creates a series of options and stores a cryptographic challenge in the database. This challenge is a high-entropy, single-use string that the next step of the process uses.

These options define the relying party and user identity, configure how the authenticator should behave (for example, preferring passkey-style resident credentials and user verification), and use the timeout and generated challenge to control how long and under what conditions the client (typically the user's browser) will allow the registration to continue.

The server returns the object created from the options to the client along with the challenge.

```tsx {{ filename: 'src/app/api/register/options/route.ts', fold: [[1, 7]] }}
import { NextRequest, NextResponse } from 'next/server'
import { generateRegistrationOptions } from '@simplewebauthn/server'
import { findOrCreateUser, insertChallenge } from '@/lib/db'
import type { RegistrationOptionsRequest } from '@/types/webauthn'

export const runtime = 'nodejs'

export async function POST(request: NextRequest) {
  try {
    // Parse and validate the incoming request
    const body: RegistrationOptionsRequest = await request.json()
    const { username, displayName } = body

    if (!username || !displayName) {
      return NextResponse.json({ error: 'Username and displayName are required' }, { status: 400 })
    }

    // Create the user record or retrieve existing one
    const user = await findOrCreateUser(username, displayName)

    // Configure the Relying Party (RP) - your application's identity
    // The rpId must match the domain where passkeys will be used
    const rpId = process.env.NEXT_PUBLIC_RP_ID || 'localhost'
    const rpName = 'Passkey Demo'

    // Generate WebAuthn registration options
    // This creates a cryptographic challenge the client must sign
    const options = await generateRegistrationOptions({
      rpName,
      rpID: rpId,
      userName: username,
      userDisplayName: displayName,
      // 'none' means we don't need hardware attestation certificates
      attestationType: 'none',
      authenticatorSelection: {
        // 'preferred' allows both passkeys and security keys
        residentKey: 'preferred',
        // 'preferred' requests biometric/PIN verification when available
        userVerification: 'preferred',
      },
      timeout: 60000,
    })

    // Store the challenge in the database for verification later
    // The challenge expires after 60 seconds for security
    await insertChallenge(user.id, 'registration', options.challenge, 60000)

    // Return options to client - disable caching since challenge is single-use
    return NextResponse.json(options, {
      headers: {
        'Cache-Control': 'no-store',
      },
    })
  } catch (error) {
    console.error('Registration options error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}
```

The client receives the challenge and generates a public/private key pair. The private key is stored on the device that created it and never leaves that device. It is used to digitally sign the challenge that was received from the server. The client also creates a "credential ID," which uniquely identifies that device and is stored in the database, enabling a single account to register multiple passkeys if needed.

Once complete, the client sends the signed challenge, public key, and credential ID back to the server. The `startRegistration` function provided by `@simplewebauthn/browser` triggers this second trip to the server (shown in the client-side code above).

> \[!NOTE]
> Most browsers support using smartphones to create and store passkeys. When using this option, browsers often present a QR code to create a secure tunnel (using Bluetooth or Wi-Fi) between the computer and the smartphone to complete the registration process.

#### Verifying the passkey registration response

When the server receives this payload, it retrieves the user and challenge from the database. Using the provided public key, the server verifies the signature and (assuming the verification succeeds) stores that public key as a passkey for the user.

```tsx {{ filename: 'src/app/api/register/verify/route.ts', fold: [[1, 22]] }}
import { NextRequest, NextResponse } from 'next/server'
import { verifyRegistrationResponse } from '@simplewebauthn/server'
import type { RegistrationResponseJSON } from '@simplewebauthn/server'
import { isoBase64URL } from '@simplewebauthn/server/helpers'
import { getUserByUsername, consumeChallenge, insertCredential } from '@/lib/db'

export const runtime = 'nodejs'

interface VerifyRequest {
  username: string
  id: string
  rawId: string
  response: {
    clientDataJSON: string
    attestationObject: string
    transports?: AuthenticatorTransport[]
  }
  type: string
  clientExtensionResults?: Record<string, unknown>
  authenticatorAttachment?: string
}

export async function POST(request: NextRequest) {
  try {
    const body: VerifyRequest = await request.json()
    const { username, ...credential } = body

    if (!username || !credential.id) {
      return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
    }

    // Look up the user who initiated registration from the database
    const user = await getUserByUsername(username)
    if (!user) {
      return NextResponse.json({ error: 'User not found' }, { status: 404 })
    }

    // Decode the base64url-encoded client data to access the challenge
    // The client data contains the challenge, origin, and type of operation
    const clientDataBytes = isoBase64URL.toBuffer(credential.response.clientDataJSON)
    const clientDataJSON = JSON.parse(new TextDecoder().decode(clientDataBytes))

    // Verify the challenge matches what we stored and hasn't expired
    // This prevents replay attacks - each challenge can only be used once
    const challengeValid = await consumeChallenge(user.id, 'registration', clientDataJSON.challenge)
    if (!challengeValid) {
      return NextResponse.json({ error: 'Invalid or expired challenge' }, { status: 400 })
    }

    // These must match the values used during registration options
    const rpId = process.env.NEXT_PUBLIC_RP_ID || 'localhost'
    const expectedOrigin = process.env.NEXT_PUBLIC_ORIGIN || 'http://localhost:3000'

    // Cryptographically verify the registration response
    // This checks the signature, origin, and challenge
    const verification = await verifyRegistrationResponse({
      response: credential as RegistrationResponseJSON,
      expectedChallenge: clientDataJSON.challenge,
      expectedOrigin,
      expectedRPID: rpId,
      requireUserVerification: false,
    })

    if (!verification.verified || !verification.registrationInfo) {
      return NextResponse.json({ error: 'Registration verification failed' }, { status: 400 })
    }

    // Extract the credential info we need to store
    // aaguid identifies the authenticator model (useful for security policies)
    const { credential: dbCredential, aaguid } = verification.registrationInfo

    // Convert the raw public key bytes to PEM format for database storage
    // PEM is a standard format that's easy to store and retrieve
    const publicKeyPem = `-----BEGIN PUBLIC KEY-----\n${Buffer.from(dbCredential.publicKey)
      .toString('base64')
      .match(/.{1,64}/g)
      ?.join('\n')}\n-----END PUBLIC KEY-----\n`

    // Store the credential - this public key will verify future sign-ins
    await insertCredential(
      user.id,
      dbCredential.id,
      publicKeyPem,
      dbCredential.counter, // Counter helps detect cloned credentials (see the callout below for more details.)
      credential.response.transports, // How the authenticator communicates (USB, NFC, etc.)
      aaguid,
    )

    return NextResponse.json(
      { ok: true },
      {
        headers: {
          'Cache-Control': 'no-store',
        },
      },
    )
  } catch (error) {
    console.error('Registration verification error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}
```

> \[!NOTE]
> Passkey clients automatically track the number of times a passkey is used. A best practice is to track the count server-side as well. If the client sends a count that the server does not expect, it could indicate that a passkey has leaked. You'll see below that the counter is incremented each time a passkey is used for authentication.

Once this process completes, the server deletes the challenge from the database. Since each challenge has an expiration time (60 seconds in this demo), the entire flow must complete within the specified time; otherwise, it becomes invalid.

### Passkey authentication flow

Authenticating with passkeys follows a similar flow to registration. While the details of each step are different, the number of steps involved in authentication is the same:

![Passkeys authentication sequence diagram](./passkeys-authentication-diagram.png)

1. **User enters username** - The user types their username into the sign-in form in the browser.
2. **Browser requests authentication options** - The browser sends a request to the server asking for WebAuthn options (including a fresh challenge) for this username.
3. **Server prepares options** - The server looks up the user, finds their registered passkeys, generates a new random challenge, and sends the authentication options back to the browser.
4. **Browser shows native authentication prompt** - The browser or OS shows a built-in prompt such as "Sign in with Face ID?" or "Use your security key".
5. **User approves with a passkey** - The user completes the biometric check or uses their security key. The authenticator unlocks the private key stored on the device.
6. **Authenticator signs the challenge** - The authenticator signs the challenge (and related data) with the private key. The private key never leaves the device.
7. **Browser sends the signed result to the server** - The browser sends the credential data (including the signature and identifiers) to the server for verification.
8. **Server verifies the response** - The server looks up the matching public key, checks that the challenge is valid and unused, and verifies the signature and counter.
9. **User is signed in** - If everything checks out, the server responds with success and the browser updates the UI to show that the user is signed in (and typically establishes a session or issues a token).

#### The client-side authentication component

In the `PasskeyAuth` component, the user enters their username and submits the form. The component sends this data to the `/api/auth/options` endpoint.

```tsx {{ filename: 'src/components/PasskeyAuth.tsx', fold: [[1, 16], [77, 119]] }}
'use client'

import { useState } from 'react'
import { startAuthentication } from '@simplewebauthn/browser'
import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/browser'

interface Props {
  onSuccess?: () => void
}

export default function PasskeyAuth({ onSuccess }: Props) {
  const [username, setUsername] = useState('')
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  const [success, setSuccess] = useState(false)

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setError('')
    setSuccess(false)
    setLoading(true)

    try {
      // Verify the browser supports WebAuthn
      if (!window.PublicKeyCredential) {
        throw new Error('WebAuthn is not supported in this browser')
      }

      // Request authentication options (including challenge) from server
      // The server looks up the user's registered passkeys
      const optionsRes = await fetch('/api/auth/options', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username }),
      })

      if (!optionsRes.ok) {
        const errorData = await optionsRes.json()
        throw new Error(errorData.error || 'Failed to get authentication options')
      }

      const options: PublicKeyCredentialRequestOptionsJSON = await optionsRes.json()

      // Trigger the browser's passkey authentication flow
      // This prompts for biometric/PIN and signs the challenge
      const credential = await startAuthentication({ optionsJSON: options })

      // Send the signed challenge to the server for verification
      const verifyPayload = {
        ...credential,
        username,
      }

      const verifyRes = await fetch('/api/auth/verify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(verifyPayload),
      })

      if (!verifyRes.ok) {
        const errorData = await verifyRes.json()
        throw new Error(errorData.error || 'Authentication verification failed')
      }

      // Authentication successful
      setSuccess(true)
      setUsername('')
      onSuccess?.()
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Authentication failed')
    } finally {
      setLoading(false)
    }
  }

  // Expand to see the UI code
  return (
    <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-md">
      <h2 className="mb-4 text-2xl font-bold">Sign In with Passkey</h2>

      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label htmlFor="auth-username" className="mb-1 block text-sm font-medium">
            Username
          </label>
          <input
            id="auth-username"
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            required
            disabled={loading}
            className="w-full rounded-md border border-gray-300 px-3 py-2 focus:ring-2 focus:ring-green-500 focus:outline-none"
          />
        </div>

        <button
          type="submit"
          disabled={loading}
          className="w-full rounded-md bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:cursor-not-allowed disabled:bg-gray-400"
        >
          {loading ? 'Signing in...' : 'Sign In'}
        </button>
      </form>

      {error && (
        <div className="mt-4 rounded border border-red-400 bg-red-100 p-3 text-red-700">
          {error}
        </div>
      )}

      {success && (
        <div className="mt-4 rounded border border-green-400 bg-green-100 p-3 text-green-700">
          Authenticated successfully!
        </div>
      )}
    </div>
  )
}
```

#### Generating passkey authentication options on the server

On the server, the user is retrieved from the database along with their registered passkeys. The server creates authentication options using details for each of the user's credentials (allowing the use of any passkeys the user owns), and stores the challenge in the database before sending the options back to the client.

```tsx {{ filename: 'src/app/api/auth/options/route.ts', fold: [[1, 7]] }}
import { NextRequest, NextResponse } from 'next/server'
import { generateAuthenticationOptions } from '@simplewebauthn/server'
import { getUserByUsername, getCredentialsByUserId, insertChallenge } from '@/lib/db'
import type { AuthenticationOptionsRequest } from '@/types/webauthn'

export const runtime = 'nodejs'

export async function POST(request: NextRequest) {
  try {
    const body: AuthenticationOptionsRequest = await request.json()
    const { username } = body

    if (!username) {
      return NextResponse.json({ error: 'Username is required' }, { status: 400 })
    }

    // Find the user attempting to authenticate from the database
    const user = await getUserByUsername(username)
    if (!user) {
      return NextResponse.json({ error: 'User not found' }, { status: 404 })
    }

    // Retrieve all passkeys registered to this user
    // Users can have multiple passkeys (e.g., laptop + phone)
    const credentials = await getCredentialsByUserId(user.id)
    if (credentials.length === 0) {
      return NextResponse.json({ error: 'No credentials found for user' }, { status: 404 })
    }

    const rpId = process.env.NEXT_PUBLIC_RP_ID || 'localhost'

    // Generate authentication options with a fresh challenge
    const options = await generateAuthenticationOptions({
      rpID: rpId,
      // List all valid credentials - the authenticator will use one it recognizes
      allowCredentials: credentials.map((cred) => ({
        id: cred.credential_id,
        type: 'public-key',
        transports: cred.transports as AuthenticatorTransport[] | undefined,
      })),
      userVerification: 'preferred',
      timeout: 60000,
    })

    // Store the challenge for verification - expires in 60 seconds
    await insertChallenge(user.id, 'authentication', options.challenge, 60000)

    return NextResponse.json(options, {
      headers: {
        'Cache-Control': 'no-store',
      },
    })
  } catch (error) {
    console.error('Authentication options error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}
```

#### Verifying the passkey authentication response

Assuming the client has access to one of the registered passkeys, the private key associated with that passkey signs the challenge and sends the signature back to the server for verification. As with registration, `@simplewebauthn/browser` handles the second trip back to the server, this time with the `startAuthentication` method. Once the client signs the challenge, it sends the signed challenge to the `/api/auth/verify` endpoint for verification.

```tsx {{ filename: 'src/app/api/auth/verify/route.ts', fold: [[1, 28]] }}
import { NextRequest, NextResponse } from 'next/server'
import { verifyAuthenticationResponse } from '@simplewebauthn/server'
import type { AuthenticationResponseJSON } from '@simplewebauthn/server'
import { isoBase64URL } from '@simplewebauthn/server/helpers'
import {
  getUserByUsername,
  consumeChallenge,
  getCredentialById,
  updateCredentialSignCount,
} from '@/lib/db'

export const runtime = 'nodejs'

interface VerifyRequest {
  username: string
  id: string
  rawId: string
  response: {
    clientDataJSON: string
    authenticatorData: string
    signature: string
    userHandle?: string
  }
  type: string
  clientExtensionResults?: Record<string, unknown>
  authenticatorAttachment?: string
}

export async function POST(request: NextRequest) {
  try {
    const body: VerifyRequest = await request.json()
    const { username, ...credential } = body

    if (!username || !credential.id) {
      return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
    }

    // Find the user attempting to sign in from the database
    const user = await getUserByUsername(username)
    if (!user) {
      return NextResponse.json({ error: 'User not found' }, { status: 404 })
    }

    // Decode the client data to extract the challenge
    const clientDataBytes = isoBase64URL.toBuffer(credential.response.clientDataJSON)
    const clientDataJSON = JSON.parse(new TextDecoder().decode(clientDataBytes))

    // Verify the challenge is valid and mark it as used
    // This prevents replay attacks
    const challengeValid = await consumeChallenge(
      user.id,
      'authentication',
      clientDataJSON.challenge,
    )
    if (!challengeValid) {
      return NextResponse.json({ error: 'Invalid or expired challenge' }, { status: 400 })
    }

    // Load the stored credential to get the public key
    // Also verify this credential belongs to this user
    const dbCredential = await getCredentialById(credential.id)
    if (!dbCredential || dbCredential.user_id !== user.id) {
      return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
    }

    const rpId = process.env.NEXT_PUBLIC_RP_ID || 'localhost'
    const expectedOrigin = process.env.NEXT_PUBLIC_ORIGIN || 'http://localhost:3000'

    // Convert the stored PEM public key back to raw bytes
    const publicKeyPem = dbCredential.public_key
    const publicKeyBase64 = publicKeyPem
      .replace(/-----BEGIN PUBLIC KEY-----/, '')
      .replace(/-----END PUBLIC KEY-----/, '')
      .replace(/\s/g, '')
    const publicKeyBytes = Buffer.from(publicKeyBase64, 'base64')

    // Cryptographically verify the signed challenge
    // This proves the user has the private key (stored on their device)
    const verification = await verifyAuthenticationResponse({
      response: credential as AuthenticationResponseJSON,
      expectedChallenge: clientDataJSON.challenge,
      expectedOrigin,
      expectedRPID: rpId,
      credential: {
        id: credential.id,
        publicKey: new Uint8Array(publicKeyBytes),
        counter: dbCredential.sign_count, // Used to detect cloned credentials
      },
      requireUserVerification: false,
    })

    if (!verification.verified) {
      return NextResponse.json({ error: 'Authentication verification failed' }, { status: 401 })
    }

    // Update the sign count to track usage
    // If the count ever goes backwards, it may indicate a cloned credential
    const newCounter = verification.authenticationInfo.newCounter
    await updateCredentialSignCount(credential.id, newCounter)

    // Authentication successful - at this point you would typically
    // create a session, set a cookie, or issue a JWT
    return NextResponse.json(
      { ok: true },
      {
        headers: {
          'Cache-Control': 'no-store',
        },
      },
    )
  } catch (error) {
    console.error('Authentication verification error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}
```

A verified signature indicates the user is authenticated. At this point, you'd typically create a session and set a cookie or return a JWT.

## Clerk makes passkeys simple

Clerk supports passkeys out of the box with a simple toggle in our [dashboard](https://dashboard.clerk.com):

![The Clerk dashboard with the passkeys toggle highlighted](./dashboard.png)

Once enabled, any mounted `<SignIn />` or `<SignUp />` components will present your users with an option to register or authenticate using passkeys, supporting the workflows described above without building, testing, or maintaining code.

![Sign In form with passkeys option highlighted](./sign-in.png)

All it takes is a single line of code in your project to render this form:

```tsx {{ ins: [6] }}
import { SignIn } from '@clerk/nextjs'

export default function SignInPage() {
  return (
    <div className="bg-muted flex w-full flex-1 items-center justify-center p-6 md:p-10">
      <SignIn />
    </div>
  )
}
```

For growth-minded engineering teams, this is a build‑vs‑buy decision: do you want engineers investing cycles in auth primitives, or in features that move activation, retention, and revenue?

### Beyond passwordless: Complete User Management

Furthermore, our platform offers more than sign-up and sign-in using passkeys. You can enable additional [authentication options](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategies) your users expect (email codes, social sign in, etc.), and you don't have to own the edge cases around recovery, device changes, and evolving platform behavior.

On top of authentication, you get an easy way for your users to self-serve their needs, from user profile details to password resets. For B2B apps, you can use [Organizations](/docs/organizations/overview) to enable multi-tenancy support for your application in a few lines of code, while supporting passkeys across all tenants of your application, a requirement that often shows up in enterprise evaluations sooner than founders expect.

> \[!NOTE]
> For developers exploring other passwordless options, [magic links](/blog/magic-links) provide an alternative authentication method worth considering.

## Conclusion

Passkeys remove much of the pain and risk of authentication by replacing brittle passwords with strong, origin-bound cryptography. By leaning on platform authenticators and standards like WebAuthn and FIDO2, you get phishing resistance and built-in MFA without making users jump through extra hoops, and you reduce the blast radius of credential theft for your company.

In this tutorial, we explored what passkeys are, how the registration and authentication flows work, and how to wire them up end-to-end in a Next.js app using SimpleWebAuthn. We also explored how simple it is to add passkeys to your application with Clerk, while reducing effort around testing and maintenance. To get started with Clerk, check out our [quickstart guide](/docs/quickstarts/nextjs) or learn more about [passkey configuration options](/docs/authentication/configuration/sign-up-sign-in-options#passkeys).

---

# Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list
URL: https://clerk.com/blog/clerk-ranked-4-fastest-growing-software-vendor-by-ramp.md
Date: 2025-12-04
Category: Company
Description: Recognition highlights Clerk’s rapid customer adoption

We’re excited to share that **Clerk has been ranked the #4 fastest-growing software vendor by customer count** on Ramp’s **[Top SaaS Vendors on Ramp – December 2025](https://ramp.com/velocity/top-saas-vendors-on-ramp-december-2025)** list. The ranking is based entirely on real customer spend and adoption patterns across Ramp’s platform — making this recognition especially meaningful.

Ramp’s monthly analysis tracks which vendors are seeing the fastest acceleration in **new customer acquisition** and **new spend**. Clerk’s placement near the top of the list reflects the growth we’re seeing every day: more teams choosing to trust Clerk with authentication, user management, organizations, and billing.

This year, we’ve seen rapid adoption from AI-native builders, SaaS companies, and enterprises rethinking their identity foundations. As applications become more sophisticated — and as agent-based systems introduce new requirements around access, auditability, and permissions — Clerk has become a key part of the way modern companies manage users and devices.

As we continue investing in authentication, users, organizations, and billing, our focus remains unchanged: giving developers best-practice solutions with unmatched ease of use.

**A sincere thank you to every team that partnered with Clerk this year.** This recognition is a direct reflection of your trust, and a preview of what’s ahead.

---

# How do I handle JWT verification in Next.js?
URL: https://clerk.com/blog/how-do-i-handle-jwt-verification-in-nextjs.md
Date: 2025-10-16
Category: Guides
Description: Learn all about JWTs: what they are, how they are created, and how to verify them in a Next.js application

In this article, you’ll learn what JWTs are and how to verify them in a Next.js application. Code samples are also provided to demonstrate how to access the JWTs transmitted through a cookie or a request header.

**Summary:**

- JWTs are specially crafted, cryptographically signed tokens that are used to identify users or systems making requests.
- JWTs can be signed using symmetric or asymmetric cryptography, with the latter being more secure while supporting distributed systems.
- They are verified by extracting the value from the request and checking the signature using a shared secret or public key, which allows the receiving server to trust the encoded claims.

## What Is a JWT?

A JSON Web Token (JWT) is a compact, specially crafted string used to represent a user, session, or other principal. JWTs are cryptographically signed, and that signature is used to confidently identify the party making a request.

A JWT is structured into three distinct segments, each separated by a period to indicate where one part ends and another begins. Each segment is base64 encoded before being joined to create the final token. The following diagram shows what a JWT looks like, along with each segment being color-coded:

![Color-coded diagram showing the different segments of a JWT.](./jwt-diagram.png)

### Header

The header contains information to help the receiving system understand the token's purpose. This includes the type (typically set to `jwt`) and the algorithm used to generate the signature.

### Claims

The claims are the primary payload of the JWT and contain information about the party for which the JWT was created. The following list outlines claims that are standard across most JWTs:

- **Subject** (`sub`) - Identifies who the JWT was created for. Often this is the username or user ID.
- **Issuer** (`iss`) - Who created the JWT
- **Expiration** (`exp`) - The epoch timestamp for when the JWT expires
- **Issued at** (`iat`) - The timestamp for when the JWT was created
- **Not before** (`nbf`) - A timestamp to restrict use of the JWT before a specific moment
- **Audience** (`aud`) - The designated audience for the JWT

Not all of these claims are required. The following snippet shows the claims from the Clerk token shown in the image above:

```json
{
  "azp": "https://quillmate.ink",
  "exp": 1759338857,
  "fva": [0, -1],
  "iat": 1759338797,
  "iss": "https://clerk.quillmate.ink",
  "jti": "890f222a3f2a4ce5f782",
  "nbf": 1759338787,
  "sid": "sess_33TQq7sI3bxADiC3KgBVJlASViA",
  "sts": "active",
  "sub": "user_2urxDrHRFn0g7jbmVH88oTLuME6",
  "v": 2
}
```

You can also add any arbitrary JSON-compliant data to a JWT. Say, for example, you are using `sub` as the user ID but also want to encode the username, you can create a `username` claim and simply store it the JWT claims before signing.

### Signature

The signature is the result of combining the base64 encoded versions of the header and claims, and using the specified algorithm to generate a cryptographic signature. This signature is used to ensure that the JWT has not been tampered with, which is how the receiving system can be confident in the JWT's authenticity.

## How JWT verification uses cryptography

There are two cryptographic methods by which JWTs are created: symmetric and asymmetric.

When symmetric encryption is used, the server uses a shared secret for encrypting and decrypting the token. When a JWT is received by the server, it uses that secret to verify that the signature of the JWT is valid. If either the header or claims have been tampered with, then the signature verification will fail. When using symmetric encryption, any server that needs to verify JWTs would need a copy of the secret.

Asymmetric encryption uses public key cryptography which involves two keys: a public key that can be shared with anyone and a private key that should be secured as you would secure a password. The private key is capable of creating and verifying signatures, whereas the public key is only capable of verifying signatures against a pre-existing payload. With both keys, the signature is verified with the same logic as described in the previous section.

With asymmetric encryption, only the public key is used to verify the signature. All servers would need a copy of this key, but since the public key can only verify signatures and not create signatures, it is a much more secure method of verifying signatures.

> \[!NOTE]
> A deep dive into cryptography used for JWT signing is beyond the scope of this article, but you can check [the RFC](https://tools.ietf.org/html/rfc7515) if you are interested in learning more.

## How are JWTs created?

In a typical web application, users will start by submitting their credentials (e.g., username and password). When the server receives these credentials, it will verify them with the database to ensure the user record is found and the provided information matches what's stored. The server will then create a JWT and send it back to the client, often by setting it in a cookie but the server can also provide it in a response body and leave it up to the client to store for later use.

The following sequence diagram demonstrates this flow visually:

1. The client sends the user's credentials to the server
2. The server checks the credentials with the database
3. The database responds to the server to confirm the user record exists and credentials match
4. The server uses the private key to create a JWT and sends it back to the client

![JWT authorization sequence diagram.](./sequence.png)

> \[!NOTE]
> If you want to explore this in practice, we have [a guide that covers how to build JWT authentication into a Next.js application from scratch](/blog/how-to-add-authentication-to-a-nextjs-application).

## Verifying JWTs in a Next.js app

JWTs are verified on each request to a server. When the server receives a request and the accompanying JWT, it will use the public key to verify the signature of the JWT. The verification also checks various claims encoded within the JWT such as the `iat`, `nbf`, and `exp` values to ensure the token is not being used outside of its designated window. If the server is able to verify the signature, it can trust the data encoded within the JWT.

Since the public key can be cached on the receiving server, communicating with additional auth servers or databases is not required.

The following snippets demonstrate how to verify a JWT using the `jose` library in a Next.js application. In these scenarios, the public key is stored in the `JWT_PUBLIC_KEY` environment variable. Here is an example of that key:

```text
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGJjKFz2TB66CY3O3J8E
Q1HbMhg0IE/zK6rxOWx4wuMLXsWhFzEOgqQhEPXAC2IzoIQ5JiV8MKe/xI5NQPr6
sBsPNvf50VBEu/4LAo2fJLFFaqfLpV6p0kLU93EgmklrYaGhU+qhYWdqJQGlafTZ
bNG07VM3mO4qX6iGGn2uhF+KCwGRA06wIJHkprWlSjxM0HjpHCBJ5Vd44D2D5nRT
jQ6W+SEQBiU8CerIPLlEHlCMxVarwq9Pa385vi1PPoDcCB9j/qY6FupDB1e3q27x
+bubo+ZtAmF4mOIPHH/xdg3AhoDYYJcCarCUzfFAEbXM5b6vQx4kCTxTvlN6bRJ2
NwIDAQAB
-----END PUBLIC KEY-----
```

### Cookies

Cookies are small bits of data stored in a web browser that are automatically transmitted to servers that match the domain for which they were created when a request is sent. This snippet shows how to parse and verify the JWT from the `token` cookie:

```tsx
import React from 'react'
// Read cookies on the server in the App Router
import { cookies } from 'next/headers'
// Jose is used for standards-compliant JWT crypto/validation
import * as jose from 'jose'

// RS256 public key (PEM/SPKI). Keep this in env, not in code.
const publicKeyPem = process.env.JWT_PUBLIC_KEY as string

async function getPublicKey() {
  // Convert PEM string to a CryptoKey usable by jose.jwtVerify
  return jose.importSPKI(publicKeyPem, 'RS256')
}

async function Page() {
  // Access the cookie store on the server
  const cookieStore = await cookies()
  // JWT expected to be set as a 'token' cookie
  const tokenCookie = await cookieStore.get('token')
  // Use a placeholder for the token claims
  let claims: jose.JWTPayload | null = null

  if (tokenCookie) {
    try {
      const publicKey = await getPublicKey()
      // Verify signature, exp/nbf, and decode claims (throws on failure)
      const { payload } = await jose.jwtVerify(tokenCookie?.value, publicKey)
      claims = payload
    } catch (err) {
      // Treat any verification/parsing error as unauthorized
      console.error('Error parsing token', err)
    }
  }

  if (claims) {
    // Authorized view when token is valid
    return <div>Authorized content</div>
  } else {
    // Fallback when missing/invalid token
    return <div>Unauthorized content</div>
  }
}

export default Page
```

> \[!NOTE]
> You can learn more about how `jose.jwtVerify` works in [the `jose` docs on GitHub](https://github.com/panva/jose/blob/main/docs/jwt/verify/functions/jwtVerify.md).

Cookies are often considered the most secure method since you can restrict JavaScript from accessing the value of cookies, limiting what can potentially exfiltrate a user's token for impersonation.

### Headers

Request headers are a popular way to transmit JWTs and can be used across domains; however, since the client needs to manually add the header to each request, JavaScript needs to be able to access the value of the JWT. The most common header to transmit a JWT with is the `Authorization` header with a value formatted as `Bearer {TOKEN}`.

Using `fetch` to make a request to an API route would be similar to the following:

```ts
await fetch('/api/data', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
  },
})
```

The JWT in the header would be verified in the route handler like so:

```ts
import * as jose from 'jose'

const publicKeyPem = process.env.JWT_PUBLIC_KEY as string

async function getPublicKey() {
  return jose.importSPKI(publicKeyPem, 'RS256')
}

export async function POST(req: Request) {
  // Placeholder for verified JWT claims
  let claims: jose.JWTPayload | null = null

  // Read the Authorization header in a case-insensitive way since header casing can vary by clients/proxies.
  const auth = req.headers.get('authorization') || req.headers.get('Authorization')
  // Ensure the header exists and uses the expected "Bearer <token>" scheme.
  if (!auth || !auth.startsWith('Bearer ')) {
    return new Response(null, { status: 401 })
  }
  // Extract the raw JWT by removing the leading "Bearer " prefix and trimming whitespace.
  const token = auth.slice(7).trim()

  try {
    const publicKey = await getPublicKey()
    const { payload } = await jose.jwtVerify(token, publicKey)
    claims = payload
  } catch (err) {
    console.error('Error parsing token', err)
  }

  if (claims) {
    return new Response(null, { status: 200 })
  } else {
    return new Response(null, { status: 401 })
  }
}
```

Using the `Authorization` header is common practice but you can transmit a JWT in any header that makes sense for your application.

### Other methods

The methods above are the most common and secure methods of transmitting a JWT, but ultimately, any way that the token can be transmitted from a client to the server will work, provided the server expects it. Alternate methods you may encounter are as a path or query parameter in a URL, however, these approaches are typically not recommended as they pose potential security risks by exposing tokens via logging platforms or in the browser history.

## Conclusion

This article covered what JWTs are, how they are created, and how to verify them in a Next.js application. Clerk handles this entire process for you in only a few lines of code, while going beyond simple JWT verification by providing a full user management suite. This includes social sign-in with popular providers, multi-tenancy with RBAC, and even a billing solution for SaaS platforms that need to provide subscriptions.

---

# Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund
URL: https://clerk.com/blog/series-c.md
Date: 2025-10-15
Category: Company
Description: Funding will be used to advance Agent Identity, expand products, and elevate developer experience.

We’re excited to share that Clerk has raised a **$50 million Series C**, led by Menlo and Anthropic’s Anthology Fund, alongside Georgian and previous investors.

Clerk started with a simple vision: offer best-practice authentication with spectacularly easy developer experience. Since then, we’ve become the user management solution of choice, and scaled to manage **over 200 million users for over 15,000 applications**.

A key reason developers choose Clerk is because authentication is always evolving, and Clerk helps them stay on the cutting edge. In our short history, we’ve already helped our customers navigate the introduction of both passkeys and crypto wallets on sign-in pages.

Today, everyone’s attention is on AI. It’s clear that “agents” need a new authentication solution: one that lets them act on behalf of humans with fine-grained permissions, and is easily auditable by both their operating human and application administrators.

The path to secure agent identity won’t come from Clerk alone, but we’re eager to help bring it to life. As the IETF works to extend OAuth with agent identity specifications, we’re laying the groundwork to adopt those standards as soon as they’re ready.

Once formalized, Clerk will bring support to our signature `<SignUp />` and `<SignIn />` components, and make agent identity available to our customers with just a few clicks.

Beyond agent identity, this capital will also be used for:

- **Doubling down on developer experience**: maintaining our emphasis on spectacular DX with deeper integrations, improved documentation, and expanded support for vibe coding and agentic workflows
- **Expanding our product surface**: improving our existing products for multi-tenancy and billing, and adding new products to support other business primitives
- **Investing in reliability**: making sure that the infrastructure powering your users is always there when you need it

Of course, this Series C would not have been possible without our customers, partners, and the incredible team at Clerk. We’re endlessly grateful for your support, and look forward to collaborating on this next chapter of both Clerk and authentication itself.

---

# What is the best way to handle authentication in Next.js App Router?
URL: https://clerk.com/blog/best-auth-nextjs-app-router.md
Date: 2025-09-26
Category: Guides
Description: Learn about the various authentication options available when using Next.js app router, and how to properly secure access to protected areas of your application.

Next.js has dramatically simplified the process of building full stack applications with React by providing the flexibility of server- and client-side rendering and introducing patterns like middleware and server actions, but these bring additional complexities when it comes to securing your application. With multiple points of protection, it can be challenging to understand how to properly implement Next.js App Router authentication and check that your users are authenticated before allowing them to access the data they are requesting.

In this article, you'll learn about the available Next.js App Router authentication options, and how to check the authentication status in middleware, layouts, server actions, and more.

## What are the main authentication options for Next.js App Router?

### Fully custom

One option is to create a fully customized authentication solution for your application.

This gives you the most control over your authentication stack, but it also puts all of the responsibility on you to build a fast and secure authentication system. You’ll have to consider:

- Where will you store your user data?
- How do you verify user email addresses?
- What will your sign-in/up experience be like?
- How do you implement single sign-on?
- How will the server identify the user?

On top of implementation details, you’ll also be responsible for monitoring the ever-evolving landscape of cybersecurity to understand newly discovered vulnerabilities, as well as taking time to patch your code to protect against them.

> \[!NOTE]
> Learn more about how to implement [JWT authentication in a Next.js app](/blog/how-to-add-authentication-to-a-nextjs-application) in our dedicated guide.

### Package-based solution

There are many pre-built packages available for developers to implement Next.js app router authentication. These packages often include the base logic for implementing authentication (sign in, sign up, etc), but you are still responsible for storing the user data, which is a benefit and a drawback. While you can easily access user data in your own database, you are still responsible for securing that data and making sure that attackers cannot gain access to it.

On top of that, you’d need to make sure that the package is continually kept up to date to ensure that any vulnerabilities are mitigated. Some packages are maintained by well-known and highly reputable security teams, while others might be independently maintained and worked on only when the package owner is able.

### Third-party providers

Services like Clerk offer hosted solutions for authentication and user management. These platforms typically have opinionated approaches to authentication which can provide a clear implementation direction. Good hosted solutions also have dedicated security teams to ensure that products using the platform are protected against emerging threats. And while these platforms often store user data on your behalf, you’d have access to the user data through data exports, automatic synchronization, and API access.

Not all third-party authentication solutions are alike though. You’ll have to do your research to compare and contrast the features each platform enables. Furthermore, it’s a good idea to reference unaffiliated sources to understand potential security flaws that real-world products have encountered while using a solution you might be interested in.

## How to implement authentication in Next.js App Router?

While the above section details the available Next.js App Router authentication options to consider when securing your application, understanding the different ways you can secure your application once you make that decision is another factor.

Ultimately, the way you protect areas of your application depends on how you identify the user making the request. The following examples assume the application uses a JWT stored within a cookie that's sent back to the server with each request.

You’ll notice in many of the samples below, the same approach is being used throughout to identify the user making the request by:

- Parsing the `token` cookie
- Validating and parsing the JWT claims into a `user` object
- Checking that the user details are parsed successfully

```tsx
// Parse the cookie
const token = request.cookies.get('token')?.value

// Validate and parse the claims
if (token) {
  // Use the `parseToken` helper function to extract the claims into `user`
  user = await parseToken(token)
}

// Check the user
if (!user) {
  // The user is not authorized
}
```

For reference, here is the full implementation of `parseToken`:

```ts {{ filename: 'src/lib/jwt.ts' }}
import * as jose from 'jose'

const publicKeyPem = process.env.JWT_PUBLIC_KEY as string

async function getPublicKey() {
  return jose.importSPKI(publicKeyPem, 'RS256')
}

export async function parseToken(token: string): Promise<jose.JWTPayload | null> {
  if (!token) return null

  try {
    const publicKey = await getPublicKey()
    const { payload } = await jose.jwtVerify(token, publicKey)
    return payload
  } catch (err) {
    console.error('Error parsing token', err)
    return null
  }
}
```

> \[!NOTE]
> This snippet is included for quick reference as it's used throughout the article. You can see the full implementation of `src/lib/jwt.ts`, including code to create JWTs, [on GitHub](https://github.com/clerk/nextjs-jwt-auth-demo/blob/main/src/middleware.ts).

### Middleware

Next.js middleware allows developers to execute functions on every incoming request and decide what to do with the request. In the context of Next.js App Router authentication, middleware can decide if the user should proceed to their requested page or be redirected to a sign in page if they are not authenticated.

Below is a sample middleware file that shows how to redirect the user to `/sign-in` if they try to access `/dashboard` and are signed out:

```tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { parseToken } from './lib/jwt'

export async function middleware(request: NextRequest) {
  // Create a variable to hold user details
  let user
  const token = request.cookies.get('token')?.value
  if (token) {
    // Use the `parseToken` helper function to extract the claims into `user`
    user = await parseToken(token)
  }

  // Get the pathname of the requested URL
  const { pathname } = request.nextUrl

  // If there is no user info and the request is to /dashboard, redirect to /sign-in
  if (pathname.includes('/dashboard') && !user) {
    const url = new URL('/sign-in', req.nextUrl)
    return NextResponse.redirect(url)
  }
}

export const config = {
  matcher: '/(.*)',
}
```

> \[!NOTE]
> A full demo of this implementation including sign-up and sign-in flows can be found [on GitHub](https://github.com/clerk/nextjs-jwt-auth-demo).

### Protecting individual pages and routes

You can opt to protect individual pages and routes by using the same pattern as above and deciding how to address unauthenticated users. The following snippet will parse the user and redirect users to `/sign-in` if they are not already authenticated:

```jsx
import { cookies } from 'next/headers'
import { parseToken } from '@/lib/jwt'
import { redirect } from 'next/navigation'

async function DashboardPage() {
  let user
  const token = (await cookies()).get('token')?.value
  if (token) {
    user = await parseToken(token)
  }
  if (!user) {
    redirect('/sign-in')
  }

  return <div>{/* Dashboard content */}</div>
}

export default DashboardPage
```

And this snippet will respond with a `401 Unauthorized` status when a user tries to access an API endpoint without being authenticated:

```tsx
import { parseToken } from '@/lib/jwt'
import { NextRequest } from 'next/server'

export async function GET(request: NextRequest) {
  let user
  const token = request.cookies.get('token')?.value
  if (token) {
    user = await parseToken(token)
  }
  if (!user) {
    return new Response('', { status: 401 })
  }

  return new Response(JSON.stringify({ message: 'Ok!' }), { status: 200 })
}
```

### Securing server actions

Server Actions are functions that execute on the server but can be called in server and client components to handle a variety of tasks such as form submissions. When it comes to authentication, you should treat them with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform the action.

When a server action is called, Next.js will make an HTTP request to the current route with a special header to tell the server what function to execute. This means that even if you use middleware to protect the `/dashboard` route, if you call a server function that is stored in the `/dashboard` route from `/` it will not consider any rules defined in the middleware.

To ensure that server actions are always secure, make sure to check the user details in each individual function:

```tsx
'use server'

import { cookies } from 'next/headers'
import { parseToken } from '@/lib/jwt'

export async function getChartData() {
  const cookieStore = await cookies()
  const tokenCookie = await cookieStore.get('token')
  const user = await parseToken(tokenCookie?.value as string)

  if (!user) {
    throw new Error('User is unauthorized')
  }

  return {
    message: 'OK!',
  }
}
```

### Client-side pages and components

It’s not recommended to add authorization code or security checks in client-side components since the code is downloaded to the user’s device regardless of authentication state. Any security checks performed locally are purely for cosmetic reasons or to inform the user they need to be authenticated to view the contents of that component.

If you are building an application where secure client-side components are mounted on public pages, the best approach is to use `fetch` to request data from an API that performs security checks in the route handler before responding with the data, or an unauthorized status code. Calling a server action that has security rules built into the function is also acceptable.

The following `Chart.tsx` component demonstrates how this should work when fetching data from an API:

```tsx
'use client'
import { useEffect, useState } from 'react'

function Chart() {
  // Set up component state
  const [isLoading, setIsLoading] = useState(true)
  const [requiresAuth, setRequiresAuth] = useState<boolean>()
  const [data, setData] = useState<any>()

  // On component render, fetch data from /api/data (implementation shown above)
  useEffect(() => {
    async function loadData() {
      const res = await fetch('/api/data')
      // If the response is a 401 status, indicate the viewer is unauthorized
      if (res.status === 401) {
        setRequiresAuth(true)
      } else {
        const json = await res.json()
        setData(json)
      }
      setIsLoading(false)
    }
    loadData()
  }, [])

  if (isLoading) {
    return <div>Loading...</div>
  }

  // If the init logic determines the viewer is unauthorized, render that message
  if (requiresAuth) {
    return <div>You need to sign in to view this component</div>
  }

  return <div>{/* Chart data */}</div>
}

export default Chart
```

### A note on securing layouts

Layouts in Next.js are special React components that are applied to any pages in the same directory as well as nested directories. This means that as the user accesses routes deeper within the application, each layout would be wrapped by its parent layout before rendering the final page content as children of the layout.

Take the following application structure as an example:

```txt
src/
│
├── app/
│   ├── layout.tsx       # RootLayout (wraps entire application)
│   ├── page.tsx         # Root page
│   │
│   └── dashboard/
│       ├── layout.tsx   # DashboardLayout (specific to /dashboard routes)
│       └── page.tsx     # Dashboard main page
```

If the user were to access the `/` route, the resulting page would load with its content wrapped in the `RootLayout`. Accessing the `/dashboard` route would first apply the `RootLayout`, then the `DashboardLayout`, and finally the page content.

While it may be tempting to add security and authorization code to layouts, Next.js does not run the code in a layout on every single navigation event due to Partial Rendering. This means that there is no guarantee the code will run consistently and it could lead to vulnerabilities in your application. Instead, you should do the security checks close to your data source or the component that'll be conditionally rendered.

> \[!NOTE]
> You can learn more about [Partial Rendering in the Next.js docs](https://nextjs.org/docs/app/getting-started/linking-and-navigating#client-side-transitions).

## Tips on choosing the best solution for your application

### Choosing the implementation

When it comes to choosing between a custom authentication system, packages, or a hosted platform, it ultimately comes down to the level of effort or engineering hours you are willing to give to your implementation. Custom solutions require the most time and responsibility and are well suited for dedicated teams of security-minded engineers.

Packages are a middle-ground. Some of the hard work has already been done, but you are still completely responsible for the way it is implemented, and for keeping it up to date. Finally, great hosted solutions often provide a turn-key offering with dedicated personnel to prioritize security for their customers (you) and offer support for implementation and any ongoing issues you might encounter.

### Securing your application

As stated earlier, selecting the best method from the list above highly depends on how your application is structured.

Middleware is easily the most secure and flexible to work with. It lets you apply security checks before the request ever reaches the code within pages, layouts, server actions, etc. One thing to consider is that Next.js defaults to the edge runtime for middleware, which does not support all Node.js APIs and may cause some confusion when building your middleware. This however can be changed in the middleware configuration:

```ts {{ filename: 'src/middleware.ts' }}
export const config = {
  runtime: 'nodejs',
}
```

Securing individual pages and routes would be used sparingly or only in special circumstances since it's very narrowly scoped. It would however be useful for API routes where only some methods need to be protected, such as a `GET` request that's public but a `POST` request that requires authentication.

Securing client-side code should only be done for cosmetic purposes as it doesn’t actually apply any security, and adding authorization code directly in a layout should be avoided. Finally, any server actions should always be protected individually since they could be called from any area of the application by mistake.

## Try Clerk for Next.js App Router authentication

If you want to quickly add authentication and user management to your application, Clerk can be implemented in as little as two minutes and provides comprehensive Next.js App Router authentication. Our solution uses middleware to apply security rules to all requests and wraps your entire application at the root layout to ensure that all child routes, server or client rendered, have access to the user context, allowing you to easily secure your application in any way you see fit.

---

# Postmortem: Database Incident (September 14–18, 2025)
URL: https://clerk.com/blog/2025-09-18-database-incident-postmortem.md
Date: 2025-09-18
Category: Company
Description: A detailed post-mortem of the database incident that occurred between September 14-18, 2025, including root cause analysis, timeline, and remediation steps.


Between September 14th and September 18th, 2025, we experienced a database incident that intermittently impacted customer traffic with significant request failures and latency spikes. The issue originated from an automatic database upgrade by our cloud provider, which exposed an interaction with our connection pooling configuration. This document explains the timeline, root cause, contributing factors, and our mitigation of the issue.

## Timeline

- **Sep 14, 05:30 UTC** – Cloud provider auto-upgrades our database (minor version).
- **Sep 14, shortly after** – Internal monitoring detects an increased but still acceptable database load. No impact on latency is observed.
- **Sep 15, 13:08 UTC** – Internal monitoring alerts about significant request failures and increased latency. The incident begins.
- **Sep 15–18** – Engineering teams continually investigate, optimize queries, and tune database parameters.
- **Sep 15, 14:09 UTC** – Request latency normalizes but our database remains unhealthy.
- **Sep 15, 14:58-15:16 UTC** – Significant request failures and latency spikes.
- **Sept 17, 00:22 UTC** – A deploy including a new query optimization appears to eliminate spikes in database load.
- **Sept 17, 01:00 UTC** – Spikes in database load reappear. However, engineers note that the remaining spikes now appear periodic, occurring roughly every 15 minutes. During these periodic spikes, session management was functional due to the session endpoints higher tolerance for latency, as well as the retry mechanism in our SDKs.
- **Sep 17, 07:15-08:10 UTC** - Engineering teams perform a manual minor version upgrade and increase database capacity. User sessions remain active throughout. For a period of 3-6 minutes, new sign ins, sign ups, and other account management activities face failures. The database health improves notably and latency spikes again appear to be eliminated.
- **Sep 17, 15:07-15:14 UTC** - Significant request failures and latency spikes. Engineers again observe periodic spikes in database load every 15 minutes.
- **Sep 18, 03:19 UTC** – Root cause identified, and a fix applied to database connection pooling configuration. After release, database load immediately dropped below pre-incident levels. Latency spikes resolved and stability restored.

## Root Cause

The incident was triggered by an automatic minor version upgrade of our database (Postgres) performed by our cloud provider on September 14th. While this was a minor upgrade, it contained a significant internal change to how Postgres handled connection locks.

### 1. Postgres change to connection lock handling

- In versions prior to the upgrade, Postgres' lock manager granted new connections with O(n²) time complexity, meaning each connection request slowed as more connections were processed.
- This inefficiency inadvertently acted as a natural rate limiter, spacing out how quickly new client connections were created.
- After the upgrade, this bottleneck was removed and [connection granting was optimized](https://www.postgresql.org/docs/release/13.14/) [(commit)](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=dc9d424cf), meaning large batches of new connections could be granted nearly instantaneously.

### 2. Our Cloud Run and connection pooling configuration

- Our Cloud Run configuration leverages many containers. During a deploy, these containers spin up sequentially, within about a minute.
- Our connection pooler in each Cloud Run container was configured with a static maximum connection lifetime of 15 minutes, which causes each connection to be closed and replaced every 15 minutes.
- Under the old Postgres behavior, new connections were created slowly enough that these expirations were naturally spread out over time.
- Under the new Postgres behavior, all expired connections were recreated simultaneously, leading to synchronized connection recycling.
- Unknowingly, our database was in a new state: Synchronized connection recycling of a single Cloud Run container could be managed by our database, but synchronized connection recycling across all Cloud Run containers creates unmanageable load.

### 3. Back pressure causes synchronization of connection recycling across all Cloud Run containers

- At deploy time, each Cloud Run container start is sufficiently spread out such that the load from each connection recycling is unnoticeable.
- Over time, any back pressure in Clerk's database causes the recycling events of each container to synchronize through a "[bus bunching](https://en.wikipedia.org/wiki/Bus_bunching)" effect. Once the busses are bunched, there is a "[thundering herd](https://en.wikipedia.org/wiki/Thundering_herd_problem)" effect every 15 minutes as each containers refreshes its connections all at once.
- As this effect continues to compound every 15 minutes, more connections begin cycling simultaneously, eventually exhausting the pool of active connections and leading to customer-facing latency spikes.

## Mitigations During the Incident

- **Query optimization**: We optimized several expensive queries, permanently reducing baseline database load. Additionally, we refined several critical indexes and carried out a comprehensive re-indexing, resulting in reduced database overhead.
- **Traffic shaping**: To protect stability, we temporarily applied more aggressive blocking of abusive traffic. While effective, this may have inadvertently resulted in a small number of legitimate requests being rejected (HTTP 429).
- **Cloud provider engagement**: We explored rolling back the auto-upgrade, but our provider was unwilling to revert the database version.

## Why Diagnosis Was Difficult

Although the root cause may seem straightforward in hindsight, several factors combined to make identification challenging:

### 1. Metrics resolution hid the cycling pattern

- Our database connection metrics were sampled at 60-second intervals, but the connection recycling occurred within seconds every 15 minutes.
- This meant our dashboards showed only *occasional spikes* in active connections, rather than the synchronized, repeating pattern.
- Because the spikes appeared sporadic rather than rhythmic, we initially treated them as a symptom of underlying load rather than the initiating cause.

### 2. Overlapping events confused the timeline

- During the same window, we were mitigating a large volume of fraudulent sign ups targeting some of our customers.
- This attack created its own periodic load surges every \~10 minutes, which overlapped with the database's 15-minute connection cycling.
- The similar periods led us to initially suspect the attack was the primary driver of latency spikes, delaying deeper focus on the database layer.

### 3. Confounding jobs and queries

- Our database also runs several recurring background jobs and scheduled queries.
- Some of these happen on 10, 15, and 30-minute intervals, which coincidentally aligned with the timing of observed spikes.
- These jobs, while legitimate contributors to load, became false positives during our investigation, diverting attention and masking the real synchronization issue.

### 4. Mixed symptoms across APIs

- Latency spikes were seen in our Frontend API but not consistently in our Backend API, which has the same connection pooling logic.
- This discrepancy made the issue harder to triangulate, as it suggested selective load / query inefficiency rather than a systemic connection pooling effect.
- Looking back, we believe that we did not observe this with our Backend API, due to the fact that it simply just manages a lot less connections (and the Database could handle the cycling load, even if they all became synchronized).

### 5. Release freeze effect not obvious

- Ironically, we imposed a release freeze (intended to minimize risk during the incident) unintentionally made the issue worse, since normal deployments naturally stagger the connection creation across machines as the fleet of machines rolled out.
- Because this wasn't an intuitive connection, it obscured one of the clearest signals that would have otherwise pointed to connection synchronization.

## Resolution and current status

- We adjusted our connection pooling strategy to include a jitter to prevent synchronized cycling.
- Since the fix, the database has remained stable and is performing better than before the upgrade, due to the performance improvements that were implemented during the incident.

## Additional remediations and closing

Of course, we are incredibly sorry for the downtime this incident caused, but we also know that apologies are not enough. While we are excited this incident is behind us, it's important to stress that we're not moving on from infrastructure improvements. Incidents like this are unacceptable, and we recognize Clerk has faced too many recently.

Since our [June 26 incident](https://clerk.com/blog/postmortem-jun-26-2025-service-outage), our team has been urgently focused on improving our resilience. We've shipped improvements every single week, and internally, we have more confidence in our reliability, and our reliability roadmap, than ever before.

Beyond the narrow resolution to this issue, we have additional remediation work planned. Most notably:

- **Evaluating other database providers:** Over the past month, we have already been evaluating database solutions that offer more control over upgrades and downgrades, and improved performance compared to our current provider.
- **Additional service isolation:** Our ability to keep sessions operational during much of this incident was a direct result of our remediations following the June 26 incident. Now, we want to extend that concept further. For example, isolating Sign Up and Sign In infrastructure from other parts of the system.
- **Continued database optimizations:** Throughout this incident, we implemented a handful of new-to-Clerk optimization techniques to our most costly queries. We'll continue marching down the list to further improve our database performance.
- **Investigating solutions for tenant isolation:** Throughout this incident, we received many requests to isolate one application from others, and we recognize the potential benefits of that approach. We are encouraged by many modern approaches to sharding and multi-tenant infrastructure, and will continue evaluating solutions. In full disclosure, this is likely on a longer time horizon than the other improvements.

We know every incident carries a cost for you and your users, and we take that responsibility seriously. Our focus is on earning back your trust through reliability, not words, and to ensure that Clerk is an infrastructure partner you can always count on.

---

# How do I add authentication to a Next.js app?
URL: https://clerk.com/blog/how-to-add-authentication-to-a-nextjs-application.md
Date: 2025-09-15
Category: Guides
Description: Learn how Next.js authentication works by implementing JWTs from scratch, including user registration, sign-in/sign-out functionality, and middleware protection.

Authentication is core to building any multi-user product, and it's important to get it right from the start.

There are a number of methods you can use when adding authentication into a product, and Next.js has its own paradigms to consider. Understanding the [Next.js authentication](/nextjs-authentication) strategies available is key to knowing which is best for your application, and properly implementing it is the next challenge.

In this article you'll learn about the most common authentication strategies, as well as how to add JWT authentication to a Next.js application.

## Next.js authentication strategies: Choosing the right approach

There are many strategies to select from when planning your approach to authentication. The most common approaches are session token authentication, JWT-based authentication, and OAuth. Let's touch on how each of these compare.

### Session tokens

Session token authentication is the oldest on this list but is still widely used today. When a user signs into an application using session token authentication, the backend service will verify the user's credentials against the database and, assuming they are valid, create an entity called a "session". Each session has some commonly tracked attributes stored such as the user it's associated with, when it was created, when it expires, and its status (valid, expired, etc). The session identifier is sent back to the user's device to be used with subsequent network requests.

The most common method of storing the session ID client-side is in a browser cookie so the ID is sent with future network requests automatically. When received by the server, the session is cross-referenced with the user for which it was created so that the server knows who is making the request and can apply security appropriately.

> \[!NOTE]
> To learn more about session management in detail, check out our comprehensive guide on [what is session management](/blog/what-is-session-management) and how it works in modern applications.

### JWT

JSON Web Tokens (JWTs) are specially formatted strings that contain embedded information about a particular user or session and are cryptographically signed by the server. When a user signs in, the server will validate the user's credentials just like with session token authentication but instead of creating a session record (commonly in a database table), the details are encoded into a JWT and signed before being sent back to the client. The JWT is also sent with each request but since it contains the user and/or session details, the server does not have to look up those in the database. The server can simply verify the JWT signature is valid and can trust the encoded details if it is.

This has some benefits and drawbacks. One of the primary benefits is the speed by which requests are validated as no datastore lookups are required. Since verification is mostly performed on the receiving server, this also makes JWT authentication more scalable than session token authentication. As long as a server has a cached version of the signing secret (or public key in asymmetric signing configurations), that server can verify the JWTs authenticity.

The primary drawback is the lack of control if a JWT is leaked to an unauthorized party. Since all of the session information is embedded with the token and the verification process does not require any additional checks with a central datastore, there is no standard way to invalidate tokens once they've been signed and sent out.

> \[!NOTE]
> Learn more about how Clerk overcomes this drawback with [a hybrid authentication strategy](/blog/combining-the-benefits-of-session-tokens-and-jwts).

### OAuth

OAuth is a standard that allows a user to authenticate with one system and access multiple services using a single account. If you've ever signed into a web application with a Google or Apple account, you've used OAuth. In a typical configuration, the service provider (SP) will redirect users attempting to sign in to an identity provider (IdP) to supply their credentials and create a session. Once authenticated, the user's device will receive a code that can be provided to the SP, which will communicate with the IdP to verify the code, create the JWT, and send it back to the user.

This flow (known as the "Authorization Code Grant") describes how the SP and IdP work together to create the session and is only one of many flows that are part of the OAuth spec.

> \[!NOTE]
> A full explanation of OAuth is beyond the scope of this article, but we have [another on our blog that covers OAuth](/blog/how-oauth-works) and its implementation in detail.

## How to implement Next.js authentication with JWT tokens

Now that you have a solid understanding of some common authentication strategies, let's learn how to manually implement Next.js authentication using JWT tokens. To do this, you'll step through the following:

- Configure a SQLite database to store user records
- Set up public/private keys to sign and validate JWTs in a helper
- Create sign-up and sign-in pages
- Configure a **Sign out** button
- Show claims from the JWT within a server-rendered page

> \[!NOTE]
> This guide is meant to act as an introduction to JWT authentication by demonstrating a minimal implementation, however a robust and scalable solution involves significantly more than what's covered. Considerations for a complete user management solution are discussed after the tutorial.

To follow along, you'll need the following:

- A general understanding of React, and ideally experience with Next.js
- Node.js installed on your workstation

You'll use the supplied starter repository that is a Next.js application preconfigured with SQLite, a few shadcn/ui components, and a dashboard page with some dummy data. Through this guide, you'll create the sign-up page, sign-in page, sign-out button, and you'll configure the middleware to enforce authentication on the `/dashboard` route.

Upon signing in, the middleware will parse the JWT (stored as a cookie) to determine the user's authentication status. Server actions will be used throughout the various authentication functions.

The following dependencies are also pre-installed:

- `bcrypt` to salt and hash user passwords before storing them in the database.
- `jose` to create and validate JWTs

> \[!NOTE]
> [Hashing](https://www.geeksforgeeks.org/dsa/what-is-hashing/) is a process that lets you obfuscate data with an irreversible cryptographic method so it is not stored in plain text, which should never be done with passwords in a database.

Before moving on, clone the `start` branch of this repository: [clerk/nextjs-jwt-auth-demo](https://github.com/clerk/nextjs-jwt-auth-demo/tree/start). Once cloned, open the project in your code editor of choice and run `pnpm install` to install the dependencies.

### Creating the SQLite and JWT helpers

You'll start by creating a helper file that lets the application interact with the SQLite database. The helper will create the connection, create the `users` table if it does not yet exist, and return a connection object to the caller. The table needed to support user authentication contains only three columns:

- `id` is the unique identifier for the user
- `username` is their username
- `password_hash` is the salted and hashed representation of their password

Create the `src/lib/db.ts` and populate it with the following:

```ts {{ filename: 'src/lib/db.ts' }}
import sqlite3 from 'sqlite3'
import path from 'path'

sqlite3.verbose()

const db = new sqlite3.Database(path.join(process.cwd(), 'sqlite.db'), (err) => {
  if (err) {
    console.error(err.message)
  } else {
    console.log('Connected to the SQLite database.')
  }
})

db.serialize(() => {
  db.run(
    'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password_hash TEXT)',
  )
})

export { db }
```

Next, you'll create the JWT helper file that contains the configuration for `jose` as well as the `createToken` function to generate a new JWT for the user and `parseToken` which verifies the token's validity and returns the claims (the data encoded within JWTs) to the caller if it is.

Create the `src/lib/jwt.ts` file and add the following:

```ts {{ filename: 'src/lib/jwt.ts' }}
import * as jose from 'jose'

const privateKeyPem = process.env.JWT_PRIVATE_KEY as string
const publicKeyPem = process.env.JWT_PUBLIC_KEY as string

const jwtConfig = {
  protectedHeader: { alg: 'RS256', typ: 'JWT' },
}

interface CustomJWTPayload extends jose.JWTPayload {
  username?: string
}

export async function parseToken(token: string): Promise<CustomJWTPayload | null> {
  if (!token) return null

  try {
    // Import the public key
    const publicKey = await jose.importSPKI(publicKeyPem, 'RS256')
    const { payload } = await jose.jwtVerify(token, publicKey)
    return payload
  } catch (err) {
    console.error('Error parsing token', err)
    return null
  }
}

export async function createToken(sub: string, username: string): Promise<string> {
  try {
    // Import the private key
    const privateKey = await jose.importPKCS8(privateKeyPem, 'RS256')
    return await new jose.SignJWT({ sub, username })
      .setProtectedHeader(jwtConfig.protectedHeader)
      .setIssuedAt()
      .setExpirationTime('1h')
      .sign(privateKey)
  } catch (err) {
    console.error('Error creating token', err)
    throw err
  }
}
```

Notice in the above code that the `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` are being referenced from the environment variables. To set this up, run the following command in your terminal to generate a key pair and set them in the `.env.local` file:

```bash
npm run generate-keys
```

Inspecting the `.env.local` file will look similar to the following (albeit with a larger value for each variable):

```{{ filename: '.env.local' }}
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkq..."
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvAIBAD..."
```

> \[!NOTE]
> Make sure to protect the `JWT_PRIVATE_KEY` as anyone with it can generate tokens on behalf of any user of your application!

### Building the sign-up flow

Before users can sign-in and use the application, they'll need a way to sign-up first. Create the `src/app/actions.ts` file to store the server actions required to sign-up. This configuration will check if a record with that username exists (responding with an error if found), creates the JWT and stores it as a cookie, and redirects the user to the `/dashboard` route.

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

import bcrypt from 'bcrypt'
import { db } from '@/lib/db'
import { RunResult } from 'sqlite3'
import { cookies } from 'next/headers'
import { createToken } from '@/lib/jwt'

const SALT_ROUNDS = 10

// Hashes the password for storing it
async function hashPassword(password: string) {
  const salt = await bcrypt.genSalt(SALT_ROUNDS)
  const hash = await bcrypt.hash(password, salt)
  return { hash, salt }
}

// Creates the user record in the database
async function createUserRecord(username: string, hash: string): Promise<number> {
  return new Promise((resolve, reject) => {
    db.run(
      'INSERT INTO users (username, password_hash) VALUES (?, ?)',
      [username, hash],
      function (err: Error, results: RunResult) {
        if (err) {
          reject(err)
        }
        resolve(results?.lastID)
      },
    )
  })
}

interface CheckCountResult extends RunResult {
  count: number
}

async function checkDoesUserExist(username: string): Promise<boolean> {
  return new Promise((resolve, reject) => {
    db.get(
      'select count(*) as count from users where username=?',
      [username],
      (err: Error, results: CheckCountResult) => {
        if (err) {
          reject(err)
        }
        resolve(results.count !== 0)
      },
    )
  })
}

// The action used in the sign-up route
export async function registerUser(username: string, password: string) {
  try {
    const userExists = await checkDoesUserExist(username)
    if (userExists) {
      throw new Error('User with this name already exists')
    }

    // Hash the password
    const { hash } = await hashPassword(password)

    // Create the user record
    const userId = await createUserRecord(username, hash)

    // Create the token and set it in a cookie
    const token = await createToken(userId?.toString(), username as string)
    const cookieStore = await cookies()
    cookieStore.set('token', token, {
      path: '/',
      httpOnly: true,
      maxAge: 3600, // 1 hour in seconds
    })
  } catch (err) {
    throw err
  }
}
```

Now create the `src/app/sign-up/page.tsx` file to store the sign-up form used to create an account:

```tsx {{ filename: 'src/app/sign-up/page.tsx' }}
'use client'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useState } from 'react'
import { registerUser } from '../actions'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { useRouter } from 'next/navigation'

function SignUpPage() {
  const router = useRouter()
  const [username, setUsername] = useState<string>('')
  const [pass, setPass] = useState<string>('')
  const [confPass, setConfPass] = useState<string>('')
  const [err, setErr] = useState<string>()

  async function register() {
    if (!username) {
      setErr('Must specify a username')
      return
    }
    if (pass !== confPass) {
      setErr('Passwords do not match')
      return
    }
    try {
      await registerUser(username as string, pass as string)
      router.push('/dashboard')
    } catch (err) {
      setErr((err as Error).message)
    }
  }

  return (
    <div className="align-center flex flex-col items-center justify-center p-8">
      <Card className="flex w-[400px] flex-col gap-2 p-4">
        <h1>Sign up</h1>
        <Label>Username</Label>
        <Input value={username} onChange={(e) => setUsername(e.target.value)} />
        <Label>Password</Label>
        <Input type="password" value={pass} onChange={(e) => setPass(e.target.value)} />
        <Label>Confirm password</Label>
        <Input type="password" value={confPass} onChange={(e) => setConfPass(e.target.value)} />
        <Button onClick={register}>Sign up</Button>
        {err && (
          <Alert variant="destructive">
            <AlertTitle>Error</AlertTitle>
            <AlertDescription>{err}</AlertDescription>
          </Alert>
        )}
      </Card>
    </div>
  )
}

export default SignUpPage
```

You can now start the application with `npm run dev`, access it using the provided URL, and navigate to the `/sign-up` route to create a user. After creating a user, you'll be redirected to the `/dashboard` route.

### Configure sign-out

Since the JWT is stored in a cookie with the `httpOnly` flag, client-side JavaScript will not be able to access it, so you'll need to configure a server action to clear the cookie. Update the `actions.ts` file and append the `signOut` function as shown below:

```ts {{ filename: 'src/app/actions.ts', fold: [[1, 79]] }}
'use server'

import bcrypt from 'bcrypt'
import { db } from '@/lib/db'
import { RunResult } from 'sqlite3'
import { cookies } from 'next/headers'
import { createToken } from '@/lib/jwt'

const SALT_ROUNDS = 10

// Hashes the password for storing it
async function hashPassword(password: string) {
  const salt = await bcrypt.genSalt(SALT_ROUNDS)
  const hash = await bcrypt.hash(password, salt)
  return { hash, salt }
}

// Creates the user record in the database
async function createUserRecord(username: string, hash: string): Promise<number> {
  return new Promise((resolve, reject) => {
    db.run(
      'INSERT INTO users (username, password_hash) VALUES (?, ?)',
      [username, hash],
      function (err: Error, results: RunResult) {
        if (err) {
          reject(err)
        }
        resolve(results?.lastID)
      },
    )
  })
}

interface CheckCountResult extends RunResult {
  count: number
}

async function checkDoesUserExist(username: string): Promise<boolean> {
  return new Promise((resolve, reject) => {
    db.get(
      'select count(*) as count from users where username=?',
      [username],
      (err: Error, results: CheckCountResult) => {
        if (err) {
          reject(err)
        }
        resolve(results.count !== 0)
      },
    )
  })
}

// The action used in the sign-up route
export async function registerUser(username: string, password: string) {
  try {
    const userExists = await checkDoesUserExist(username)
    if (userExists) {
      throw new Error('User with this name already exists')
    }

    // Hash the password
    const { hash } = await hashPassword(password)

    // Create the user record
    const userId = await createUserRecord(username, hash)

    // Create the token and set it in a cookie
    const token = await createToken(userId?.toString(), username as string)
    const cookieStore = await cookies()
    cookieStore.set('token', token, {
      path: '/',
      httpOnly: true,
      maxAge: 3600, // 1 hour in seconds
    })
  } catch (err) {
    throw err
  }
}

export async function signOut() {
  const cookieStore = await cookies()
  cookieStore.delete('token')
}
```

Next, create a Sign Out button component at `src/components/SignOutButton.tsx` and paste in the following:

```tsx {{ filename: 'src/components/SignOutButton.tsx' }}
'use client'

import { Button } from '@/components/ui/button'
import React from 'react'
import { signOut } from '../app/actions'

function SignOutButton() {
  async function onClick() {
    await signOut()
    window.location.pathname = '/'
  }

  return <Button onClick={onClick}>Sign out</Button>
}

export default SignOutButton
```

Then you'll need to update the Navigation component to check if the user is logged in and render the button if they are. Since it is a server-rendered component, you can use the `next/headers` package to access the request cookies and the `parseToken` function to verify the user is signed in.

Update the `src/components/Navigation.tsx` file as follows:

```tsx {{ filename: 'src/components/Navigation.tsx', ins: [[1, 3], [8, 10], [19, 26]], del: [[27, 30]] }}
import { cookies } from 'next/headers'
import { parseToken } from '@/lib/jwt'
import SignOutButton from './SignOutButton'
import Link from 'next/link'
import Logo from './Logo'

async function Navigation() {
  const cookieStore = await cookies()
  const tokenCookie = await cookieStore.get('token')
  const user = await parseToken(tokenCookie?.value as string)

  return (
    <nav className="flex flex-row items-center justify-between border-b-1 border-b-neutral-200 p-6">
      <Link href="/" className="flex flex-row items-center gap-2">
        <Logo />
        Next.js JWT Auth Demo
      </Link>

      {user ? (
        <SignOutButton />
      ) : (
        <div className="flex flex-row gap-2">
          <Link href="/sign-in">Sign in</Link>
          <Link href="/sign-up">Sign up</Link>
        </div>
      )}
      <div className="flex flex-row gap-2">
        <Link href="/sign-in">Sign in</Link>
        <Link href="/sign-up">Sign up</Link>
      </div>
    </nav>
  )
}

export default Navigation
```

Now access the application in your browser once again and click the **Sign out** button in the navigation. You'll be redirected if you are on the `/dashboard` page and the navigation bar will update to show the **Sign in** and **Sign up** links.

### Configure the sign-in page and actions

Now that sign-up and sign-out are working, you'll need a way for existing users to sign-in. Update the `actions.ts` file once again and append the following actions:

```ts {{ filename: 'src/app/actions.ts', fold: [[1, 84]] }}
'use server'

import bcrypt from 'bcrypt'
import { db } from '@/lib/db'
import { RunResult } from 'sqlite3'
import { cookies } from 'next/headers'
import { createToken } from '@/lib/jwt'

const SALT_ROUNDS = 10

// Hashes the password for storing it
async function hashPassword(password: string) {
  const salt = await bcrypt.genSalt(SALT_ROUNDS)
  const hash = await bcrypt.hash(password, salt)
  return { hash, salt }
}

// Creates the user record in the database
async function createUserRecord(username: string, hash: string): Promise<number> {
  return new Promise((resolve, reject) => {
    db.run(
      'INSERT INTO users (username, password_hash) VALUES (?, ?)',
      [username, hash],
      function (err: Error, results: RunResult) {
        if (err) {
          reject(err)
        }
        resolve(results?.lastID)
      },
    )
  })
}

interface CheckCountResult extends RunResult {
  count: number
}

async function checkDoesUserExist(username: string): Promise<boolean> {
  return new Promise((resolve, reject) => {
    db.get(
      'select count(*) as count from users where username=?',
      [username],
      (err: Error, results: CheckCountResult) => {
        if (err) {
          reject(err)
        }
        resolve(results.count !== 0)
      },
    )
  })
}

// The action used in the sign-up route
export async function registerUser(username: string, password: string) {
  try {
    const userExists = await checkDoesUserExist(username)
    if (userExists) {
      throw new Error('User with this name already exists')
    }

    // Hash the password
    const { hash } = await hashPassword(password)

    // Create the user record
    const userId = await createUserRecord(username, hash)

    // Create the token and set it in a cookie
    const token = await createToken(userId?.toString(), username as string)
    const cookieStore = await cookies()
    cookieStore.set('token', token, {
      path: '/',
      httpOnly: true,
      maxAge: 3600, // 1 hour in seconds
    })
  } catch (err) {
    throw err
  }
}

export async function signOut() {
  const cookieStore = await cookies()
  cookieStore.delete('token')
}

export async function signinUser(username: string, password: string) {
  try {
    // Get the user record and verify the provided password matches what's stored
    const user = await fetchUserFromDb(username)

    // Compare the provided password with what's stored in the database
    // and make sure they match.
    const isPasswordValid = await bcrypt.compare(password, user.password_hash)
    if (!isPasswordValid) {
      throw new Error('Username and/or password is incorrect')
    }

    if (!user.id || !user.username) {
      console.error('Error parsing user details', user)
      throw new Error('Unknown error')
    }

    // Create the token and set it in a cookie
    const token = await createToken(user.id?.toString(), user.username as string)
    const cookieStore = await cookies()
    cookieStore.set('token', token, {
      path: '/',
      httpOnly: true,
      maxAge: 3600, // 1 hour in seconds
    })
  } catch (err) {
    throw err
  }
}

interface FetchUserResult extends RunResult {
  id?: number
  username?: string
  password_hash?: string
}

// Fetches the user record from the database using the provided username
async function fetchUserFromDb(username: string): Promise<FetchUserResult> {
  return new Promise((resolve, reject) => {
    db.get('select * from users where username=?', [username], (err, results: FetchUserResult) => {
      if (err) {
        reject(err)
      }
      if (!results.id) {
        reject('User not found')
      }
      resolve(results)
    })
  })
}
```

Then create the `src/app/sign-in/page.tsx` file to display the sign-in form:

```tsx {{ filename: 'src/app/sign-in/page.tsx' }}
'use client'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useState } from 'react'
import { signinUser } from '../actions'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { useRouter } from 'next/navigation'

function SignInPage() {
  const router = useRouter()
  const [username, setUsername] = useState<string>('')
  const [pass, setPass] = useState<string>('')
  const [err, setErr] = useState<string>()

  async function onSignInClicked() {
    if (!username || !pass) {
      setErr('Must specify username and password')
      return
    }
    try {
      await signinUser(username as string, pass as string)
      router.push('/dashboard')
    } catch (err) {
      setErr((err as Error).message)
    }
  }

  return (
    <div className="align-center flex flex-col items-center justify-center p-8">
      <Card className="flex w-[400px] flex-col gap-2 p-4">
        <h1>Sign in</h1>
        <Label>Username</Label>
        <Input value={username} onChange={(e) => setUsername(e.target.value)} />
        <Label>Password</Label>
        <Input type="password" value={pass} onChange={(e) => setPass(e.target.value)} />
        <Button onClick={onSignInClicked}>Sign in</Button>
        {err && (
          <Alert variant="destructive">
            <AlertTitle>Error</AlertTitle>
            <AlertDescription>{err}</AlertDescription>
          </Alert>
        )}
      </Card>
    </div>
  )
}

export default SignInPage
```

In the application, navigate to the `/sign-in` route and use the credentials you created earlier to sign-in and access the `/dashboard` route once again.

### Protecting routes with Next.js authentication middleware

As of now, even unauthenticated users can access `/dashboard` if they enter the path in their browser. Next.js authentication middleware can be configured to intercept inbound requests and check the cookies to ensure a user is signed-in before allowing them to access protected routes. Furthermore, the middleware can also be configured to redirect unauthenticated users to `/sign-in` if they attempt to access `/dashboard`.

Create the `src/middleware.ts` file and populate it like so to achieve this protection:

```ts {{ filename: 'src/middleware.ts' }}
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { parseToken } from './lib/jwt'

export async function middleware(request: NextRequest) {
  // Create a variable to hold user details
  let user
  const token = request.cookies.get('token')?.value
  if (token) {
    // Use the `parseToken` helper function to extract the claims into `user`
    user = await parseToken(token)
  }

  // Get the pathname of the requested URL
  const { pathname } = request.nextUrl

  // If there is no user info and the request is to /dashboard, redirect to /sign-in
  if (pathname.includes('/dashboard') && !user) {
    const url = request.nextUrl.clone()
    url.pathname = '/sign-in'
    return NextResponse.redirect(url)
  }

  // If the user is signed in and trying to access the auth routes, redirect them to the home page
  if ((pathname.includes('/sign-in') || pathname.includes('/sign-up')) && user) {
    const url = request.nextUrl.clone()
    url.pathname = '/'
    return NextResponse.redirect(url)
  }
}

export const config = {
  matcher: '/(.*)',
}
```

> \[!WARNING]
> While this approach does protect accessing routes and pages with `/dashboard` in the pathname, it may not necessarily protect server actions stored within the `/dashboard` folder since Next.js uses the browser's current URL to call server actions.

> \[!NOTE]
> If you want to learn more about Next.js middleware, we have [an in-depth guide in our blog](/blog/what-is-middleware-in-nextjs) that dives deep into the topic.

### Display user information on the Dashboard page (optional)

Since the `/dashboard` route is protected by our Next.js authentication system, any users that can access it will have already signed in, meaning the information in the JWT can be trusted. The `next/headers` package can be used in page content just like in the `Navigation.tsx` component to parse details about the authenticated user and render the details on the page.

To do this, update `src/app/dashboard/page.tsx` as follows to display the user's username in the page header:

```tsx {{ filename: 'src/app/dashboard/page.tsx', ins: [3, 4, 7, 8, 9, 15], del: [14] }}
import { Card } from '@/components/ui/card'
import React from 'react'
import { cookies } from 'next/headers'
import { parseToken } from '@/lib/jwt'

async function DashboardPage() {
  const cookieStore = await cookies()
  const tokenCookie = await cookieStore.get('token')
  const user = await parseToken(tokenCookie?.value as string)

  return (
    <div className="max-w-800px flex flex-col gap-4 p-8">
      <div className="flex flex-row justify-between">
        <h1>Welcome!</h1>
        <h1>Welcome {user?.username}!</h1>
      </div>
      <div className="grid grid-cols-3 gap-2">
        <Card>
          <div className="p-4">
            <div className="text-lg font-semibold">Users</div>
            <div className="text-2xl font-bold">1,234</div>
            <div className="text-sm text-gray-500">Active this month</div>
          </div>
        </Card>
        <Card>
          <div className="p-4">
            <div className="text-lg font-semibold">Revenue</div>
            <div className="text-2xl font-bold">$12,345</div>
            <div className="text-sm text-gray-500">This month</div>
          </div>
        </Card>
        <Card>
          <div className="p-4">
            <div className="text-lg font-semibold">New Signups</div>
            <div className="text-2xl font-bold">321</div>
            <div className="text-sm text-gray-500">Past 7 days</div>
          </div>
        </Card>
      </div>
    </div>
  )
}

export default DashboardPage
```

Accessing the `/dashboard` page will now show the username in the welcome message!

## Now what?

You now have a functional system that allows users to sign up and sign in to this demo app, however there are a number of missing, critical user management features:

- Email address verification
- Password reset functionality
- Advanced attack protection and rate limiting
- Session management and refresh tokens
- Multi-factor authentication
- Social login providers

These are just to name a few of the gaps. Production-ready authentication in Next.js goes well beyond basic JWT implementation, and this is where [Clerk's Next.js authentication solution](/docs/quickstarts/nextjs) comes in.

### Why choose Clerk for Next.js authentication?

Clerk is a complete user management platform that allows developers to add enterprise-grade Next.js authentication into their applications as quickly as possible. With Next.js applications, this can be done in just a few lines of code.

Once implemented, you'll automatically gain all of the features listed above along with many more such as:

- One-click social authentication (Google, GitHub, Apple, etc.)
- Simple multi-tenancy for B2B applications (including custom RBAC)
- Subscription management with [Clerk Billing](/billing)
- Advanced security features that protect against bots, brute force attacks, and abuse
- Pre-built UI components that can be configured to match your application's design

### Get started with production-ready Next.js authentication

If you're ready to implement a robust Next.js authentication solution for your Next.js application, check out our [Next.js quickstart guide](/docs/quickstarts/nextjs) to learn how to get authentication added to your application in as little as 2 minutes. You'll have a complete, secure, and scalable authentication system without the complexity of building and maintaining it yourself.

---

# Introducing Free Trials in Clerk Billing
URL: https://clerk.com/blog/introducing-free-trials-in-clerk-billing.md
Date: 2025-09-02
Category: Company
Description: Clerk Billing now supports free trials to help developers boost conversion rates and reduce buyer friction.

Let’s face it: a frictionless billing system is great, but giving users a risk‑free peek at premium features can turn hesitation into conversion.

Clerk Billing was designed to take the same great developer experience we built for implementing [authentication](/user-authentication) and apply that philosophy to subscription-based billing. It allows developers to drop in a single [`<PricingTable />`](/docs/components/pricing-table) component to display a beautiful pricing table that is typical across hosted SaaS applications. Selecting a tier prompts the user to enter their payment information and once processed, any entitlements configured with their selected plan are flagged on their account for you to easily allow access to gated content.

With the addition of free trials, your users can now more easily experience premium features of your app to decide if it's the right fit for them before automatically converting to paid customers. In this article, we'll explore why you should use free trials for your app and how to implement it with [Clerk Billing](/blog/add-subscriptions-to-your-saas-with-clerk-billing).

## Free trials lead to higher conversions

Free trials reduce buyer friction and drive higher conversion rates by removing the immediate financial commitment. The data is compelling: [SaaS trial-to-paid conversion rates average around **25%**](https://userpilot.com/blog/saas-average-conversion-rate/). In B2B SaaS, [opt-out trials (requiring credit card upfront) can reach nearly **49%** conversion](https://userpilot.com/blog/saas-average-conversion-rate/), while opt-in trials typically convert around 18%.

Beyond metrics, free trials create **product-qualified leads (PQLs)** who've actively engaged with your product. This makes [conversion rates a reliable gauge of real value](https://www.zigpoll.com/content/what-are-the-most-effective-strategies-to-increase-free-trial-to-paid-subscription-conversion-rates-on-a-saas-website) while reducing churn from sign-up to paid status.

### Best practices with free trials

Allowing users to try your product for free is enough to increase conversion rates; however, there are a few considerations to maximize the impact. One practice is to test different trial lengths and models to optimize your conversion rates. By trying a few different trial periods and measuring the conversion rate, you can fine-tune what period leads to the highest revenue increase.

Another practice is to set realistic trial periods. For most SaaS applications, [7–14 days is a great starting point](https://www.zigpoll.com/content/what-are-the-most-effective-strategies-to-increase-free-trial-to-paid-subscription-conversion-rates-on-a-saas-website). If you have a more complex app, extending the period beyond that range could result in higher conversions due to the steps involved in utilizing the SaaS most effectively.

Trial periods present a perfect opportunity to engage users through product education. This can be through a very polished onboarding experience within the app, or something more traditional like using a newsletter platform for a drip campaign during the trial period to provide ways in which your app can be used.

As with most optimizations, success depends on rigorous measurement and iteration. Track key metrics like trial-to-paid conversion rates and analyze performance across different trial lengths and content strategies.

## How to configure free trials using Clerk Billing

If you've not yet used [Clerk Billing](/billing), configuring subscriptions is as simple as defining your subscription plans, associating the proper features with each plan, and adding the [`<PricingTable />`](/docs/components/pricing-table) component to your application.

The following screenshot demonstrates what a three-tier pricing table would look like for a task management app:

![PricingTable in a Next.js app](./1.png)

And the code for this page (built with Next.js) looks like this:

```tsx
import { PricingTable } from '@clerk/nextjs'

export default function Home() {
  return (
    <div className="min-h-screen items-center justify-items-center gap-16 p-8 pb-20 font-sans sm:p-20">
      <PricingTable />
    </div>
  )
}
```

Free trials can now be enabled for individual plans within the Clerk dashboard:

![Free Trials toggle in the Clerk dashboard](./2.png)

Once configured, the `<PricingTable />` component in your app will automatically update to reflect the plans where a free trial is available. You don't even have to make any changes to the code in your application.

![Updated PricingTable](./3.png)

When a user wants to trial a plan, they'll still be prompted for their payment information so that they can transition to a paying customer once the trial period ends. Your customers will automatically be notified when their trial period is coming to an end.

![Credit card capture modal open](./4.png)

If you handle your own messaging, you can also use the [`subscriptionItem.freeTrialEnding`](/docs/billing/events-webhooks#subscription-items) webhook to be notified when a customer's trial is ending so you can notify them or handle it accordingly. Below is a sample of the payload sent when a trial period is ending.

```json
{
  "data": {
    "created_at": 1716883200000,
    "id": "csub_item_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
    "interval": "month",
    "is_free_trial": true,
    "object": "subscription_item",
    "payer": {
      "created_at": 1716883200000,
      "email": "user@example.com",
      "first_name": "John",
      "id": "cpayer_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
      "image_url": "https://img.clerk.com/xxxxxx",
      "instance_id": "ins_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
      "last_name": "Doe",
      "object": "commerce_payer",
      "updated_at": 1716883200000,
      "user_id": "user_2g7np7Hrk0SN6kj5EDMLDaKNL0S"
    },
    "period_end": 1719561600000,
    "period_start": 1716883200000,
    "plan": {
      "amount": 2999,
      "currency": "USD",
      "free_trial_days": 14,
      "free_trial_enabled": true,
      "id": "cplan_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
      "is_default": false,
      "is_recurring": true,
      "name": "Premium Plan with Trial",
      "slug": "premium-trial"
    },
    "plan_id": "cplan_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
    "status": "active",
    "subscription_id": "csub_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
    "updated_at": 1716883200000
  },
  "event_attributes": {
    "http_request": {
      "client_ip": "192.168.1.100",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
    }
  },
  "instance_id": "ins_2g7np7Hrk0SN6kj5EDMLDaKNL0S",
  "object": "event",
  "timestamp": 1716883200,
  "type": "subscriptionItem.freeTrialEnding"
}
```

## Conclusion

[Free trials](/docs/billing/free-trials) represent more than just a conversion strategy. They can be the key deciding factor in whether a user tries your product or moves on to your competitor.

Clerk's free trial implementation stays true to our core philosophy by allowing developers the easiest possible path to providing trials for your customers. Your `<PricingTable />` component automatically adapts and payment collection flows seamlessly handle trial-to-paid transitions. All while respecting your customers by informing them of their trial status.

---

# Postmortem: August 28, 2025 - elevated API latency and errors
URL: https://clerk.com/blog/postmortem-aug-28-2025-elevated-latency-errors.md
Date: 2025-08-28
Category: Company
Description: On August 28, 2025, a credential stuffing attack caused elevated API latency and errors. This postmortem details the impact, root cause, and remediations.

On August 28, two short periods of a distributed credential stuffing attack to our authentication endpoints of a specific tenant, led to elevated latency across the Frontend and Backend APIs and elevated errors in the Backend API.

Services remained partially available while we mitigated load and stabilized the underlying infrastructure. Importantly, our mitigation controls kept session token issuance operating normally throughout the incident.

- **Impact window #1:** 14:53–15:15 UTC (≈22 minutes)
- **Impact window #2:** 17:04–17:16 UTC (≈12 minutes)

### Timeline (UTC)

- **14:53** — Alert triggered for high CPU utilization in the storage layer; elevated API latency observed.
- **15:00** — Incident declared; mitigation initiated.
- **15:15** — Metrics returned to baseline.
- **17:04** — Second spike in CPU and API latency detected.
- **17:16** — Metrics returned to baseline.

### Root Cause Analysis

Investigation points to several compounding contributors in the authentication and data-write path:

1. **Automated traffic targeting authentication flows** generated an unusually high volume of sign-in and sign-up attempts.
2. **Write-intensive activity** from those attempts increased contention on hot authentication-related tables.
3. **A recently introduced CDC consumer** (used for near real-time consumption of auth events) lagged under burst conditions, amplifying contention within a segment of the storage tier.

**Observed error rates during the incident windows:** 2.52% of Backend API requests and 0.14% of Frontend API requests returned errors.

There was **no data loss or corruption**. The impact was limited to increased latency and errors.

### Remediations

- We disabled the lagging change-stream processor pending adjustments.
- We are strengthening adaptive protections at the edge and auth layer (rate limiting, anomaly detection, and upstream filtering).
- We are performing schema and query-path improvements on authentication workloads to reduce contention under spikes.
- We will be further strengthening per-customer isolation to contain issues to the originating application and minimize blast radius.

---

# Introducing Mosaic: Bring Your Brand to Every Authentication Flow
URL: https://clerk.com/blog/introducing-mosaic-bring-your-brand-to-every-authentication-flow.md
Date: 2025-08-20
Category: Company
Description: Introducing Mosaic, our new Figma design system that mirrors every Clerk UI component. Design and prototype auth flows that look and feel like your product before writing a single line of code.

At Clerk, we believe authentication should be as elegant and intuitive as the rest of your product. That's why our platform provides developers with drop-in authentication components that are secure, scalable, and easy to implement—just like Stripe, but for auth.

But we also know that authentication is part of your **brand experience**. From sign-up to user profile, every touchpoint needs to reflect your visual identity. And while our UI components are easily customizable for developers, we are now bringing that to designers as well.

Today, we're excited to introduce **Mosaic**, our new Figma design system. Mosaic gives product teams a powerful, visual way to explore and brand Clerk's UI components before writing a single line of code.

## What Is Mosaic?

**Mosaic** is a Figma design system that mirrors every Clerk UI component—exactly as they appear in production. You can use it to:

- Visually prototype authentication flows that look and feel like your product
- Apply your brand's colors, typography, and imagery in minutes
- Seamlessly transition from Figma to production with pixel-perfect consistency

Whether you're a designer creating your first Clerk experience, or a developer collaborating with a design team, Mosaic provides a shared foundation to ensure your auth UI feels like *your* product—not ours.

## Why We Built Mosaic

Clerk has always been about giving you control without the complexity. We provide fully-featured authentication out of the box, and you can customize every aspect of the UI through [themes](/docs/customization/themes), [CSS variables](/docs/customization/variables), or the API.

But we noticed something important: many design teams wanted to *preview and prototype* these customizations visually—before implementation. Some teams were rebuilding Clerk's components manually in Figma. Others were stuck with screenshots or guesswork.

Mosaic solves that problem. It's a ready-to-use, fully branded design kit that helps you explore, customize, and validate Clerk components right in Figma. You no longer have to imagine what your sign-up form will look like—you can see it, test it, and iterate.

## Getting Started with Mosaic

We've designed Mosaic to be as intuitive as possible. Here's how to get started and make Clerk's components your own in just a few steps.

You can access the [Clerk Mosaic UI Components in the Figma Community](https://www.figma.com/community/file/1521965427913384177).

### 1. Watch the Intro Video

If you prefer learning by watching rather than reading, we've put together a concise, 5-minute [**introductory video**](https://youtu.be/zgqyEZc1SvY). It walks you through everything from setup to customization, so you can get up and running quickly.

### 2. Set Your Brand Colors

Before diving into components, start by defining your **core brand colors**.

Open Figma's **Variables panel** and update the following key color variables:

- `Primary`
- `Background`
- `Input`
- `Danger`
- `Success`
- `Warning`
- `Foreground`
- `MutedForeground`
- `PrimaryForeground`
- `InputForeground`
- `Neutral`
- `Border`
- `Shadow`
- `Ring`
- `Muted`

All Mosaic components are linked to these variables. That means once you set your colors, every component will update automatically—keeping your brand consistent throughout.

For developers implementing these designs, you can achieve the same dynamic theming using [Clerk's CSS variables support](/changelog/2025-07-15-clerk-css-variables-support).

Want to tweak the shape of UI elements? You can also adjust the **border radius variable** to match your design language, whether that's soft and rounded or sharp and clean.

### 3. Run the Clerk Mosaic Color Generator Plugin

Once your core colors are defined, run the [**Clerk Mosaic Color Generator plugin**](https://www.figma.com/community/plugin/1459093748129448022/clerk-ui-components-color-palette-generator) in Figma. This tool automatically generates the full color palette needed for Clerk's UI components—shades, tints, contrast-safe variants, and more.

This step ensures visual cohesion and accessibility across your entire auth experience, without manual color tweaking.

### 4. Set Your Avatar

Personalization matters—especially for components that display real user data.

To make your designs feel realistic and aligned with your brand, update the Figma company logo to your company logo. This will be used across the components to showcase your brand. Also, assign your logo's fill color to the `PrimaryForeground` variable to ensure proper contrast and consistency.

### 5. Set Your Typography

Next, bring in your **brand typography**.

In Figma Variables, update the following:

- `fontFamily`
- `fontFamilyButtons`

Mosaic will automatically apply these fonts across all headings, labels, buttons, and body text—giving your auth UI the same voice as the rest of your app.

### 6. Set Your Appearance Mode

With colors, typography, and imagery set, you're now ready to use Mosaic with your own **visual identity**.

Simply pull any UI component into your canvas and set the **appearance mode** you've configured. You'll instantly see each component styled with your brand's look and feel—ready for design reviews, prototyping, or handoff.

![Mosaic components in action](./Slide_16_9_-_1.png)

### 7. Explore Clerk's Component Library

Mosaic includes every core Clerk component, organized and documented within the Figma file. That includes:

- [`<SignIn />`](/docs/components/authentication/sign-in)
- [`<SignUp />`](/docs/components/authentication/sign-up)
- [`<UserProfile />`](/docs/components/user/user-profile)
- [`<UserButton />`](/docs/components/user/user-button)
- [`<OrganizationProfile />`](/docs/components/organization/organization-profile)
- [`<OrganizationSwitcher />`](/docs/components/organization/organization-switcher)
- [`<CreateOrganization />`](/docs/components/organization/create-organization)
- [`<VerifyEmail />`](/docs/components/authentication/verify-email)
- [`<VerifyCode />`](/docs/components/authentication/verify-code)
- [`<ResetPassword />`](/docs/components/authentication/reset-password)

Each component is interactive and pre-wired to respond to your Figma variables, so you can test different states and variations instantly. There's no need to recreate anything from scratch—just customize and go.

When you're ready to implement these components in code, our comprehensive [component documentation](/docs/components/overview) provides everything you need to get started.

(Foundational internal components used to build these aren't exposed—they're there to keep things structured, but you won't need to touch them.)

### 8. Staying Updated

One important note: Mosaic is a **Figma file**, so when you duplicate it to your own workspace, it becomes disconnected from future updates.

If we release a new component or update the design system, you'll need to **pull the latest version manually** and reapply your brand settings (colors, avatar, typography). We'll announce all updates clearly, so you never miss a new feature or visual improvement.

## Final Thoughts

Authentication is the front door to your product—and Mosaic ensures that door looks exactly the way you want it to.

With a simple, visual setup process, deeply customizable styles, and a full suite of production-matching components, Mosaic gives you the tools to design authentication flows that *belong* to your brand from the first pixel to the last.

We're excited to see what you build with it!

---

# Resilience in Practice: Regional Failover at Clerk
URL: https://clerk.com/blog/resilience-in-practice-regional-failover.md
Date: 2025-08-18
Category: Engineering
Description: See how Clerk's new regional failover kept services running during a cloud provider outage.

On Monday, August 4th, we shared that Clerk had implemented [automatic regional failover](/changelog/2025-08-04-regional-failover)
for critical parts of our infrastructure, a major upgrade to protect against large-scale, regional-level outages.

A few days later, that system was put to the test.

## The August 6th incident

On August 6th, between 02:30 UTC and 04:11 UTC, our primary cloud region experienced intermittent issues.
Outages came in short intervals of 5-10 minutes. During each disruption, our health checks detected failures and
automatically rerouted traffic to our failover region.

From a customer perspective, there was no noticeable disruption. Aside from a few early errors, which were
automatically retried by our SDKs, the only potential impact was a brief increase in API latency during some
failover periods.

### The timeline

*2:55 UTC*: We experienced a sudden spike of `429` responses.

![Graph of 429 response spike](./responses-429.png)

*2:58 UTC*: Our team was alerted about downtime on our services.

![Screenshot of internal alert](./alert.png)

*2:59 UTC*: Investigation began. We noticed that our failover region had already picked up traffic and scaled up its
available containers, explaining why no customers had reported issues.

![Screenshot of request throughput and container count in failover region](./regional-failover-kicks-in.png)

*3:35 UTC*: Google confirmed their internal incident.

![Screenshot of Google confirming incident](./google-incident.png)

*3:50 UTC*: Another switchover to our failover region occurred.

*4:11 UTC*: Google's network stabilized and traffic returned to our primary region.

## Why resilience matters so much to Clerk

As an authentication provider, Clerk sits in front of every application that uses our platform.
This means that if our services experience an outage, the impact is immediate and visible within our customers'
applications.
Even brief interruptions can affect sign-ins, sign-ups, and session management, critical flows for end users.

High resilience isn't just a nice-to-have for us. It's fundamental to ensuring our customers' apps remain
reliable and trusted.

## How our regional failover works

We've always run our services across multiple availability zones to handle localized failures.
But the [June 26th service outage](/blog/postmortem-jun-26-2025-service-outage) highlighted a gap: a single-region
architecture, even with AZ redundancy, is still vulnerable to full regional outages.

Our new setup adds a continuously running failover region:

- **Always-on failover region:** The failover region continuously handles live production traffic to ensure it stays warm, healthy, and ready at all times.
- **Fast detection & switchover:** Health checks trigger an immediate reroute when issues are detected in the primary region.
- **Bidirectional failover:** If the failover region experiences issues, traffic switches back to the primary.
- **Local storage in failover:** Data is replicated to a dedicated storage layer in the failover region, minimizing latency during switchover.

![Regional failover high-level architecture](./architecture.png)

## What's next

This failover system is an important milestone but not the end of our reliability journey.

We're actively working on:

- Increasing the resilience of our stateful systems
- Exploring multi-cloud redundancy to remove single-provider dependencies
- Further automating recovery playbooks to reduce operational response times even more

Last week's event validated our regional failover strategy, showing early positive ROI as we continue expanding our
resilience capabilities.

---

# Build a Cross-Platform B2B App with Clerk, Expo, and Supabase
URL: https://clerk.com/blog/build-a-cross-platform-b2b-app-expo-supabase.md
Date: 2025-08-07
Category: Guides
Description: Learn how to add multi-tenancy to your React Native & Expo app using Clerk and Supabase.

[B2B applications](/b2b-saas) with multi-tenancy capabilities often outperform their single-tenant counterparts, driving higher revenue and accelerated growth. The ability to serve multiple organizations from a single codebase is both a technical feature and a business advantage.

Popular mobile apps like Notion, Slack, and Asana have solved this problem by implementing robust multi-tenancy features, allowing users to effortlessly manage multiple organizations and switch between them. What was once a complex feature reserved for enterprise applications is now expected in even small-scale business apps.

In this article, you'll learn how to add multi-tenancy to your [React Native & Expo app](/expo-authentication) using Clerk and Supabase. We'll walk through the process from setup to implementation, showing you how to give your users a seamless experience switching between their organizations.

## What is multi-tenancy?

[Multi-tenancy](/glossary/multi-tenancy) is when a single instance of an application is used by multiple tenants or organizations. Each tenant has their own data and can't see or access the data of other tenants. Modern applications often allow users to belong to multiple tenants and switch between them as needed.

Building an application with multiple tenants is complex. Allowing users to switch between tenants means you have to carefully consider and plan your security model to ensure the application is performant and that tenant data cannot be accessed by users who don't belong to that tenant.

## How does Clerk help?

Clerk makes it easy to add multi-tenancy to your app by providing the necessary [APIs to allow users not only to switch between tenants, but also to create and manage their own organizations](/docs/organizations/overview). This eliminates the need to build complex organization management systems from scratch.

When a user switches between tenants, the token issued to that user automatically includes the organization ID of the active tenant, making it easy to check which tenant's data should be presented. By handling these authentication complexities, Clerk allows developers to focus on building the core functionality of their application.

> \[!TIP]
> If you're working with web applications, you can also explore similar patterns with [React authentication](/react-authentication).

## Demo: Adding multi-tenancy to a time tracking app

To demonstrate how to add multi-tenancy to an app with Clerk, we'll be adding multi-tenancy to a time tracking app called Aika. Here is an overview of the tasks we'll be doing:

1. Enable organizations for the Clerk application
2. Implement an organization switcher so users can switch between their organizations
3. Add a method for users to create organizations and invite others
4. Update the Supabase RLS policies to use the organization ID to filter data

If you want to follow along, you can clone the [orgs-start branch](https://github.com/bmorrisondev/aika/tree/orgs-start) of the [Aika repository](https://github.com/bmorrisondev/aika). Follow the setup instructions in the README to get the app running.

> \[!NOTE]
> In the provided code snippets, be sure to check the comments for additional context on what each line or block does.

### Step 1: Enable organizations

Access the application in the Clerk dashboard and navigate to the **Configure** tab. Then in the left navigation under **Organization management** select the **Settings** option. Finally, toggle **Enable organizations**.

![Enable organizations](./image1.png)

> \[!NOTE]
> For more detailed guidance, see the [organizations setup documentation](/docs/organizations/overview#enable-organizations).

### Step 2: Implement organizations on the settings screen

Most of the changes in the app will take place in the settings screen. A new button will be added to the list that lets the user see which organization they currently have active, and lets them switch between their organizations, create new ones, or accept invitations using modals and Clerk's helper functions.

Below is an annotated picture of what these changes will look like:

1. The settings screen with a button to open the organization selection modal (shown)
2. A secondary modal that appears when selecting an invitation
3. A new screen to create an organization
4. Another new screen to invite members to a new organization

![New screens](./image2.png)

Before starting, install the `@clerk/types` package since you'll use it for type safety. If you need help with the initial [Expo setup](/expo-authentication), refer to the [quickstart guide](/docs/quickstarts/expo).

```bash
pnpm install @clerk/types
```

Now you'll create the button to open the organization selection modal. This will use helper functions from the Clerk Expo SDK to display the active organization. Since no organization is active by default, the button will display "Personal account".

Open the `app/(tabs)/protected/settings.tsx` file and make the following changes:

```tsx {{ filename: 'app/(tabs)/protected/settings.tsx', ins: [1, 8, 9, [15, 26], [47, 64], [131, 156]], del: [7], collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import React, { useState } from 'react'
import { Image, StyleSheet, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { useAuth, useUser } from '@clerk/clerk-expo'
import { useAuth, useOrganization, useOrganizationList, useUser } from '@clerk/clerk-expo'
import { UserOrganizationInvitationResource } from '@clerk/types'

export default function HomeScreen() {
  const { user } = useUser()
  const { signOut } = useAuth()

  // Get the list of organizations the user is a member of and any invitations they have
  // Note: The `useOrganizationList` function requires that you specify what's included in the response
  const { userMemberships, userInvitations, setActive } = useOrganizationList({
    userMemberships: true,
    userInvitations: true,
  })

  // Get the active organization
  const { organization } = useOrganization()

  // A state object to control the visibility of the organization selection modal (will be added next)
  const [modalVisible, setModalVisible] = useState(false)

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.titleContainer}>
        <ThemedText type="title">Settings</ThemedText>
      </ThemedView>
      <ThemedView style={styles.contentContainer}>
        <ThemedText type="subtitle">User Information</ThemedText>
        <ThemedView style={styles.userContainer}>
          <ThemedView style={styles.userImageContainer}>
            <Image source={{ uri: user?.imageUrl || '' }} style={styles.userImage} />
          </ThemedView>
          <ThemedView style={styles.userInfoContainer}>
            <ThemedText type="defaultSemiBold">
              {user?.firstName} {user?.lastName}
            </ThemedText>
            <ThemedText>{user?.emailAddresses[0].emailAddress}</ThemedText>
          </ThemedView>
        </ThemedView>

        <ThemedText type="subtitle">Organization</ThemedText>
        <TouchableOpacity onPress={() => setModalVisible(true)}>
          <ThemedView style={styles.organizationContainer}>
            <ThemedView style={styles.organizationImageContainer}>
              <Image
                source={{ uri: organization?.imageUrl || user?.imageUrl || '' }}
                style={styles.organizationImage}
              />
            </ThemedView>
            <ThemedView style={styles.organizationInfoContainer}>
              <ThemedText type="defaultSemiBold">
                {organization?.name || 'Personal account'}
              </ThemedText>
              <ThemedText>{organization?.slug || 'No organization'}</ThemedText>
            </ThemedView>
            <Ionicons name="chevron-forward" size={24} color="#888" />
          </ThemedView>
        </TouchableOpacity>

        <ThemedView style={styles.signOutButtonContainer}>
          <TouchableOpacity onPress={() => signOut()}>
            <ThemedText style={{ color: 'red' }}>Sign Out</ThemedText>
          </TouchableOpacity>
        </ThemedView>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 16,
  },
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
    marginTop: 16,
    paddingHorizontal: 16,
  },
  contentContainer: {
    gap: 12,
    marginBottom: 24,
    borderRadius: 8,
    padding: 16,
    width: '100%',
    height: 150,
    paddingHorizontal: 16,
  },
  userContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f7f7f7',
    padding: 8,
    borderRadius: 8,
  },
  userInfoContainer: {
    marginLeft: 12,
    flex: 1,
    backgroundColor: 'transparent',
  },
  userImageContainer: {
    width: 50,
    height: 50,
    borderRadius: 100,
    backgroundColor: 'transparent',
  },
  userImage: {
    width: 50,
    height: 50,
    borderRadius: 100,
    backgroundColor: 'transparent',
  },
  signOutButtonContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    borderRadius: 8,
    padding: 8,
    backgroundColor: '#ffebee',
    justifyContent: 'center',
  },
  organizationContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f7f7f7',
    padding: 8,
    borderRadius: 8,
    justifyContent: 'space-between',
  },
  organizationInfoContainer: {
    marginLeft: 12,
    flex: 1,
    backgroundColor: 'transparent',
  },
  organizationImageContainer: {
    width: 50,
    height: 50,
    borderRadius: 10,
    backgroundColor: 'transparent',
  },
  organizationImage: {
    width: 50,
    height: 50,
    borderRadius: 10,
    backgroundColor: 'transparent',
  },
})
```

Now create `components/OrganizationSwitcherModal.tsx` file that will store the code to display the organization switcher modal.

```tsx {{ filename: 'components/OrganizationSwitcherModal.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import { router } from 'expo-router'
import React from 'react'
import { FlatList, Image, Modal, SafeAreaView, StyleSheet, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'

interface Props {
  modalVisible: boolean
  setModalVisible: (visible: boolean) => void
  userInvitations: { data?: any[] } | null | undefined
  userMemberships: { data?: any[] } | null | undefined
  organization: any | null | undefined
  user: any | null | undefined
  handleOpenInvitation: (invitation: any) => void
  setActive?: (params: { organization: string | null }) => Promise<void>
}

export function OrganizationSwitcherModal({
  modalVisible,
  setModalVisible,
  userInvitations,
  userMemberships,
  organization,
  user,
  handleOpenInvitation,
  setActive,
}: Props) {
  // When the user selects an organization, use the parent component to set the active organization then redirect to the home screen
  const handleSelectOrganization = async (orgId: string) => {
    try {
      if (setActive) {
        if (orgId === 'personal-account') {
          await setActive({ organization: null })
        } else {
          await setActive({ organization: orgId })
        }
        setModalVisible(false)
        router.replace('/protected')
      }
    } catch (error) {
      console.error('Error setting active organization:', error)
    }
  }

  // When the user clicks the "Create New Organization" button, close the modal and navigate to the create organization screen
  const handleCreateOrganization = () => {
    // Close the modal first
    setModalVisible(false)
    // Navigate to the create organization screen
    router.push('/screens/create-organization')
  }
  return (
    <Modal
      animationType="slide"
      transparent={true}
      visible={modalVisible}
      onRequestClose={() => setModalVisible(false)}
    >
      <SafeAreaView style={styles.modalContainer}>
        <ThemedView style={styles.modalContent}>
          <ThemedView style={styles.modalHeader}>
            <ThemedText type="subtitle">Select Organization</ThemedText>
            <TouchableOpacity onPress={() => setModalVisible(false)}>
              <Ionicons name="close" size={24} color="#888" />
            </TouchableOpacity>
          </ThemedView>

          {/* Pending Invitations */}
          {(userInvitations?.data?.length || 0) > 0 && (
            <ThemedView style={styles.sectionContainer}>
              <ThemedView style={styles.sectionHeader}>
                <ThemedText type="defaultSemiBold" style={styles.sectionTitle}>
                  Pending Invitations
                </ThemedText>
                <ThemedView style={styles.badge}>
                  <ThemedText style={styles.badgeText}>{userInvitations?.data?.length}</ThemedText>
                </ThemedView>
              </ThemedView>
              <FlatList
                data={userInvitations?.data || []}
                keyExtractor={(item) => item.id}
                renderItem={({ item }) => (
                  <TouchableOpacity
                    style={[styles.invitationItem]}
                    onPress={() => handleOpenInvitation(item)}
                  >
                    <ThemedView style={styles.orgItemImageContainer}>
                      <Image
                        source={{
                          uri: item.publicOrganizationData.imageUrl || user?.imageUrl || '',
                        }}
                        style={styles.orgItemImage}
                      />
                    </ThemedView>
                    <ThemedView style={styles.orgItemInfoContainer}>
                      <ThemedText type="defaultSemiBold">
                        {item.publicOrganizationData.name}
                      </ThemedText>
                      <ThemedText>{item.publicOrganizationData.slug}</ThemedText>
                    </ThemedView>
                    <Ionicons name="mail-outline" size={24} color="#2196F3" />
                  </TouchableOpacity>
                )}
              />
              <ThemedView style={styles.divider} />
            </ThemedView>
          )}

          {/* Your Organizations */}
          <ThemedView style={styles.sectionContainer}>
            <ThemedView style={styles.sectionHeader}>
              <ThemedText type="defaultSemiBold" style={styles.sectionTitle}>
                Your Organizations
              </ThemedText>
            </ThemedView>

            <FlatList
              data={[
                // Personal account always at the top
                { isPersonal: true, id: 'personal-account', organization: null },
                ...(userMemberships?.data || []).map((membership) => ({
                  isPersonal: false,
                  id: membership.organization.id,
                  organization: membership.organization,
                })),
              ]}
              keyExtractor={(item) => item.id}
              renderItem={({ item }) => {
                // Handle personal account special case
                if (item.isPersonal) {
                  return (
                    <TouchableOpacity
                      style={[
                        styles.organizationItem,
                        organization?.id === undefined && styles.activeOrganization,
                      ]}
                      onPress={() => handleSelectOrganization(item.id)}
                    >
                      <ThemedView style={styles.orgItemImageContainer}>
                        <Image source={{ uri: user?.imageUrl || '' }} style={styles.orgItemImage} />
                      </ThemedView>
                      <ThemedView style={styles.orgItemInfoContainer}>
                        <ThemedText type="defaultSemiBold">Personal account</ThemedText>
                        <ThemedText>
                          {user?.username || user?.emailAddresses[0].emailAddress}
                        </ThemedText>
                      </ThemedView>
                      {organization?.id === undefined && (
                        <Ionicons name="checkmark" size={24} color="#4CAF50" />
                      )}
                    </TouchableOpacity>
                  )
                }

                // Regular organization item
                return (
                  <TouchableOpacity
                    style={[
                      styles.organizationItem,
                      item.id === organization?.id && styles.activeOrganization,
                    ]}
                    onPress={() => handleSelectOrganization(item.id)}
                  >
                    <ThemedView style={styles.orgItemImageContainer}>
                      <Image
                        source={{ uri: item.organization?.imageUrl || user?.imageUrl || '' }}
                        style={styles.orgItemImage}
                      />
                    </ThemedView>
                    <ThemedView style={styles.orgItemInfoContainer}>
                      <ThemedText type="defaultSemiBold">{item.organization?.name}</ThemedText>
                      <ThemedText>{item.organization?.slug}</ThemedText>
                    </ThemedView>
                    {item.id === organization?.id && (
                      <Ionicons name="checkmark" size={24} color="#4CAF50" />
                    )}
                  </TouchableOpacity>
                )
              }}
            />
          </ThemedView>

          {/* Add a button to create a new organization */}
          {/* This will direct them to app/screens/create-organization */}
          <TouchableOpacity style={styles.createOrgButton} onPress={handleCreateOrganization}>
            <Ionicons name="add-circle-outline" size={24} color="#fff" />
            <ThemedText style={styles.createOrgButtonText}>Create New Organization</ThemedText>
          </TouchableOpacity>
        </ThemedView>
      </SafeAreaView>
    </Modal>
  )
}

const styles = StyleSheet.create({
  modalContainer: {
    flex: 1,
    justifyContent: 'flex-end',
    backgroundColor: 'rgba(0, 0, 0, 0)',
  },
  modalContent: {
    backgroundColor: 'white',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    padding: 16,
    maxHeight: '80%',
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: -2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  modalHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 16,
    paddingBottom: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  sectionContainer: {
    marginBottom: 16,
  },
  sectionHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 8,
  },
  sectionTitle: {
    marginRight: 8,
  },
  badge: {
    backgroundColor: '#2196F3',
    borderRadius: 100,
    paddingHorizontal: 8,
  },
  badgeText: {
    color: 'white',
    fontSize: 12,
  },
  divider: {
    height: 1,
    backgroundColor: '#f0f0f0',
    marginVertical: 8,
  },
  invitationItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    borderRadius: 8,
    marginBottom: 8,
    backgroundColor: '#f7f7f7',
  },
  organizationItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    borderRadius: 8,
    marginBottom: 8,
    backgroundColor: '#f7f7f7',
  },
  activeOrganization: {
    backgroundColor: '#e3f2fd',
  },
  orgItemImageContainer: {
    width: 40,
    height: 40,
    borderRadius: 8,
    overflow: 'hidden',
  },
  orgItemImage: {
    width: 40,
    height: 40,
  },
  orgItemInfoContainer: {
    marginLeft: 12,
    flex: 1,
    backgroundColor: 'transparent',
  },
  createOrgButton: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#2196F3',
    padding: 12,
    borderRadius: 8,
  },
  createOrgButtonText: {
    color: 'white',
    marginLeft: 8,
  },
})
```

The `components/InvitationModal.tsx` is also needed so the user can accept invitations if they are selected from the organization switcher modal.

Create the `components/InvitationModal.tsx` and add the following code to it:

```tsx {{ filename: 'components/InvitationModal.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import { router } from 'expo-router'
import React from 'react'
import { Image, Modal, SafeAreaView, StyleSheet, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'

interface Props {
  isVisible: boolean
  onClose: () => void
  selectedInvitation: any | null
  user: any | null | undefined
  setActive?: (params: { organization: string | null }) => Promise<void>
  onComplete?: () => void
}

export function InvitationModal({
  isVisible,
  onClose,
  selectedInvitation,
  user,
  setActive,
  onComplete,
}: Props) {
  // Handle accepting an invitation and set the active organization
  const handleAcceptInvitation = async () => {
    try {
      if (!selectedInvitation) return
      await selectedInvitation?.accept()
      onClose()
      if (setActive) {
        await setActive({ organization: selectedInvitation?.publicOrganizationData.id })
      }
      if (onComplete) {
        onComplete()
      }
      router.replace('/protected')
    } catch (error) {
      console.error('Error accepting invitation:', error)
    }
  }

  // Handle skipping an invitation
  const handleSkipInvitation = async () => {
    try {
      onClose()
      if (onComplete) {
        onComplete()
      }
    } catch (error) {
      console.error('Error skipping invitation:', error)
    }
  }
  return (
    <Modal visible={isVisible} animationType="slide" transparent={true} onRequestClose={onClose}>
      <SafeAreaView style={styles.modalContainer}>
        <ThemedView style={styles.modalContent}>
          <ThemedView style={styles.modalHeader}>
            <ThemedText type="subtitle">Accept Invitation</ThemedText>
            <TouchableOpacity onPress={onClose}>
              <Ionicons name="close" size={24} color="#888" />
            </TouchableOpacity>
          </ThemedView>
          <ThemedView style={styles.modalBody}>
            <ThemedView style={styles.invitationDetails}>
              <ThemedView style={styles.orgImageContainer}>
                <Image
                  source={{
                    uri:
                      selectedInvitation?.publicOrganizationData.imageUrl || user?.imageUrl || '',
                  }}
                  style={styles.orgImage}
                />
              </ThemedView>
              <ThemedView style={styles.invitationTextContainer}>
                <ThemedText type="defaultSemiBold" style={styles.orgName}>
                  {selectedInvitation?.publicOrganizationData.name}
                </ThemedText>
                <ThemedText style={styles.orgSlug}>
                  {selectedInvitation?.publicOrganizationData.slug}
                </ThemedText>
              </ThemedView>
            </ThemedView>
            <ThemedText style={styles.invitationMessage}>
              You&apos;ve been invited to join this organization. Would you like to accept?
            </ThemedText>
          </ThemedView>
          <ThemedView style={styles.modalFooter}>
            <TouchableOpacity style={styles.rejectButton} onPress={handleSkipInvitation}>
              <ThemedText style={{ color: '#fff' }}>Not now</ThemedText>
            </TouchableOpacity>
            <TouchableOpacity style={styles.acceptButton} onPress={handleAcceptInvitation}>
              <ThemedText style={{ color: '#fff' }}>Accept</ThemedText>
            </TouchableOpacity>
          </ThemedView>
        </ThemedView>
      </SafeAreaView>
    </Modal>
  )
}

const styles = StyleSheet.create({
  modalContainer: {
    flex: 1,
    justifyContent: 'flex-end',
    backgroundColor: 'rgba(0, 0, 0, 0)',
  },
  modalContent: {
    backgroundColor: 'white',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    padding: 16,
    maxHeight: '80%',
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: -2,
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5,
  },
  modalHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 16,
    paddingBottom: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  modalBody: {
    padding: 16,
  },
  invitationDetails: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 16,
  },
  orgImageContainer: {
    width: 60,
    height: 60,
    borderRadius: 10,
    overflow: 'hidden',
    marginRight: 16,
  },
  orgImage: {
    width: 60,
    height: 60,
  },
  invitationTextContainer: {
    flex: 1,
  },
  orgName: {
    fontSize: 18,
    marginBottom: 4,
  },
  orgSlug: {
    opacity: 0.7,
  },
  invitationMessage: {
    marginTop: 8,
    lineHeight: 22,
  },
  modalFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginTop: 24,
  },
  rejectButton: {
    backgroundColor: '#f44336',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    flex: 1,
    marginRight: 8,
    alignItems: 'center',
  },
  acceptButton: {
    backgroundColor: '#4CAF50',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    flex: 1,
    marginLeft: 8,
    alignItems: 'center',
  },
})
```

Now back in the `app/(tabs)/protected/settings.tsx` file, add the two modal components to the screen. We're also adding two state objects for when a user selects an invitation and the required functions to handle those interactions:

```tsx {{ filename: 'app/(tabs)/protected/settings.tsx', ins: [11, 12, [30, 46], [86, 104]], collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import React, { useState } from 'react'
import { Image, StyleSheet, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { useAuth, useUser } from '@clerk/clerk-expo'
import { useAuth, useOrganization, useOrganizationList, useUser } from '@clerk/clerk-expo'
import { UserOrganizationInvitationResource } from '@clerk/types'

import { OrganizationSwitcherModal } from '@/components/OrganizationSwitcherModal'
import { InvitationModal } from '@/components/InvitationModal'

export default function HomeScreen() {
  const { user } = useUser()
  const { signOut } = useAuth()

  // Get the list of organizations the user is a member of and any invitations they have
  // Note: The `useOrganizationList` function requires that you specify what's included in the response
  const { userMemberships, userInvitations, setActive } = useOrganizationList({
    userMemberships: true,
    userInvitations: true,
  })

  // Get the active organization
  const { organization } = useOrganization()

  // A state object to control the visibility of the organization selection modal (will be added next)
  const [modalVisible, setModalVisible] = useState(false)
  const [selectedInvitation, setSelectedInvitation] =
    useState<UserOrganizationInvitationResource | null>(null)
  const [isInvitationModalVisible, setIsInvitationModalVisible] = useState(false)

  const handleInvitationComplete = () => {
    setSelectedInvitation(null)
  }

  const handleOpenInvitation = async (invitation: UserOrganizationInvitationResource) => {
    try {
      setSelectedInvitation(invitation)
      setModalVisible(false)
      setIsInvitationModalVisible(true)
    } catch (error) {
      console.error('Error setting active organization:', error)
    }
  }

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.titleContainer}>
        <ThemedText type="title">Settings</ThemedText>
      </ThemedView>
      <ThemedView style={styles.contentContainer}>
        <ThemedText type="subtitle">User Information</ThemedText>
        <ThemedView style={styles.userContainer}>
          <ThemedView style={styles.userImageContainer}>
            <Image source={{ uri: user?.imageUrl || '' }} style={styles.userImage} />
          </ThemedView>
          <ThemedView style={styles.userInfoContainer}>
            <ThemedText type="defaultSemiBold">
              {user?.firstName} {user?.lastName}
            </ThemedText>
            <ThemedText>{user?.emailAddresses[0].emailAddress}</ThemedText>
          </ThemedView>
        </ThemedView>

        <ThemedText type="subtitle">Organization</ThemedText>
        <TouchableOpacity onPress={() => setModalVisible(true)}>
          <ThemedView style={styles.organizationContainer}>
            <ThemedView style={styles.organizationImageContainer}>
              <Image
                source={{ uri: organization?.imageUrl || user?.imageUrl || '' }}
                style={styles.organizationImage}
              />
            </ThemedView>
            <ThemedView style={styles.organizationInfoContainer}>
              <ThemedText type="defaultSemiBold">
                {organization?.name || 'Personal account'}
              </ThemedText>
              <ThemedText>{organization?.slug || 'No organization'}</ThemedText>
            </ThemedView>
            <Ionicons name="chevron-forward" size={24} color="#888" />
          </ThemedView>
        </TouchableOpacity>

        <OrganizationSwitcherModal
          modalVisible={modalVisible}
          setModalVisible={setModalVisible}
          userInvitations={userInvitations}
          userMemberships={userMemberships}
          organization={organization}
          user={user}
          handleOpenInvitation={handleOpenInvitation}
          setActive={setActive}
        />

        <InvitationModal
          isVisible={isInvitationModalVisible}
          onClose={() => setIsInvitationModalVisible(false)}
          selectedInvitation={selectedInvitation}
          user={user}
          setActive={setActive}
          onComplete={handleInvitationComplete}
        />

        <ThemedView style={styles.signOutButtonContainer}>
          <TouchableOpacity onPress={() => signOut()}>
            <ThemedText style={{ color: 'red' }}>Sign Out</ThemedText>
          </TouchableOpacity>
        </ThemedView>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 16,
  },
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
    marginTop: 16,
    paddingHorizontal: 16,
  },
  contentContainer: {
    gap: 12,
    marginBottom: 24,
    borderRadius: 8,
    padding: 16,
    width: '100%',
    height: 150,
    paddingHorizontal: 16,
  },
  userContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f7f7f7',
    padding: 8,
    borderRadius: 8,
  },
  userInfoContainer: {
    marginLeft: 12,
    flex: 1,
    backgroundColor: 'transparent',
  },
  userImageContainer: {
    width: 50,
    height: 50,
    borderRadius: 100,
    backgroundColor: 'transparent',
  },
  userImage: {
    width: 50,
    height: 50,
    borderRadius: 100,
    backgroundColor: 'transparent',
  },
  signOutButtonContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    borderRadius: 8,
    padding: 8,
    backgroundColor: '#ffebee',
    justifyContent: 'center',
  },
  organizationContainer: {
    gap: 4,
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f7f7f7',
    padding: 8,
    borderRadius: 8,
    justifyContent: 'space-between',
  },
  organizationInfoContainer: {
    marginLeft: 12,
    flex: 1,
    backgroundColor: 'transparent',
  },
  organizationImageContainer: {
    width: 50,
    height: 50,
    borderRadius: 10,
    backgroundColor: 'transparent',
  },
  organizationImage: {
    width: 50,
    height: 50,
    borderRadius: 10,
    backgroundColor: 'transparent',
  },
})
```

Next, you'll add the two screens to allow users to create organizations. When the user selects "Create organization" they will be directed to the `create-organization` screen first. When they specify the name and slug of the organization, they will then go to the `add-organization-members` screen where they can invite members to the organization by email.

Create the `app/screens/create-organization.tsx` file and add the following code:

```tsx {{ filename: 'app/screens/create-organization.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import { router } from 'expo-router'
import React, { useState } from 'react'
import { StyleSheet, TextInput, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { useOrganizationList } from '@clerk/clerk-expo'

export default function CreateOrganizationScreen() {
  const [name, setName] = useState('')
  const [slug, setSlug] = useState('')
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState('')

  // Function to generate slug from name
  const generateSlug = (name: string): string => {
    return name
      .toLowerCase()
      .trim()
      .replace(/[^\w\s-]/g, '') // Remove special characters except whitespace and hyphens
      .replace(/\s+/g, '-') // Replace spaces with hyphens
      .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
  }

  // Update slug when name changes
  const handleNameChange = (text: string) => {
    setName(text)
    setSlug(generateSlug(text))
  }

  // Using the Clerk Expo SDK helper function to create an organization and set it as active
  const { createOrganization, setActive } = useOrganizationList({ userMemberships: true })

  // Function to reset the form state
  const resetForm = () => {
    setName('')
    setSlug('')
    setError('')
    setIsLoading(false)
  }

  const handleCreateOrganization = async () => {
    if (!name.trim()) {
      setError('Organization name is required')
      return
    }

    setIsLoading(true)
    setError('')

    try {
      if (createOrganization) {
        const organization = await createOrganization({
          name: name.trim(),
          slug: slug.trim() || undefined, // Use the provided slug or let Clerk generate one
        })

        // Reset the form state
        resetForm()

        // Set the active organization
        setActive({ organization: organization.id })

        // Navigate to the add members screen with the new organization ID
        router.push('/screens/add-organization-members')
      }
    } catch (error) {
      console.error('Error creating organization:', error)
      setError('Failed to create organization. Please try again.')
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.header}>
        <TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
          <Ionicons name="arrow-back" size={24} color="#000" />
        </TouchableOpacity>
        <ThemedText type="title">Create Organization</ThemedText>
      </ThemedView>

      <ThemedView style={styles.content}>
        <ThemedView style={styles.inputContainer}>
          <ThemedText type="subtitle">Organization Name *</ThemedText>
          <TextInput
            value={name}
            onChangeText={handleNameChange}
            placeholder="Enter organization name"
            style={styles.input}
          />
        </ThemedView>

        <ThemedView style={styles.inputContainer}>
          <ThemedText type="subtitle">Organization Slug (optional)</ThemedText>
          <ThemedText style={styles.helpText}>
            The slug will be used in URLs and must be unique. If not provided, one will be
            generated.
          </ThemedText>
          <TextInput
            value={slug}
            onChangeText={(text: string) => setSlug(text.toLowerCase().replace(/\s+/g, '-'))}
            placeholder="your-organization-slug"
            style={styles.input}
          />
        </ThemedView>

        {error ? <ThemedText style={styles.errorText}>{error}</ThemedText> : null}

        <TouchableOpacity
          style={[styles.nextButton, (!name.trim() || isLoading) && styles.disabledButton]}
          onPress={handleCreateOrganization}
          disabled={!name.trim() || isLoading}
        >
          <ThemedText style={styles.nextButtonText}>
            {isLoading ? 'Creating...' : 'Next'}
          </ThemedText>
        </TouchableOpacity>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 16,
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingTop: 16,
    paddingBottom: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  backButton: {
    marginRight: 16,
  },
  content: {
    padding: 16,
    flex: 1,
  },
  inputContainer: {
    marginBottom: 24,
  },
  input: {
    marginTop: 8,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderRadius: 8,
    padding: 12,
  },
  helpText: {
    fontSize: 12,
    color: '#666',
    marginTop: 4,
  },
  nextButton: {
    backgroundColor: '#2196F3',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginTop: 16,
  },
  nextButtonText: {
    color: 'white',
    fontWeight: '600',
    fontSize: 16,
  },
  disabledButton: {
    backgroundColor: '#BDBDBD',
  },
  errorText: {
    color: 'red',
    marginBottom: 16,
  },
})
```

Then create the `app/screens/add-organization-members.tsx` file and add the following code:

```tsx {{ filename: 'app/screens/add-organization-members.tsx', collapsible: true }}
import { Ionicons } from '@expo/vector-icons'
import { router } from 'expo-router'
import React, { useState } from 'react'
import { FlatList, StyleSheet, TextInput, TouchableOpacity } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { useOrganization } from '@clerk/clerk-expo'

interface MemberEmail {
  id: string
  email: string
}

export default function AddOrganizationMembersScreen() {
  const [emails, setEmails] = useState<MemberEmail[]>([])
  const [currentEmail, setCurrentEmail] = useState('')
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState('')

  const { organization } = useOrganization()

  const isValidEmail = (email: string) => {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
  }

  const handleAddEmail = () => {
    if (!currentEmail.trim()) return

    if (!isValidEmail(currentEmail)) {
      setError('Please enter a valid email address')
      return
    }

    // Check if email already exists in the list
    if (emails.some((item) => item.email.toLowerCase() === currentEmail.toLowerCase())) {
      setError('This email has already been added')
      return
    }

    setEmails([...emails, { id: Date.now().toString(), email: currentEmail.trim() }])
    setCurrentEmail('')
    setError('')
  }

  const handleRemoveEmail = (id: string) => {
    setEmails(emails.filter((email) => email.id !== id))
  }

  const handleInviteMembers = async () => {
    if (emails.length === 0) {
      setError('Please add at least one email address')
      return
    }

    setIsLoading(true)
    setError('')

    try {
      if (organization) {
        // Invite members using the Clerk Expo SDK
        await organization.inviteMembers({
          emailAddresses: emails.map((e) => e.email),
          role: 'org:member',
        })

        // Navigate back to the home screen
        router.replace('/protected')
      } else {
        throw new Error('Organization not found')
      }
    } catch (error) {
      console.error('Error inviting members:', error)
      setError('Failed to invite members. Please try again.')
    } finally {
      setIsLoading(false)
    }
  }

  const handleSkip = () => {
    // Navigate back to home screen without inviting anyone
    router.replace('/protected')
  }

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.header}>
        <TouchableOpacity onPress={() => router.back()} style={styles.backButton}>
          <Ionicons name="arrow-back" size={24} color="#000" />
        </TouchableOpacity>
        <ThemedText type="title">Add Members</ThemedText>
      </ThemedView>

      <ThemedView style={styles.content}>
        <ThemedText type="subtitle">Invite members to your organization</ThemedText>
        <ThemedText style={styles.helpText}>
          Enter email addresses of people you&apos;d like to invite to your organization.
        </ThemedText>

        <ThemedView style={styles.inputContainer}>
          <ThemedView style={styles.emailInputRow}>
            <TextInput
              value={currentEmail}
              onChangeText={(text: string) => {
                setCurrentEmail(text)
                if (error) setError('')
              }}
              placeholder="Enter email address"
              style={styles.input}
              keyboardType="email-address"
              autoCapitalize="none"
              onSubmitEditing={handleAddEmail}
            />
            <TouchableOpacity
              style={styles.addButton}
              onPress={handleAddEmail}
              disabled={!currentEmail.trim()}
            >
              <Ionicons name="add" size={24} color="white" />
            </TouchableOpacity>
          </ThemedView>

          {error ? <ThemedText style={styles.errorText}>{error}</ThemedText> : null}
        </ThemedView>

        <ThemedView style={styles.emailListContainer}>
          <FlatList
            data={emails}
            keyExtractor={(item) => item.id}
            renderItem={({ item }) => (
              <ThemedView style={styles.emailItem}>
                <ThemedText>{item.email}</ThemedText>
                <TouchableOpacity onPress={() => handleRemoveEmail(item.id)}>
                  <Ionicons name="close-circle" size={20} color="#888" />
                </TouchableOpacity>
              </ThemedView>
            )}
            ListEmptyComponent={
              <ThemedView style={styles.emptyList}>
                <ThemedText style={styles.emptyListText}>No members added yet</ThemedText>
              </ThemedView>
            }
          />
        </ThemedView>

        <ThemedView style={styles.buttonContainer}>
          <TouchableOpacity style={styles.skipButton} onPress={handleSkip} disabled={isLoading}>
            <ThemedText style={styles.skipButtonText}>Skip</ThemedText>
          </TouchableOpacity>

          <TouchableOpacity
            style={[styles.inviteButton, isLoading && styles.disabledButton]}
            onPress={handleInviteMembers}
            disabled={emails.length === 0 || isLoading}
          >
            <ThemedText style={styles.inviteButtonText}>
              {isLoading ? 'Inviting...' : 'Invite Members'}
            </ThemedText>
          </TouchableOpacity>
        </ThemedView>
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 16,
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 16,
    paddingTop: 16,
    paddingBottom: 8,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  backButton: {
    marginRight: 16,
  },
  content: {
    padding: 16,
    flex: 1,
  },
  helpText: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
    marginBottom: 16,
  },
  inputContainer: {
    marginBottom: 16,
  },
  emailInputRow: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderRadius: 8,
    padding: 12,
  },
  addButton: {
    backgroundColor: '#2196F3',
    width: 48,
    height: 48,
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
    marginLeft: 8,
  },
  emailListContainer: {
    flex: 1,
    marginTop: 8,
    borderWidth: 1,
    borderColor: '#e0e0e0',
    borderRadius: 8,
    padding: 8,
  },
  emailItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  emptyList: {
    padding: 24,
    alignItems: 'center',
  },
  emptyListText: {
    color: '#888',
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginTop: 16,
  },
  skipButton: {
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#e0e0e0',
    flex: 1,
    marginRight: 8,
  },
  skipButtonText: {
    fontWeight: '600',
  },
  inviteButton: {
    backgroundColor: '#2196F3',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    flex: 2,
  },
  inviteButtonText: {
    color: 'white',
    fontWeight: '600',
  },
  disabledButton: {
    backgroundColor: '#BDBDBD',
  },
  errorText: {
    color: 'red',
    marginTop: 8,
  },
})
```

The last detail is to add the new screens into the routing configuration in `app/(tabs)/_layout.tsx`. These are being added to the protected stack so only authorized users may access them.

Open that file and make the following changes:

```tsx {{ filename: 'app/(tabs)/_layout.tsx', ins: [16, 17] }}
import { useAuth } from '@clerk/clerk-expo'
import { Stack } from 'expo-router'

export default function AppLayout() {
  const { isSignedIn } = useAuth()

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Protected guard={!isSignedIn}>
        <Stack.Screen name="index" />
        <Stack.Screen name="sign-up" />
      </Stack.Protected>

      <Stack.Protected guard={isSignedIn!}>
        <Stack.Screen name="protected" />
        <Stack.Screen name="screens/create-organization" />
        <Stack.Screen name="screens/add-organization-members" />
      </Stack.Protected>
    </Stack>
  )
}
```

Now you may access the application using `pnpm start` and then `w` to open the application in your web browser. Try creating a new organization and adding a member to it. It's best if you have an alternate email address you can use for this. If you do, invite that email address then sign into the app as that account and check the organization switcher modal as that user to test accepting an invitation.

### Step 3: Update RLS policies to use organization ID

One thing you may notice is that the data on the home screen stays the same regardless of the active organization. This is because the existing [RLS policies](https://supabase.com/docs/guides/database/postgres/row-level-security) still use the `sub` claim (which is the user ID) to identify the user making requests. We need to update the RLS policies to use the `o`.`id` claim instead to identify the active organization of the user making the request. The policies will be configured to fall back on the `sub` claim to identify the user if the `o`.`id` claim is not present (as in, they are in their personal account).

Instead of accessing the claims from the [JWT](/glossary#json-web-token) directly, a custom SQL function can be used to extract the correct claim and return it as a string to the RLS policy. This approach minimizes potential mistakes and centralizes the logic for accessing the claims in one place.

Start by running the following command in your terminal to create a new migration file.

```bash
supabase migration new setup-orgs
```

A new file with the name `setup-orgs` will be created in the `migrations` directory. Open that file and add the following SQL to add the function, update the existing policies, and add a `created_by` column to the `time_entries` table (which will be used in the next step to identify timers on the home screen of the app):

```sql {{ filename: 'supabase/migrations/{TIMESTAMP}_setup-orgs.sql' }}
-- Add requesting_owner_id function
create or replace function requesting_owner_id()
returns text as $$
    select coalesce(
        (auth.jwt() -> 'o'::text) ->> 'id'::text,
        (auth.jwt() ->> 'sub'::text)
    )::text;
$$ language sql stable;

-- Update RLS policies to use the new requesting_owner_id function
-- Update select policy
DROP POLICY IF EXISTS select_own_time_entries ON time_entries;
CREATE POLICY select_own_time_entries ON time_entries
  FOR SELECT
  USING (owner_id = requesting_owner_id());

-- Update insert policy
DROP POLICY IF EXISTS insert_own_time_entries ON time_entries;
CREATE POLICY insert_own_time_entries ON time_entries
  FOR INSERT
  WITH CHECK (owner_id = requesting_owner_id());

-- Update update policy
DROP POLICY IF EXISTS update_own_time_entries ON time_entries;
CREATE POLICY update_own_time_entries ON time_entries
  FOR UPDATE
  USING (owner_id = requesting_owner_id());

-- Update delete policy
DROP POLICY IF EXISTS delete_own_time_entries ON time_entries;
CREATE POLICY delete_own_time_entries ON time_entries
  FOR DELETE
  USING (owner_id = requesting_owner_id());

-- Add created_by column to time_entries table
alter table time_entries add created_by text default (auth.jwt() ->> 'sub'::text);
```

Now run the following command to apply the migration:

```bash
supabase db push
```

Next you'll need to update some of the database access code in `app/(tabs)/protected/index.tsx` to use the organization ID instead of the user ID when creating records. These changes also add a `setInterval` to refresh the entries every 30 seconds, acting as a polling mechanism to detect changes from other users.

Open the `app/(tabs)/protected/index.tsx` file and make the following changes:

```tsx {{ filename: 'app/(tabs)/protected/index.tsx', ins: [11, 19, 20, 25, 26, 43, 44, [107, 109], 174, [241, 261]], del: [10, 106, 173, [228, 239]], collapsible: true }}
import React, { useEffect, useRef, useState } from 'react'
import { FlatList, StyleSheet } from 'react-native'

import { ThemedText } from '@/components/ThemedText'
import { ThemedView } from '@/components/ThemedView'
import { TimeEntryItem } from '@/components/TimeEntryItem'
import { TimerCounter } from '@/components/TimerCounter'
import { TimerInputForm } from '@/components/TimerInputForm'
import { createSupabaseClerkClient } from '@/utils/supabase'
import { useAuth, useUser } from '@clerk/clerk-expo'
import { useAuth, useOrganization, useUser } from '@clerk/clerk-expo'

interface TimeEntry {
  id: string
  description: string
  start_time: string
  end_time: string | null
  created_at: string
  // This is the user ID of the user who created the time entry
  created_by: string
}

export default function HomeScreen() {
  const { user } = useUser()
  // This is the active organization of the user
  const { organization } = useOrganization()
  const { getToken } = useAuth()
  const [description, setDescription] = useState('')
  const [isTimerRunning, setIsTimerRunning] = useState(false)
  const [currentEntryId, setCurrentEntryId] = useState<string | null>(null)
  const [timeEntries, setTimeEntries] = useState<TimeEntry[]>([])
  const [elapsedTime, setElapsedTime] = useState(0)
  // We track the start time to calculate elapsed time
  const startTimeRef = useRef<Date | null>(null)
  // Track the last time we updated the elapsed time
  const lastUpdateRef = useRef<number>(0)

  // Timer interval reference
  const timerIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)

  const supabase = createSupabaseClerkClient(getToken())

  // This is the owner ID of the time entry, defaults to the user ID if no organization is active
  const ownerId = organization?.id || user?.id

  // Stop the timer counter
  const stopTimerCounter = () => {
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current)
      timerIntervalRef.current = null
    }
    setElapsedTime(0)
    startTimeRef.current = null
  }

  // Start the timer counter
  const startTimerCounter = (initialStartTime?: Date) => {
    const start = initialStartTime || new Date()
    startTimeRef.current = start
    lastUpdateRef.current = Date.now()

    // Calculate initial elapsed time
    const now = new Date()
    const initialDiffInSeconds = Math.floor((now.getTime() - start.getTime()) / 1000)
    setElapsedTime(initialDiffInSeconds)

    // Clear any existing interval
    if (timerIntervalRef.current) {
      clearInterval(timerIntervalRef.current)
      timerIntervalRef.current = null
    }

    // Set up the interval to update elapsed time every second
    const intervalId = setInterval(() => {
      if (startTimeRef.current) {
        // Use the current time for accurate timing
        const now = new Date()
        const diffInSeconds = Math.floor((now.getTime() - startTimeRef.current.getTime()) / 1000)

        // Only update if the time has actually changed
        if (diffInSeconds !== elapsedTime) {
          setElapsedTime(diffInSeconds)
        }
      }
    }, 500) // Update more frequently for better accuracy

    timerIntervalRef.current = intervalId
  }

  // Function to fetch time entries from Supabase
  const fetchTimeEntries = async () => {
    try {
      const { data, error } = await supabase
        .from('time_entries')
        .select('*')
        .order('start_time', { ascending: false })

      if (error) {
        console.error('Error fetching time entries:', error)
        return
      }

      setTimeEntries(data || [])

      // Check if there's an active timer (entry without end_time)
      const activeEntry = data?.find((entry) => !entry.end_time)
      const activeEntry = data?.find(
        (entry: TimeEntry) => !entry.end_time && entry.created_by === user?.id,
      )

      if (activeEntry) {
        setIsTimerRunning(true)
        setCurrentEntryId(activeEntry.id)
        setDescription(activeEntry.description)

        // Start the timer counter with the saved start time
        const startDate = new Date(activeEntry.start_time)
        startTimerCounter(startDate)
      }
    } catch (error) {
      console.error('Error fetching time entries:', error)
    }
  }

  // Function to update a time entry
  const updateTimeEntry = async (id: string, updates: Partial<TimeEntry>) => {
    try {
      const { error } = await supabase.from('time_entries').update(updates).eq('id', id)

      if (error) {
        console.error('Error updating time entry:', error)
        return
      }

      // Refresh the time entries list
      fetchTimeEntries()
    } catch (error) {
      console.error('Error updating time entry:', error)
    }
  }

  // Function to delete a time entry
  const deleteTimeEntry = async (id: string) => {
    try {
      const { error } = await supabase.from('time_entries').delete().eq('id', id)

      if (error) {
        console.error('Error deleting time entry:', error)
        return
      }

      // Refresh the time entries list
      fetchTimeEntries()
    } catch (error) {
      console.error('Error deleting time entry:', error)
    }
  }

  // Start a new timer
  const startTimer = async () => {
    if (!description.trim()) {
      alert('Please enter what you are working on')
      return
    }

    try {
      const startDate = new Date()
      const { data, error } = await supabase
        .from('time_entries')
        .insert({
          description: description.trim(),
          start_time: startDate.toISOString(),
          owner_id: user?.id,
          owner_id: ownerId,
        })
        .select()

      if (error) {
        console.error('Error starting timer:', error)
        return
      }

      if (data && data[0]) {
        setIsTimerRunning(true)
        setCurrentEntryId(data[0].id)
        startTimerCounter(startDate)
        fetchTimeEntries()
      }
    } catch (error) {
      console.error('Error in startTimer:', error)
    }
  }

  // Stop the current timer
  const stopTimer = async () => {
    if (!currentEntryId) return

    try {
      const { error } = await supabase
        .from('time_entries')
        .update({ end_time: new Date().toISOString() })
        .eq('id', currentEntryId)

      if (error) {
        console.error('Error stopping timer:', error)
        return
      }

      setIsTimerRunning(false)
      setCurrentEntryId(null)
      setDescription('')
      stopTimerCounter()
      fetchTimeEntries()
    } catch (error) {
      console.error('Error in stopTimer:', error)
    }
  }

  // Update the elapsed time even when the app is in background
  useEffect(() => {
    if (isTimerRunning && startTimeRef.current) {
      const now = new Date()
      const diffInSeconds = Math.floor((now.getTime() - startTimeRef.current.getTime()) / 1000)
      setElapsedTime(diffInSeconds)
    }
  }, [isTimerRunning])

  // Fetch time entries when component mounts
  useEffect(() => {
    fetchTimeEntries()

    // Clean up timer interval when component unmounts
    return () => {
      if (timerIntervalRef.current) {
        clearInterval(timerIntervalRef.current)
        timerIntervalRef.current = null
      }
    }
  }, [])

  // Fetch time entries when component mounts or user changes
  useEffect(() => {
    fetchTimeEntries()

    // Set up periodic refresh every 30 seconds to detect changes from other users
    const refreshInterval = setInterval(() => {
      fetchTimeEntries()
    }, 30000)

    // Clean up interval on component unmount
    return () => {
      if (timerIntervalRef.current) {
        clearInterval(timerIntervalRef.current)
        timerIntervalRef.current = null
      }

      if (refreshInterval) {
        clearInterval(refreshInterval)
      }
    }
  }, [ownerId])

  return (
    <ThemedView style={styles.container}>
      <ThemedView style={styles.titleContainer}>
        <ThemedText type="title">⏳ Aika Timer</ThemedText>
      </ThemedView>

      {/* Timer Form */}
      <ThemedView style={styles.formContainer}>
        {!isTimerRunning ? (
          <TimerInputForm
            description={description}
            onDescriptionChange={setDescription}
            onStartTimer={startTimer}
          />
        ) : (
          <TimerCounter
            isRunning={isTimerRunning}
            description={description}
            elapsedTime={elapsedTime}
            onStart={startTimer}
            onStop={stopTimer}
          />
        )}
      </ThemedView>

      {/* Time Entries List */}
      <ThemedView style={styles.entriesContainer}>
        <ThemedText type="subtitle" style={styles.entriesTitle}>
          Previous Work Logs
        </ThemedText>
        {timeEntries.length === 0 ? (
          <ThemedText style={styles.emptyText}>
            No work logs yet. Start tracking your time!
          </ThemedText>
        ) : (
          <FlatList
            data={timeEntries}
            keyExtractor={(item) => item.id}
            renderItem={({ item }) => (
              <TimeEntryItem item={item} onUpdate={updateTimeEntry} onDelete={deleteTimeEntry} />
            )}
            style={styles.list}
            scrollEnabled={true}
            showsVerticalScrollIndicator={true}
            contentContainerStyle={styles.listContentContainer}
          />
        )}
      </ThemedView>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  titleContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
    marginTop: 16,
    paddingHorizontal: 16,
  },
  formContainer: {
    gap: 12,
    marginBottom: 24,
    borderRadius: 8,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    width: '100%',
    height: 150,
    paddingHorizontal: 16,
  },
  disabledInput: {
    backgroundColor: '#f0f0f0',
    color: '#666',
  },
  entriesContainer: {
    gap: 12,
    flex: 1,
  },
  list: {
    paddingHorizontal: 16,
  },
  listContentContainer: {
    flexGrow: 1,
    paddingBottom: 16,
  },
  entriesTitle: {
    paddingHorizontal: 16,
  },
  emptyText: {
    fontStyle: 'italic',
    color: '#888',
    marginTop: 8,
    paddingHorizontal: 16,
  },
  signOutButton: {
    backgroundColor: '#64748B',
    padding: 12,
    borderRadius: 6,
    alignItems: 'center',
    marginTop: 8,
  },
  container: {
    flex: 1,
    paddingTop: 16,
  },
  contentContainer: {
    padding: 16,
  },
})
```

The last thing to do is update the `TimeEntryItem` component to render the name of the user who created the entry. This will use the `created_by` field in the database, which is set to the user ID when the entry is created.

This is used along with the [`useOrganization` hook](/docs/references/react/use-organization) from the Clerk Expo SDK to load the list of members in the organization and find the member with the matching user ID.

Open the `components/TimeEntryItem.tsx` and make the following changes:

```tsx {{ filename: 'components/TimeEntryItem.tsx', ins: [1, 24, 83, 84, 90, [139, 157], 270, [274, 277], [585, 591]], collapsible: true }}
import { useOrganization, useUser } from '@clerk/clerk-expo'
import { Ionicons } from '@expo/vector-icons'
import React, { useEffect, useRef, useState } from 'react'
import {
  Animated,
  Easing,
  Modal,
  StyleSheet,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native'

// Import ThemedText and ThemedView components
import { ThemedText } from './ThemedText'
import { ThemedView } from './ThemedView'

interface TimeEntry {
  id: string
  description: string
  start_time: string
  end_time: string | null
  created_at: string
  created_by?: string
}

interface TimeEntryItemProps {
  item: TimeEntry
  onUpdate?: (id: string, updates: Partial<TimeEntry>) => Promise<void>
  onDelete?: (id: string) => Promise<void>
}

// Custom slow spinner component
function SlowSpinner() {
  const spinValue = useRef(new Animated.Value(0)).current

  useEffect(() => {
    // Create a continuous rotation animation
    const startRotation = () => {
      // Reset the value to 0 when starting
      spinValue.setValue(0)

      // Create the animation
      Animated.timing(spinValue, {
        toValue: 1,
        duration: 3000, // Slower animation (3 seconds per rotation)
        easing: Easing.linear,
        useNativeDriver: true,
      }).start(() => startRotation()) // When complete, run again
    }

    // Start the animation loop
    startRotation()

    // Cleanup function
    return () => {
      // This will stop any pending animations when component unmounts
      spinValue.stopAnimation()
    }
  }, [spinValue])

  const spin = spinValue.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  })

  return (
    <Animated.View
      style={{
        transform: [{ rotate: spin }],
        width: 16,
        height: 16,
        borderWidth: 2,
        borderColor: '#2563EB',
        borderTopColor: 'transparent',
        borderRadius: 8,
      }}
    />
  )
}

export function TimeEntryItem({ item, onUpdate, onDelete }: TimeEntryItemProps) {
  const { user } = useUser()
  const { organization } = useOrganization()
  const [isModalVisible, setIsModalVisible] = useState(false)
  const [editedDescription, setEditedDescription] = useState(item.description)
  const [startDate, setStartDate] = useState(new Date(item.start_time))
  const [endDate, setEndDate] = useState(item.end_time ? new Date(item.end_time) : null)
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [creatorName, setCreatorName] = useState<string | null>(null)

  // Track the text inputs separately from the actual date objects
  const [startDateText, setStartDateText] = useState('')
  const [endDateText, setEndDateText] = useState('')

  // Reset form state when modal is opened
  useEffect(() => {
    if (isModalVisible) {
      setEditedDescription(item.description)
      setStartDate(new Date(item.start_time))
      setEndDate(item.end_time ? new Date(item.end_time) : null)

      // Initialize text fields with formatted dates
      const formattedStartDate = formatDate(new Date(item.start_time).toISOString())
      const formattedStartTime = formatTime(new Date(item.start_time))
      setStartDateText(`${formattedStartDate} ${formattedStartTime}`)

      if (item.end_time) {
        const formattedEndDate = formatDate(new Date(item.end_time).toISOString())
        const formattedEndTime = formatTime(new Date(item.end_time))
        setEndDateText(`${formattedEndDate} ${formattedEndTime}`)
      } else {
        setEndDateText('')
      }
    }
  }, [isModalVisible, item.description, item.start_time, item.end_time])

  // Format date to display in a readable format
  const formatDate = (dateString: string) => {
    const date = new Date(dateString)
    return date.toLocaleDateString()
  }

  // Format time to display in a readable format
  const formatTime = (date: Date) => {
    return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
  }

  // Initialize date text fields on component mount
  useEffect(() => {
    if (startDate) {
      setStartDateText(`${formatDate(startDate.toISOString())} ${formatTime(startDate)}`)
    }
    if (endDate) {
      setEndDateText(`${formatDate(endDate.toISOString())} ${formatTime(endDate)}`)
    }
  }, [startDate, endDate])

  // Get creator name if created_by is available
  useEffect(() => {
    if (item.created_by) {
      // If the current user is the creator
      if (user && user.id === item.created_by) {
        setCreatorName('You')
      } else {
        organization?.getMemberships().then((memberships) => {
          const member = memberships.data.find((m) => m.publicUserData?.userId === item.created_by)
          setCreatorName(
            `${member?.publicUserData?.firstName} ${member?.publicUserData?.lastName} (${member?.publicUserData?.identifier})` ||
              'Another team member',
          )
        })
      }
    } else {
      setCreatorName(null)
    }
  }, [item.created_by, user])

  // Calculate duration between start and end time
  const calculateDuration = (start: string, end: string | null) => {
    if (!end) return 'In progress'

    const startDate = new Date(start)
    const endDate = new Date(end)
    const diffMs = endDate.getTime() - startDate.getTime()

    const diffMins = Math.floor(diffMs / 60000)
    const hours = Math.floor(diffMins / 60)
    const mins = diffMins % 60

    return `${hours}h${mins}m`
  }

  // Handle saving changes
  const handleSave = async () => {
    if (!onUpdate) return

    // Try to parse dates from text inputs before saving
    let validStartDate = startDate
    let validEndDate = endDate

    // Parse start date text
    try {
      const [datePart, timePart] = startDateText.split(' ')
      if (datePart && timePart) {
        const [month, day, year] = datePart.split('/')
        const [hours, minutes] = timePart.replace('AM', '').replace('PM', '').trim().split(':')

        if (month && day && year && hours && minutes) {
          const newDate = new Date()
          newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

          let hrs = parseInt(hours)
          if (timePart.includes('PM') && hrs < 12) hrs += 12
          if (timePart.includes('AM') && hrs === 12) hrs = 0

          newDate.setHours(hrs, parseInt(minutes))
          validStartDate = newDate
        }
      }
    } catch {
      // Use the existing startDate if parsing fails
    }

    // Parse end date text if it exists
    if (endDateText && endDate) {
      try {
        const [datePart, timePart] = endDateText.split(' ')
        if (datePart && timePart) {
          const [month, day, year] = datePart.split('/')
          const [hours, minutes] = timePart.replace('AM', '').replace('PM', '').trim().split(':')

          if (month && day && year && hours && minutes) {
            const newDate = new Date()
            newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

            let hrs = parseInt(hours)
            if (timePart.includes('PM') && hrs < 12) hrs += 12
            if (timePart.includes('AM') && hrs === 12) hrs = 0

            newDate.setHours(hrs, parseInt(minutes))
            validEndDate = newDate
          }
        }
      } catch {
        // Use the existing endDate if parsing fails
        console.error('Failed to parse end date')
      }
    }

    setIsSubmitting(true)
    try {
      await onUpdate(item.id, {
        description: editedDescription,
        start_time: validStartDate.toISOString(),
        end_time: validEndDate ? validEndDate.toISOString() : null,
      })
      setIsModalVisible(false)
    } catch (error) {
      console.error('Failed to update time entry:', error)
    } finally {
      setIsSubmitting(false)
    }
  }

  // Handle deleting the entry
  const handleDelete = async () => {
    if (!onDelete) return

    setIsSubmitting(true)
    try {
      await onDelete(item.id)
      setIsModalVisible(false)
    } catch (error) {
      console.error('Error deleting time entry:', error)
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <ThemedView style={styles.container}>
      {/* View mode - always visible */}
      <TouchableOpacity
        style={styles.entryItem}
        onPress={() => setIsModalVisible(true)}
        activeOpacity={0.7}
      >
        <ThemedView style={styles.entryContent}>
          <View style={styles.descriptionContainer}>
            <ThemedText type="defaultSemiBold" numberOfLines={1}>
              {item.description}
            </ThemedText>
            {organization && creatorName && (
              <ThemedText style={styles.creatorText}>{creatorName}</ThemedText>
            )}
          </View>
          {item.end_time ? (
            <ThemedText style={styles.durationText}>
              {calculateDuration(item.start_time, item.end_time)}
            </ThemedText>
          ) : (
            <View style={styles.inProgressContainer}>
              <SlowSpinner />
              <ThemedText style={styles.inProgressText}>In progress</ThemedText>
            </View>
          )}
        </ThemedView>
        <View style={styles.rightSection}>
          <ThemedText style={styles.dateText}>{formatDate(item.start_time)}</ThemedText>
          <Ionicons name="chevron-forward" size={16} color="#888" />
        </View>
      </TouchableOpacity>

      {/* Edit mode - in modal */}
      <Modal
        visible={isModalVisible}
        onRequestClose={() => setIsModalVisible(false)}
        animationType="fade"
        transparent
      >
        <View style={styles.modalBackdrop}>
          <View style={styles.modalContainer}>
            <ThemedView style={styles.modalContent}>
              <ThemedText style={styles.modalTitle}>Edit Time Entry</ThemedText>

              <View style={styles.formGroup}>
                <ThemedText style={styles.label}>Description</ThemedText>
                <TextInput
                  style={styles.input}
                  value={editedDescription}
                  onChangeText={setEditedDescription}
                  placeholder="What were you working on?"
                  placeholderTextColor="#aaa"
                />
              </View>

              <View style={styles.formGroup}>
                <ThemedText style={styles.label}>Start Time</ThemedText>
                <TextInput
                  style={styles.input}
                  value={startDateText}
                  onChangeText={(text) => {
                    // Just update the text field without validation
                    setStartDateText(text)

                    // Try to parse the date but don't throw errors
                    try {
                      const [datePart, timePart] = text.split(' ')
                      if (datePart && timePart) {
                        const [month, day, year] = datePart.split('/')
                        const [hours, minutes] = timePart
                          .replace('AM', '')
                          .replace('PM', '')
                          .trim()
                          .split(':')

                        if (month && day && year && hours && minutes) {
                          const newDate = new Date(startDate)
                          newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

                          let hrs = parseInt(hours)
                          if (timePart.includes('PM') && hrs < 12) hrs += 12
                          if (timePart.includes('AM') && hrs === 12) hrs = 0

                          newDate.setHours(hrs, parseInt(minutes))
                          setStartDate(newDate)
                        }
                      }
                    } catch {
                      // Silently fail - we'll use the previous valid date if parsing fails
                    }
                  }}
                  placeholder="MM/DD/YYYY HH:MM AM/PM"
                  placeholderTextColor="#aaa"
                />
              </View>

              <View style={styles.formGroup}>
                <ThemedText style={styles.label}>End Time</ThemedText>
                {endDate ? (
                  <TextInput
                    style={styles.input}
                    value={endDateText}
                    onChangeText={(text) => {
                      // Just update the text field without validation
                      setEndDateText(text)

                      // Try to parse the date but don't throw errors
                      try {
                        const [datePart, timePart] = text.split(' ')
                        if (datePart && timePart) {
                          const [month, day, year] = datePart.split('/')
                          const [hours, minutes] = timePart
                            .replace('AM', '')
                            .replace('PM', '')
                            .trim()
                            .split(':')

                          if (month && day && year && hours && minutes) {
                            const newDate = new Date(endDate)
                            newDate.setFullYear(parseInt(year), parseInt(month) - 1, parseInt(day))

                            let hrs = parseInt(hours)
                            if (timePart.includes('PM') && hrs < 12) hrs += 12
                            if (timePart.includes('AM') && hrs === 12) hrs = 0

                            newDate.setHours(hrs, parseInt(minutes))
                            setEndDate(newDate)
                          }
                        }
                      } catch {
                        // Silently fail - we'll use the previous valid date if parsing fails
                      }
                    }}
                    placeholder="MM/DD/YYYY HH:MM AM/PM"
                    placeholderTextColor="#aaa"
                  />
                ) : (
                  <View style={styles.inProgressContainer}>
                    <SlowSpinner />
                    <ThemedText style={styles.inProgressText}>In progress</ThemedText>
                  </View>
                )}
              </View>

              <View style={styles.buttonContainer}>
                <TouchableOpacity
                  style={styles.cancelButton}
                  onPress={() => setIsModalVisible(false)}
                  disabled={isSubmitting}
                >
                  <ThemedText style={styles.buttonText}>Cancel</ThemedText>
                </TouchableOpacity>
                <TouchableOpacity
                  style={[styles.button, styles.deleteButton]}
                  onPress={handleDelete}
                  disabled={isSubmitting}
                >
                  <ThemedText style={styles.buttonText}>Delete</ThemedText>
                </TouchableOpacity>
                <TouchableOpacity
                  style={[styles.button, styles.saveButton]}
                  onPress={handleSave}
                  disabled={isSubmitting}
                >
                  <ThemedText style={styles.buttonText}>Save</ThemedText>
                </TouchableOpacity>
              </View>
            </ThemedView>
          </View>
        </View>
      </Modal>
    </ThemedView>
  )
}

const styles = StyleSheet.create({
  container: {
    marginVertical: 4,
  },
  modalContent: {
    padding: 16,
  },
  modalTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 16,
  },
  entryItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    borderBottomWidth: 1,
    borderBottomColor: 'transparent',
    backgroundColor: '#f7f7f7',
    padding: 8,
    marginVertical: 4,
    borderRadius: 6,
    gap: 3,
    alignItems: 'center',
  },
  entryContent: {
    gap: 4,
    alignItems: 'flex-start',
    justifyContent: 'center',
    backgroundColor: 'transparent',
    flex: 1,
  },
  dateText: {
    fontSize: 12,
    color: '#888',
  },
  rightSection: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 4,
  },
  durationText: {
    fontWeight: 'bold',
    fontFamily: 'monospace',
  },
  inProgressContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 6,
    backgroundColor: '#EBF4FF', // Light blue background
    paddingVertical: 1,
    paddingHorizontal: 6,
    borderRadius: 100,
  },
  inProgressText: {
    color: '#2563EB',
    fontWeight: '600',
    fontSize: 12,
  },
  formGroup: {
    marginBottom: 16,
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginBottom: 6,
  },
  input: {
    borderWidth: 1,
    borderRadius: 6,
    padding: 10,
    borderColor: '#ccc',
  },
  dateTimeButton: {
    borderWidth: 1,
    borderRadius: 6,
    padding: 10,
    backgroundColor: '#333',
    justifyContent: 'center',
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    gap: 10,
    marginTop: 20,
  },
  cancelButton: {
    backgroundColor: '#555',
    paddingVertical: 8,
    paddingHorizontal: 16,
    borderRadius: 6,
  },
  webDatePickerContainer: {
    backgroundColor: '#333',
    padding: 10,
    borderRadius: 6,
    marginTop: 8,
    gap: 10,
  },
  webDateInput: {
    borderWidth: 1,
    borderColor: '#444',
    borderRadius: 4,
    padding: 8,
    backgroundColor: '#222',
    color: 'white',
    marginBottom: 10,
  },
  webDatePickerButton: {
    backgroundColor: '#2563EB',
    padding: 8,
    borderRadius: 4,
    alignItems: 'center',
    marginTop: 8,
  },
  webDatePickerButtonText: {
    color: 'white',
    fontWeight: '600',
  },
  button: {
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderRadius: 6,
    alignItems: 'center',
    justifyContent: 'center',
  },
  saveButton: {
    backgroundColor: '#2563EB',
  },
  deleteButton: {
    backgroundColor: '#DC2626',
  },
  buttonText: {
    color: 'white',
    fontWeight: '600',
  },
  modalBackdrop: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalContainer: {
    width: '90%',
    maxWidth: 500,
    borderRadius: 8,
    overflow: 'hidden',
  },
  descriptionContainer: {
    gap: 2,
  },
  creatorText: {
    color: '#777',
    fontSize: 12,
  },
})
```

Now when you use the app within an organization, you'll see the time entries of every user within that organization, including their active ones!

## Conclusion

Multi-tenancy is a powerful feature that allows you to create a single instance of an application that can be used by multiple tenants or organizations. Many platforms use a multi-tenancy model to increase the number of users and revenue. Clerk makes it easy to add multi-tenancy to your app by providing robust [APIs for managing organizations](/docs/organizations/overview) and memberships.

These APIs can be used with Expo to implement multi-tenancy into your [cross-platform React Native app](/docs/quickstarts/expo).

---

# Highlights from the MiduDev/Clerk Hackathon
URL: https://clerk.com/blog/highlights-midudev-clerk-hackathon.md
Date: 2025-08-07
Category: Company
Description: Explore the top 5 projects from the MiduDev/Clerk Hackathon, showcasing creativity, technical skills, and community engagement.

We recently teamed up with [MiduDev](https://www.youtube.com/@midudev), one of the most popular Spanish-speaking tech YouTubers, to host a community hackathon. Over 150 projects were submitted, each showing off how developers are building with Clerk in creative and meaningful ways.

After careful review, five projects stood out and took home the top prizes. This article is a breakdown of how each app was judged, but more importantly an examination of each app that was built based on the choices of each developer which lead to their success.

## How projects were judged

While all submissions were evaluated across a variety of dimensions, the scoring emphasized three main criteria:

- **Creativity** – Was the idea unique, unexpected, or imaginative?
- **Clerk Integration** – Was Clerk used in a meaningful and intentional way?
- **User Experience (UX)** – Was the final product polished, intuitive, and a joy to use?

The top 5 finalists nailed all three. Not only was the idea thought out and executed well, they felt cohesive and genuinely useful. Let’s take a look at what made them stand out.

## 1st Place: Key Leap

> \[Key Leap] represents the deepest and most imaginative **Clerk integration** we saw.
>
> — MiduDev

![Key Leap](./image1.png)

[Key Leap](https://keyleap.vercel.app/) was a clear standout because it reimagined what authentication can be. It used Clerk beyond just the drop-in sign-up and sign-in components but used it as the *core mechanic* of an interactive game.

Players progress by meeting access-level challenges, essentially playing with Clerk’s permission model. The experience is deceptively simple but technically deep, making it a brilliant blend of creativity, Clerk integration, and UX.

## 2nd Place: Finanzz

[Finanzz](https://finanzz.vercel.app/) is a mobile-first finance management web application. It uses AI to simplify various processes such as logging expenses using voice and image recognition, automatic transaction categorization, and even learns over time to help the user make better financial decisions.

The developer used Clerk to quickly create a great login experience for users of Finanzz so they could focus on the core logic of the application.

## 3rd Place: SnippetLab

[SnippetLab](https://snippetlab.app/) impressed with its developer-first mindset. It’s a code snippet manager with both a web UI and a powerful CLI for fetching saved snippets. Various social media features have also been integrated such as liking, sharing, and commenting on snippets.

The developer took advantage of Clerk Elements to design authentication forms that matched the theme of the rest of the app while still leveraging our secure user management platform.

## 4th Place: Atomox

![Atomox](./image2.png)

[Atomox](https://atomox.vercel.app/) is a community-driven platform for creating and sharing visual web UI components. Developers can browse components, interact with them directly in the browser, and view the source code that powers each one. Its visual polish and thoughtful UX are only a few elements that made it stand out.

Behind the scenes, Clerk handles user authentication and profile management, giving the app a solid foundation for growing a creative community.

## 5th Place: SoulsPixel

![SoulsPixel](./image3.png)

[SoulsPixel](https://soulpixel.klasinky.com/soul) is a collaborative pixel board enhanced with gamification. Users place pixels on the board to earn achievements, unlock special features, and earn their place on the leaderboard. On top of that, the history of the board can be replayed to see where each pixel was placed over time.

Clerk powers user identity, allowing users to sign-in using Google or GitHub while requiring unique usernames to power the activity feed within the app.

## Conclusion

This hackathon was definitely a showcase of technical skills, but also a celebration of creativity, craft, and community. Every winning project demonstrates that no matter how technical or creative an application is, Clerk is a great choice to seamlessly integrate user management into any application.

We’re grateful to MiduDev for helping bring this event to life, and even more thankful to everyone who submitted a project!

---

# Add multi-tenancy to an app built with Clerk, Lovable, and Supabase
URL: https://clerk.com/blog/multi-tenancy-clerk-lovable.md
Date: 2025-07-11
Category: Guides
Description: Learn how to transform your single-user app into a team-ready B2B platform using Clerk Organizations with Lovable and Supabase.

Adding multi-tenancy to a B2B is foundational if you're building for teams or organizations. You want separate data contexts, scoped permissions, and a seamless experience switching between personal and org-owned content.

In this article, you'll learn how to use **Lovable**, **Clerk**, and **Supabase** to implement multi-tenancy in a React app without manually handling JWT parsing or user scopes. This guide builds on a previous project and layers in organization support with just a few key updates.

> \[!NOTE]
> Since coding with AI is non-deterministic, your results of the final app may differ from whats shown in this article, although the functionality will be similar.

## Project Recap: Lovable Vibes

In [a recent guide](/blog/build-app-with-lovable-supabase-clerk), we walked through building a full-stack app with Lovable that stores AI coding rules in Supabase. We scaffolded the application without actually touching the code and instead leveraged Lovable's AI chat. Upon logging into the application, users can create new entries to store their vibe coding rules, giving them a name, optionally specifying a project, and tagging them using a number of preconfigured frameworks and languages.

![Lovable Vibes](./image1.png)

> \[!TIP]
> If you want to follow along with this guide, complete the steps in [the previous article](/blog/build-app-with-lovable-supabase-clerk).

## Add Multi-tenancy with Clerk Organizations

Now, we’re going to extend that same project to support multi-tenancy using Clerk organizations. You’ll add the drop-in `<OrganizationSwitcher />` component (shown below) which will allow your users to self-manage the teams they create within the application, including inviting others and managing their permissions.

![The OrganizationSwitcher component](./image2.png)

### Enable Clerk Organizations

Start in the [Clerk Dashboard](https://dashboard.clerk.com). Access your Clerk application, head to **Configure** > **Settings** (under **Organization management**), and toggle on **Organizations**. A new list of settings will appear, allowing you to fine-tune the way that organizations work with your app. The defaults can be left as-is for this guide.

> \[!NOTE]
> Learn about the full capabilities of Clerk Organizations in the [Clerk documentation](/docs/organizations/overview).

### Adding the `<OrganizationSwitcher />` to the project

Open your project in Lovable and run the following prompt to add the `<OrganizationSwitcher />` to the app:

```
Add the `<OrganizationSwitcher />` from Clerk to the nav bar.
```

Once Lovable is finished, your app should update to include a new element in the header that says “Personal account”. Clicking this will open a menu allowing you to create an organization. Go ahead and create an organization, providing it a name and inviting another user (which could be a secondary email address you might have).

![The OrganizationSwitcher component in the header](./image3.png)

### Updating the RLS policies

After the organization is created, the switcher will update to show the name of the active organization, but you might notice that the list of rules in the app hasn’t changed. That's because when the project was initially created, the RLS policies (which ensures that users can only access data they are authorized to) were configured to use the user’s ID from the token.

The policies will need to be changed to use the organization ID if present, and fall back to the user ID if not. This will configure the database such that if a user has an active organization, the records belonging to that organization will be returned.

Run the following prompt in Lovable to create a database function to parse the correct ID from the request and update the policies accordingly:

```
Run the following script in Supabase to create the requesting_owner_id function:

-- Add requesting_owner_id function
create or replace function requesting_owner_id()
returns text as $$
select coalesce(
  (auth.jwt() -> 'o'::text) ->> 'id'::text,
  (auth.jwt() ->> 'sub'::text)
)::text;
$$ language sql stable;

Update all of the RLS policies and change the using statement to check requesting_user_id() = owner_id
```

You’ll also need to make sure that the app uses the correct ID when creating records. Run the following prompt to make those changes:

```
When creating rule sets, if the user has an active organization, use that as the `owner_id`, otherwise use the user ID
```

### Test it out

With an organization active, try adding a rule to ensure it properly saves. Now use the organization switcher to access your “Personal account” (which means the current user has no active organization) and you’ll see any entries you added before implementing multi-tenancy.

If you invited an alternate email address, try signing into the app with that account and the organization switcher will have a bubble on it indicating that you’ve been invited to an organization. Accepting the invitation and switching to that organization will show you the same rule set that was added to your first user account!

## Conclusion

With just a few changes to your Supabase policies and some UI wiring via Clerk, you’ve now turned a single-user app into a multi-tenant experience. Users can switch between personal and team workspaces, and all rule data is securely scoped to the appropriate context.

This setup scales well for small teams and early-stage [B2B products](/b2b-saas) using robust developer platforms designed for this purpose. Clerk handles the complexity of authentication and team management, Supabase enforces data isolation with RLS, and Lovable helps you stitch everything together quickly.

---

# How to build an AI coding rules app with Clerk, Lovable, and Supabase
URL: https://clerk.com/blog/build-app-with-lovable-supabase-clerk.md
Date: 2025-07-03
Category: Guides
Description: Learn how to vibe code a secure app with Clerk, Lovable, and Supabase

Building robust and secure full-stack applications requires a lot of upfront knowledge, especially around frameworks, databases, and authentication.

But with tools like Lovable, [Clerk](/react-authentication), and Supabase, that barrier is much lower. Now, anyone from solo developers to designers can prototype and launch apps faster by combining AI with modern dev tooling.

In this guide, you'll step through the process of creating a secure, full-stack app using Lovable’s AI builder, Clerk for authentication, and Supabase for data storage. By the end, you'll understand how these tools fit together and how to guide AI to generate working software with just a few prompts.

> \[!NOTE]
> Since coding with AI is non-deterministic, your results of the final app may differ from whats shown in this article, although the functionality will be similar. Each section also includes follow up prompts to correct common issues that may surface when building this app.

## What is Lovable?

[Lovable](https://lovable.dev) is an AI-powered "vibe coding" platform that allows developers and non-developers to build full stack applications and websites simply by chatting with an AI agent. It integrates seamlessly with popular frameworks like React and Supabase. We've worked closely with the Lovable team to ensure its AI understands how to implement Clerk properly, making it easier than ever to add authentication and user management to your Lovable-built apps.

### How it Works

Start by entering a basic prompt. For example:

```
Create an app where a user can store their vibe coding rules for different projects.
```

You can include specific fields you'd like to store and instruct the AI to use Clerk for authentication. Lovable will then scaffold the app and provide a real-time preview within the browser. You then would iterate on the generated application by refining prompts until the result aligns with your vision. Once complete, Lovable helps deploy your app to platforms like Vercel.

> \[!WARNING]
> While Lovable can generate fully functional apps without prior dev experience, AI is not foolproof. It may introduce bugs or vulnerabilities. Think of AI as your assistant, not your replacement.

## How can Clerk be used with vibe coding?

Clerk is a user management and authentication platform designed to get authentication added into applications as quickly as possible. When using Clerk, the complexity that comes with properly implemented authentication is wrapped into beautifully designed drop-in UI components that often only require a single line of code.

For example, the sign-in form shown below only takes a single line of code to render in a React app: `<SignIn />`

![The sign-in form provided by Clerk](./sign-in.png)

Because of this, vibe coding platforms like Lovable can easily configure applications with the required components to quickly get user management added to your apps. As mentioned in the previous section, we’ve worked directly with the Lovable team to make it as easy as asking Lovable to use Clerk for authentication:

```
Use Clerk for authentication and use 'modal' mode for signing in.
```

## Building a tool to store AI coding rules

If you’ve ever used AI to assist you with building projects, you probably already know that using preconfigured sets of rules can streamline the process of implementing certain frameworks or platforms. So in this guide, we’ll build a simple application that lets users store those rules for alter use, along with tagging them with specific languages or frameworks the rules are used for.

To follow along, you’ll need the following:

- A Lovable account
- A [Supabase](https://supabase.com) account
- A Clerk account

The free tiers for each of these services should be more than enough to build the example in this guide.

### Bootstrap the application

Start in Lovable and enter this prompt into the chat input on the home page:

```
Create an app where a user can store their vibe coding rules for different projects. The
user should be able to paste in a name for the rule set, the project name, the rules
themselves (as a text block), and select from a number of popular vibe coding tools.
Use Clerk for authentication and use 'modal' mode for signing in.
```

While Lovable starts building, head to [dashboard.clerk.com](https://dashboard.clerk.com/), create a new Clerk application, and configure your sign-in methods. For this tutorial, name the app "Lovable Vibes" and keep the default settings.

![The Clerk dashboard](./clerk-dashboard.png)

Once the app is created, you’ll be redirected to our onboarding screen where you can select from a list of popular frameworks that Clerk supports. Lovable uses React, so select React as the framework and locate your API key. Copy this value and return to Lovable.

![The Clerk onboarding screen](./clerk-onboarding.png)

Next, head back to Lovable. The app likely will not have loaded because the Clerk API key was not specified, so paste the key into the chat and Lovable will add it to the project.

![The Clerk API key](./clerk-api-key.png)

Once complete, your application should load properly in the preview window.  Go ahead and sign in with Google or email and you will likely be redirected to a protected area of the application where you can add your AI rules.

Verify that the `<UserButton />` component appears in the navigation. This is an example of a drop-in component provided by Clerk that allows users to manage their accounts.

![The UserButton component](./user-button.png)

### Correcting potential issues

If you're redirected to a broken page after sign-in, it's likely due to an incompatibility between Clerk's Account Portal and Lovable's preview mode. Use the following prompt to switch to modal mode:

```
Make sure the sign-in button on the home page uses Clerk’s “modal” mode.
```

## Adding Supabase for Storage

Now that authentication is working, it's time to store user data. Supabase is an open-source Backend as a Service (BaaS) built on PostgreSQL that supports advanced features like RLS and integrates well with both Clerk and Lovable.

Visit [supabase.com](http://supabase.com/) and start a new project. Name it "Lovable Vibes."

![The Supabase project create screen](./supabase-project-create.png)

Go to **Authentication** > **Sign-in / Providers** > **Third Party Auth** and select Clerk from the **Add provider** menu.

![The Supabase third party auth screen](./supabase-auth.png)

Click the “**Clerk’s Connect with Supabase page**” link from the modal to open the integration wizard. Select the Clerk app you created in the previous section, enable the integration, and copy the **Clerk domain** for your app.

![The Clerk connect with Supabase page](./clerk-connect-with-supabase.png)

Paste that URL into the Supabase integration settings and click **Create connection**.

![The Supabase integration settings](./supabase-integration-settings.png)

Back in Lovable, connect your project to Supabase by clicking the Supabase icon in the header. Grant access and select your project. Once the integration is finished, Lovable will be able to configure your Supabase project on your behalf.

![The Lovable Supabase integration settings](./supabase-lovable-integration.png)

Before proceeding, you'll need to teach Lovable how to properly create Supabase tables that work with Clerk.

Supabase uses [Row-Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security) to enforce data access rules at the database level.  RLS policies are used to check incoming requests against a set of rules to ensure users can only access data belonging to them. Without the proper configuration, users could inadvertently access records that don't belong to them. Clerk provides the authenticated user's ID as a JWT claim, and Supabase policies can use that claim to restrict access.

> \[!NOTE]
> Learn more about how Clerk integrates with Supabase in [this article on our blog](/blog/how-clerk-integrates-with-supabase-auth).

Instead of providing these instructions every time tables need to be configured, we can use the **Project Knowledge** feature of Lovable to add information that should guide all responses related to those rules (kind of like the rules that will be stored in this app!)

Under **Project Settings > Knowledge**, paste the following:

```
All Supabase tables must use Row-Level Security (RLS) with this policy:
(auth.jwt() ->> 'sub') = owner_id

This ties each row to the authenticated Clerk user.

When creating the Supabase client, pass the Clerk session token as the access token:

import { createClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/clerk-react'

const { session } = useSession()

const client = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_KEY!,
  {
    accessToken: () => session?.getToken(),
  },
)

DO NOT USE THE BEARER HEADER. Use the `accessToken` key of the Supabase client configuration.
Keep RLS enabled and always scope access using Clerk’s sub claim.
```

Then, run the following prompt to create the database tables:

```
Create the tables for the app in Supabase and wire the Supabase client into the project
as defined in the project rules. For any clients make sure to use
the generated Supabase types.
```

Lovable will generate the SQL to execute. It should look similar to the following SQL. Provided it does, go ahead and let Lovable create the tables.

```sql
-- Create a table for rule sets
CREATE TABLE public.rule_sets (
  id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
  owner_id TEXT NOT NULL, -- This will store the Clerk user ID (sub claim)
  name TEXT NOT NULL,
  project_name TEXT NOT NULL,
  rules TEXT NOT NULL,
  tools TEXT[] DEFAULT '{}', -- Array of tool names
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
  updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);

-- Enable Row Level Security
ALTER TABLE public.rule_sets ENABLE ROW LEVEL SECURITY;

-- Create RLS policy that ties each row to the authenticated Clerk user
CREATE POLICY "Users can manage their own rule sets"
  ON public.rule_sets
  FOR ALL
  USING ((auth.jwt() ->> 'sub') = owner_id)
  WITH CHECK ((auth.jwt() ->> 'sub') = owner_id);

-- Create an index for better performance on owner_id queries
CREATE INDEX idx_rule_sets_owner_id ON public.rule_sets(owner_id);
```

Now you can fully test the app by adding some rules to the application!

![The app with some rules](./app-with-rules.png)

### Correcting potential issues

If you run into type mismatches, it's usually because Lovable tried to define its own types instead of using the ones auto-generated by Supabase. In that case, re-prompt with:

```
Make sure to use the generated Supabase types.

Don’t create custom types for connecting to Supabase.
```

If authentication fails, it’s likely the Supabase client isn't configured with the proper access token from Clerk. This token is how Supabase knows which user is making the request. To fix this, re-prompt with:

```
When creating the Supabase client, pass the Clerk session token as the access token:

import { createClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/clerk-react'
const { session } = useSession()
const client = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_KEY!,
  {
    accessToken: () => session?.getToken(),
  },
)

DO NOT USE THE BEARER HEADER. Use the `accessToken` key of the Supabase client configuration.
```

These fixes help ensure your Supabase policies are enforced correctly and that user data stays properly scoped.

## Conclusion

With the help of AI, you no longer need to write every line of code by hand to build a secure, scalable application. Lovable enables you to  generate a full stack application using natural language. By pairing it with Clerk and Supabase, you ensure the app has a strong foundation for authentication and data management.

While AI can handle much of the heavy lifting, human supervision still plays an important part to ensure your app is built properly. You provide the vision, review the output, and help guide the agent in the right direction. When used thoughtfully, this toolchain gives you the speed of low-code builders with the flexibility of custom software.

---

# How to Build Multi-Tenant Authentication with Clerk
URL: https://clerk.com/blog/how-to-build-multitenant-authentication-with-clerk.md
Date: 2025-06-27
Category: Guides
Description: Multi-tenancy is one of those architectural decisions that pays off early and compounds over time. Clerk enables you to build multi-tenant authentication with ease.

If you're building a SaaS product, multi-tenancy shouldn’t be an afterthought. Designing with multiple tenants in mind sets you up to scale effectively and securely.

But building multi-tenant logic from scratch can get complicated fast. You need to think through everything from how users join organizations to how sessions are scoped and how permissions are enforced across contexts.

That’s where [Clerk](/user-authentication) comes in.

Clerk’s built-in support for isolating tenants within a single codebase simplifies the heavy lifting. Instead of wiring up custom logic, you can use Clerk’s [drop-in UI components](/docs/components/overview) to get a multi-tenant foundation in place quickly, while still maintaining flexibility as your product grows.

### What You’ll Learn in This Guide

In this guide, we’ll walk through how to use Clerk to build a complete multi-tenant experience into your app. Specifically, you’ll learn how to:

- Let users create and join organizations
- Invite users to organizations with specific roles
- Scope authentication and session logic by organization
- Use our organization switcher so users can switch between multiple tenants
- Enforce role-based access control (RBAC) per organization
- Configure custom domains with Clerk’s Verified Domains

## Building multi-tenancy features into a task manager

To demo this, you’ll learn how to add multi-tenancy features into a task manager called Kozi. At the moment, Kozi is feature complete for individual users, allowing them to add tasks and organize their tasks into projects.

Once the multi-tenancy features are added using Clerk, users will be able to create team workspaces and switch between them. Each workspace will have four distinct roles:

- Admin - the default role, users in this role can perform all actions and manage the organization
- Member - users in this role can create and manage tasks, but not the organization
- Reader - users in this role can only read existing tasks but not make changes

The app is built with Next.js, Clerk, Neon, and Prisma for the ORM.

### Following along

If you want to follow along, clone the [`orgs-exp-start` branch in the Kozi repo](https://github.com/bmorrisondev/kozi/tree/orgs-exp-start) and follow the instructions in the README to get set up.

> \[!NOTE]
> The project also uses Posthog for user activity tracking and analytics, but it’s not necessary to configure that for this article. You can skip that section of the README when configuring the project.

You’ll need the following before you get started:

- [A Clerk account](https://dashboard.clerk.com)
- [A Neon account](https://console.neon.tech/signup)
- Node and NPM installed locally
- Familiarity with Next.js

## Step 1 - Enable Organizations in your Clerk app

Start by accessing your application in the Clerk dashboard. Select **Configure** from the top nav and then **Settings** under **Organization management**. Toggle the **Enable organizations** option to turn the feature on.

Once on, the view will update to present a number of new options. Here are a few notable ones:

- **Allow new members to delete organizations** - When enabled, organization creators will be granted the `Delete organization` permission, allowing them to delete organizations. Turn this off if you want to allow users to create organizations but not delete them.
- **Enable verified domains** - This option allows you to associate domains with organizations, and is something we will explore later in this article.
- **Allow new members to create organizations** - If you want to control organization creation, disabling this option will prevent members to create organizations themselves.

![Enable organizations](./image1.png)

While we’re in the dashboard, create the roles and permissions by heading to **Roles and Permissions**, then select the **Permissions** tab. You’ll use the **Create new permission** button to create two permissions with the following values:

| Permission  | Key               | Description                                        |
| ----------- | ----------------- | -------------------------------------------------- |
| Read tasks  | `org:tasks:read`  | A user who can read organization tasks.            |
| Write tasks | `org:tasks:write` | A user who can create and edit organization tasks. |

Next head back to the **Roles** tab and select the **Create new role** button to create the following role:

| Name   | Key          | Description                     | Permissions                                  |
| ------ | ------------ | ------------------------------- | -------------------------------------------- |
| Reader | `org:reader` | A user who can only read tasks. | `org:sys_memberships:read`, `org:tasks:read` |

Next select both the **Admin** and **Member** roles and add the `org:tasks:read` and `org:tasks:write` permissions to enable those roles to use the new permissions.

> \[!NOTE]
> [You can learn more about roles and permissions in our docs.](/docs/organizations/roles-permissions#roles)

## Step 2 - Let users create and join orgs

Now that the Clerk application is configured, let’s move over to the code. The first change to make is to add the [`<OrganizationSwitcher />`](/docs/components/organization/organization-switcher) component which is an all-in-one component that lets users create organizations, invite other members, and switch between the ones they have access to.

Open the project locally and update the `Sidebar.tsx` component to add the `<OrganizationSwitcher />` component like so:

```tsx {{ filename: 'src/app/app/components/Sidebar.tsx', ins: [12, [97, 104]], del: [11] }}
'use client'

import { cn } from '@/lib/utils'
import { ChevronRightIcon, ChevronLeftIcon, InboxIcon } from 'lucide-react'
import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import CreateProjectButton from './CreateProjectButton'
import ProjectLink from './ProjectLink'
import { useProjectStore } from '@/lib/store'
import { UserButton, useUser } from '@clerk/nextjs'
import { OrganizationSwitcher, UserButton, useUser } from '@clerk/nextjs'
import { getProjects } from '../actions'

function Sidebar() {
  const [isCollapsed, setIsCollapsed] = React.useState(false)
  const { projects, setProjects } = useProjectStore()
  const { user } = useUser()
  const router = useRouter()

  const ownerId = user?.id
  const canCreateProjects = true

  // Navigate to /app when ownerId changes
  React.useEffect(() => {
    if (ownerId) {
      router.push('/app')
    }
  }, [ownerId, router])

  // Fetch projects when component mounts or organization changes
  React.useEffect(() => {
    getProjects().then(setProjects)
  }, [setProjects, ownerId])

  return (
    <div
      className={cn(
        'h-screen border-r border-gray-200 bg-gradient-to-b from-blue-50 via-purple-50/80 to-blue-50 p-4 dark:border-gray-800 dark:from-blue-950/20 dark:via-purple-950/20 dark:to-blue-950/20',
        'flex flex-col transition-all duration-300 ease-in-out',
        isCollapsed ? 'w-16' : 'w-64',
      )}
    >
      <nav className="flex-grow space-y-2">
        <div className="flex items-center justify-between gap-2">
          <div
            className={cn(
              'transition-all duration-300',
              isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
            )}
          >
            <UserButton showName />
          </div>
          <button
            onClick={() => setIsCollapsed(!isCollapsed)}
            className="flex-shrink-0 rounded-lg p-1 transition-colors hover:bg-white/75 dark:hover:bg-gray-800/50"
          >
            {isCollapsed ? (
              <ChevronRightIcon className="h-4 w-4" />
            ) : (
              <ChevronLeftIcon className="h-4 w-4" />
            )}
          </button>
        </div>

        <div
          className={cn(
            'transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <Link
            href="/app"
            className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-white/75 dark:text-gray-200 dark:hover:bg-gray-800/50"
          >
            <InboxIcon className="h-4 w-4" />
            <span>Inbox</span>
          </Link>
        </div>

        <div
          className={cn(
            'pt-4 transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <div className="flex items-center justify-between px-3 pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400">
            <span>Projects</span>
            {canCreateProjects && <CreateProjectButton />}
          </div>
          {projects.map((project) => (
            <ProjectLink key={project.id} project={project} isCollapsed={isCollapsed} />
          ))}
        </div>
      </nav>

      <div
        className={cn(
          'mt-auto pt-4 transition-all duration-300',
          isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
        )}
      >
        <OrganizationSwitcher />
      </div>
    </div>
  )
}

export default Sidebar
```

Now the bottom of the sidebar shows a “Personal account” button. This is the `<OrganizationSwitcher />` component. By default every user has a personal account (meaning they do not have an organization active on their account), but clicking that button opens a menu allowing users to create new organizations:

![The organization switcher](./image2.png)

## Step 3 - Creating an organization and inviting members

With the `<OrganizationSwitcher />` in place, let’s use it to create a new organization and invite others to it. Open the switcher and select **Create Organization**. This will bring up a modal where you can set the name of your new organization. I’ll create one called Frontiers:

![Create organization](./image3.png)

After clicking **Create organization**, the form will ask for email addresses of anyone that should be invited to the organization, along with the role those users should be assigned:

![Invite members](./image4.png)

Clerk handles the process of sending email invitations to those users. When the user clicks the **Accept Invitation** button in their email, they’ll be brought to the application where they will then be able to access that organization as well in the switcher.

![Accept invitation](./image5.png)

At this point, Kozi permits users to switch their active organization, but the code still needs to be slightly modified to only return data relevant to that organization. As of now, the current user’s ID is used to filter data returned from the queries.

For example, the following code renders the Inbox page. It includes a query that returns tasks not associated to any projects. The [`auth`](/docs/references/nextjs/auth) helper function from Clerk is parsing the `userId` and setting that to `ownerId` which is in turn used in the query:

```tsx {{ filename: 'src/app/app/page.tsx' }}
import React from 'react'
import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import TaskList from './components/TaskList'
import { redirect } from 'next/navigation'

export default async function AppHome() {
  const { userId } = await auth()

  if (!userId) {
    return redirect('/sign-in')
  }

  const ownerId = userId

  // Get the user's inbox tasks
  const tasks = await prisma.task.findMany({
    where: {
      owner_id: ownerId,
      project_id: null,
    },
    orderBy: {
      created_at: 'desc',
    },
  })

  return (
    <div className="flex h-screen">
      <TaskList title="Inbox" tasks={tasks} />
    </div>
  )
}
```

The `auth` function also returns an `orgId` if the user has an organization selected (it will return `null` otherwise) so we can modify the code to check for the `orgId` first and default back to the `userId` if it is not present:

```tsx {{ filename: 'src/app/app/page.tsx', ins: [9, 16], del: [8, 15] }}
import React from 'react'
import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import TaskList from './components/TaskList'
import { redirect } from 'next/navigation'

export default async function AppHome() {
  const { userId } = await auth()
  const { userId, orgId } = await auth()

  if (!userId) {
    return redirect('/sign-in')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  // Get the user's inbox tasks
  const tasks = await prisma.task.findMany({
    where: {
      owner_id: ownerId,
      project_id: null,
    },
    orderBy: {
      created_at: 'desc',
    },
  })

  return (
    <div className="flex h-screen">
      <TaskList title="Inbox" tasks={tasks} />
    </div>
  )
}
```

The same change can be made to the page that renders each project:

```tsx {{ filename: 'src/app/app/projects/[id]/page.tsx', ins: [15, 23], del: [14, 22] }}
import React from 'react'
import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import { notFound, redirect } from 'next/navigation'
import TaskList from '../../components/TaskList'

interface ProjectPageProps {
  params: Promise<{
    _id: string
  }>
}

export default async function Project({ params }: ProjectPageProps) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()

  // If the user is not logged in, redirect to the sign-in page
  if (!userId) {
    return redirect('/sign-in')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  const { _id } = await params
  const project = await prisma.project.findUnique({
    where: {
      id: _id,
      owner_id: ownerId,
    },
  })

  // Check if the project exists and belongs to the user
  if (!project || project.owner_id !== ownerId) {
    notFound()
  }

  // Get the project tasks
  const tasks = await prisma.task.findMany({
    where: {
      project_id: _id,
      owner_id: ownerId,
    },
    orderBy: {
      created_at: 'desc',
    },
  })

  return (
    <div className="flex h-screen">
      <TaskList title={project.name} tasks={tasks} projectId={project.id} />
    </div>
  )
}
```

And finally, all of the other queries stored in `actions.ts`:

```ts {{ filename: 'src/app/app/actions.ts', ins: [9, 21, 37, 43, 63, 69, 100, 106, 146, 152, 181, 187, 210, 216], del: [8, 20, 36, 42, 62, 68, 99, 105, 145, 151, 180, 186, 209, 215] }}
'use server'

import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createTask(formData: FormData) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()

  if (!userId) {
    throw new Error('Unauthorized')
  }

  const title = formData.get('title') as string
  if (!title?.trim()) {
    throw new Error('Title is required')
  }

  const ownerId = userId
  const ownerId = orgId || userId
  const project_id = formData.get('project_id') as string | null

  await prisma.task.create({
    data: {
      title: title.trim(),
      owner_id: ownerId,
      project_id: project_id || null,
    },
  })

  revalidatePath('/app')
}

export async function toggleTask(taskId: string) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  const task = await prisma.task.findUnique({
    where: { id: taskId },
  })

  if (!task || task.owner_id !== ownerId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id: taskId },
    data: { is_completed: !task.is_completed },
  })

  revalidatePath('/app')
}

export async function updateTask(formData: FormData) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  const id = formData.get('id') as string
  const title = formData.get('title') as string
  const description = formData.get('description') as string

  if (!id || !title?.trim()) {
    throw new Error('Invalid input')
  }

  const task = await prisma.task.findUnique({
    where: { id },
  })

  if (!task || task.owner_id !== ownerId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id },
    data: {
      title: title.trim(),
      description: description?.trim() || null,
    },
  })

  revalidatePath('/app')
}

export async function createProject(formData: FormData) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  const name = formData.get('name') as string
  if (!name?.trim()) {
    throw new Error('Project name is required')
  }

  const project = await prisma.project.create({
    data: {
      name: name.trim(),
      owner_id: ownerId,
    },
  })

  revalidatePath('/app')
  return project
}

export async function getProjects() {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  return prisma.project.findMany({
    where: {
      owner_id: ownerId,
    },
    orderBy: {
      created_at: 'asc',
    },
  })
}

export async function updateProject(formData: FormData) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  const id = formData.get('id') as string
  const name = formData.get('name') as string

  if (!id || !name?.trim()) {
    throw new Error('Invalid input')
  }

  const project = await prisma.project.findUnique({
    where: { id },
  })

  if (!project || project.owner_id !== ownerId) {
    throw new Error('Project not found or unauthorized')
  }

  await prisma.project.update({
    where: { id },
    data: {
      name: name.trim(),
    },
  })

  revalidatePath('/app')
}

export async function deleteProject(projectId: string) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  // Delete all tasks associated with the project first
  await prisma.task.deleteMany({
    where: {
      project_id: projectId,
      owner_id: ownerId,
    },
  })

  // Then delete the project
  await prisma.project.delete({
    where: {
      id: projectId,
      owner_id: ownerId,
    },
  })

  revalidatePath('/app')
}

export async function deleteTask(taskId: string) {
  const { userId } = await auth()
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = userId
  const ownerId = orgId || userId

  // Delete the task
  await prisma.task.delete({
    where: {
      id: taskId,
      owner_id: ownerId,
    },
  })

  revalidatePath('/app')
}
```

The `Sidebar.tsx` renders a list of projects associated with the current user or organization, and while the `getProjects` function in the `actions.ts` file will fetch the correct list of projects, the Sidebar is a client component and we need a way for it to react to changes. Instead of using the `auth` helper function (which is a server-side function), we can use the `useOrganization()` helper to get details about the user’s active organization and react to changes with a `useEffect`.

Make the following changes to `Sidebar.tsx` to update it’s behavior:

```tsx {{ filename: 'src/app/app/components/Sidebar.tsx', ins: [12, 19, 22], del: [11, 23] }}
'use client'

import { cn } from '@/lib/utils'
import { ChevronRightIcon, ChevronLeftIcon, InboxIcon } from 'lucide-react'
import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import CreateProjectButton from './CreateProjectButton'
import ProjectLink from './ProjectLink'
import { useProjectStore } from '@/lib/store'
import { OrganizationSwitcher, UserButton, useUser } from '@clerk/nextjs'
import { OrganizationSwitcher, UserButton, useUser, useOrganization } from '@clerk/nextjs'
import { getProjects } from '../actions'

function Sidebar() {
  const [isCollapsed, setIsCollapsed] = React.useState(false)
  const { projects, setProjects } = useProjectStore()
  const { user } = useUser()
  const { organization } = useOrganization()
  const router = useRouter()

  const ownerId = organization?.id || user?.id
  const ownerId = user?.id
  const canCreateProjects = true

  // Navigate to /app when ownerId changes
  React.useEffect(() => {
    if (ownerId) {
      router.push('/app')
    }
  }, [ownerId, router])

  // Fetch projects when component mounts or organization changes
  React.useEffect(() => {
    getProjects().then(setProjects)
  }, [setProjects, ownerId])

  return (
    <div
      className={cn(
        'h-screen border-r border-gray-200 bg-gradient-to-b from-blue-50 via-purple-50/80 to-blue-50 p-4 dark:border-gray-800 dark:from-blue-950/20 dark:via-purple-950/20 dark:to-blue-950/20',
        'flex flex-col transition-all duration-300 ease-in-out',
        isCollapsed ? 'w-16' : 'w-64',
      )}
    >
      <nav className="flex-grow space-y-2">
        <div className="flex items-center justify-between gap-2">
          <div
            className={cn(
              'transition-all duration-300',
              isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
            )}
          >
            <UserButton showName />
          </div>
          <button
            onClick={() => setIsCollapsed(!isCollapsed)}
            className="flex-shrink-0 rounded-lg p-1 transition-colors hover:bg-white/75 dark:hover:bg-gray-800/50"
          >
            {isCollapsed ? (
              <ChevronRightIcon className="h-4 w-4" />
            ) : (
              <ChevronLeftIcon className="h-4 w-4" />
            )}
          </button>
        </div>

        <div
          className={cn(
            'transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <Link
            href="/app"
            className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-white/75 dark:text-gray-200 dark:hover:bg-gray-800/50"
          >
            <InboxIcon className="h-4 w-4" />
            <span>Inbox</span>
          </Link>
        </div>

        <div
          className={cn(
            'pt-4 transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <div className="flex items-center justify-between px-3 pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400">
            <span>Projects</span>
            {canCreateProjects && <CreateProjectButton />}
          </div>
          {projects.map((project) => (
            <ProjectLink key={project.id} project={project} isCollapsed={isCollapsed} />
          ))}
        </div>
      </nav>

      <div
        className={cn(
          'mt-auto pt-4 transition-all duration-300',
          isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
        )}
      >
        <OrganizationSwitcher />
      </div>
    </div>
  )
}

export default Sidebar
```

With this change, the list of projects in the sidebar will react to changing between organizations.

## Step 4 - Gate access using RBAC

Now that users can switch between organizations, you’ll need to handle the different roles assigned to each user. To make this as simple as possible, Clerk offers the `has` helper function which takes a role or permission as a parameter to check to see if the user in the current context has the proper role/permission.

Recall that we created the `Reader` role which should have read-only access to tasks. This role omits the `orgs:tasks:write` permission so we can conditionally render the components differently if the user’s role does not have this permission.

To prevent the user from creating tasks, edit `CreateTaskInput.tsx` as follows:

```tsx {{ filename: 'src/app/app/components/CreateTaskInput.tsx', ins: [9, 19, 20], del: [18] }}
'use client'

import { useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { createTask } from '@/app/app/actions'
import { PlusIcon, LockIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
import { useAuth } from '@clerk/nextjs'

interface Props {
  projectId?: string
}

export default function CreateTaskInput({ projectId }: Props) {
  const [title, setTitle] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const canCreate = true
  const { has } = useAuth()
  const canCreate = has?.({ permission: 'org:tasks:create' })

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    // Don't create a task if the title is empty or user can't create
    if (!title.trim() || !canCreate) return

    try {
      setIsSubmitting(true)
      const formData = new FormData()
      formData.append('title', title)
      if (projectId) {
        formData.append('project_id', projectId)
      }
      await createTask(formData)
      setTitle('')
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <div
      className={cn(
        'group relative w-full rounded-full bg-white p-2 dark:bg-gray-800',
        canCreate
          ? 'focus-within:shadow-[0_4px_20px_-2px_rgba(96,165,250,0.3),0_4px_20px_-2px_rgba(192,132,252,0.3)]'
          : 'opacity-70',
        'transition-shadow duration-200',
      )}
    >
      <div
        className={cn(
          'absolute inset-0 rounded-full bg-gradient-to-r',
          canCreate
            ? 'from-blue-400/25 to-purple-400/25 transition-opacity duration-200 group-focus-within:from-blue-400 group-focus-within:to-purple-400'
            : 'from-gray-300/25 to-gray-400/25',
        )}
      ></div>
      <div className="absolute inset-[1px] rounded-full bg-white transition-all group-focus-within:inset-[2px] dark:bg-gray-800"></div>
      <div className="relative">
        <form onSubmit={handleSubmit} className="flex w-full items-center gap-2">
          {!canCreate && (
            <div className="ml-3 text-gray-400">
              <LockIcon className="h-4 w-4" />
            </div>
          )}
          <Input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder={canCreate ? 'Add a task...' : "You don't have permission to create tasks"}
            disabled={!canCreate}
            className="flex-1 border-0 bg-transparent text-gray-900 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-gray-100"
          />
          <Button
            type="submit"
            size="icon"
            disabled={isSubmitting || !title.trim() || !canCreate}
            className="flex !h-[30px] !min-h-0 !w-[30px] items-center justify-center !rounded-full !p-0 !leading-none"
          >
            <PlusIcon className="h-4 w-4" />
          </Button>
        </form>
      </div>
    </div>
  )
}
```

To make sure the user can’t edit tasks (including completing/uncompleting them), edit the `TaskCard.tsx` file:

```tsx {{ filename: 'src/app/app/components/TaskCard.tsx', ins: [8, 17, 18], del: [19] }}
'use client'

import React from 'react'
import { toggleTask } from '../actions'
import EditTaskModal from './EditTaskModal'
import { cn } from '@/lib/utils'
import { Task } from '@prisma/client'
import { useAuth } from '@clerk/nextjs'

interface Props {
  task: Task
  projectName?: string
}

export default function TaskCard({ task, projectName }: Props) {
  const [isModalOpen, setIsModalOpen] = React.useState(false)
  const { has } = useAuth()
  const canEdit = has?.({ permission: 'org:tasks:edit' })
  const canEdit = true

  const handleClick = (e: React.MouseEvent) => {
    const target = e.target as HTMLElement
    // Don't open modal if clicking the checkbox
    if (!target.closest('button')) {
      setIsModalOpen(true)
    }
  }

  return (
    <>
      <div
        onClick={handleClick}
        className={cn(
          'cursor-pointer rounded-lg border border-transparent p-2 transition-colors duration-200 hover:border-gray-100 dark:border-gray-800 dark:hover:bg-gray-800/50',
          task.is_completed && 'opacity-50',
        )}
      >
        <div className="flex items-start justify-between">
          <div className="flex items-start gap-3">
            {/* Checkbox */}
            <button
              onClick={(e) => {
                if (!canEdit) {
                  return
                }
                e.stopPropagation()
                toggleTask(task.id)
              }}
              className={cn(
                'mt-1 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border',
                canEdit
                  ? 'cursor-pointer border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500'
                  : 'cursor-not-allowed border-gray-200 opacity-60 dark:border-gray-700',
              )}
            >
              {task.is_completed && (
                <svg
                  className="h-3 w-3 text-gray-500 dark:text-gray-400"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke="currentColor"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth={2}
                    d="M5 13l4 4L19 7"
                  />
                </svg>
              )}
            </button>
            {/* Task details */}
            <div>
              <h3
                className={cn(
                  'font-medium',
                  task.is_completed && 'text-gray-400 line-through dark:text-gray-500',
                )}
              >
                {task.title}
              </h3>

              {task.description && (
                <p
                  className={cn(
                    'mt-1 text-sm text-gray-500 dark:text-gray-400',
                    task.is_completed && 'line-through opacity-75',
                  )}
                >
                  {task.description}
                </p>
              )}
            </div>
          </div>
        </div>
      </div>

      <EditTaskModal
        task={task}
        open={isModalOpen}
        onOpenChange={setIsModalOpen}
        projectName={projectName}
        canEdit={canEdit}
      />
    </>
  )
}
```

The Sidebar should also be edited to prevent `Reader` users from seeing the option to add a project:

```tsx {{ filename: 'src/app/app/components/Sidebar.tsx', ins: [13, 21, 24], del: [25] }}
'use client'

import { cn } from '@/lib/utils'
import { ChevronRightIcon, ChevronLeftIcon, InboxIcon } from 'lucide-react'
import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import CreateProjectButton from './CreateProjectButton'
import ProjectLink from './ProjectLink'
import { useProjectStore } from '@/lib/store'
import { OrganizationSwitcher, UserButton, useUser, useOrganization } from '@clerk/nextjs'
import { getProjects } from '../actions'
import { useAuth } from '@clerk/nextjs'

function Sidebar() {
  const [isCollapsed, setIsCollapsed] = React.useState(false)
  const { projects, setProjects } = useProjectStore()
  const { user } = useUser()
  const { organization } = useOrganization()
  const router = useRouter()
  const { has } = useAuth()

  const ownerId = organization?.id || user?.id
  const canCreateProjects = has?.({ permission: 'org:projects:create' })
  const canCreateProjects = true

  // Navigate to /app when ownerId changes
  React.useEffect(() => {
    if (ownerId) {
      router.push('/app')
    }
  }, [ownerId, router])

  // Fetch projects when component mounts or organization changes
  React.useEffect(() => {
    getProjects().then(setProjects)
  }, [setProjects, ownerId])

  return (
    <div
      className={cn(
        'h-screen border-r border-gray-200 bg-gradient-to-b from-blue-50 via-purple-50/80 to-blue-50 p-4 dark:border-gray-800 dark:from-blue-950/20 dark:via-purple-950/20 dark:to-blue-950/20',
        'flex flex-col transition-all duration-300 ease-in-out',
        isCollapsed ? 'w-16' : 'w-64',
      )}
    >
      <nav className="flex-grow space-y-2">
        <div className="flex items-center justify-between gap-2">
          <div
            className={cn(
              'transition-all duration-300',
              isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
            )}
          >
            <UserButton showName />
          </div>
          <button
            onClick={() => setIsCollapsed(!isCollapsed)}
            className="flex-shrink-0 rounded-lg p-1 transition-colors hover:bg-white/75 dark:hover:bg-gray-800/50"
          >
            {isCollapsed ? (
              <ChevronRightIcon className="h-4 w-4" />
            ) : (
              <ChevronLeftIcon className="h-4 w-4" />
            )}
          </button>
        </div>

        <div
          className={cn(
            'transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <Link
            href="/app"
            className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-white/75 dark:text-gray-200 dark:hover:bg-gray-800/50"
          >
            <InboxIcon className="h-4 w-4" />
            <span>Inbox</span>
          </Link>
        </div>

        <div
          className={cn(
            'pt-4 transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <div className="flex items-center justify-between px-3 pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400">
            <span>Projects</span>
            {canCreateProjects && <CreateProjectButton />}
          </div>
          {projects.map((project) => (
            <ProjectLink key={project.id} project={project} isCollapsed={isCollapsed} />
          ))}
        </div>
      </nav>

      <div
        className={cn(
          'mt-auto pt-4 transition-all duration-300',
          isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
        )}
      >
        <OrganizationSwitcher />
      </div>
    </div>
  )
}

export default Sidebar
```

Now that the frontend is updated, you also need to modify any backend code to also check the user’s permissions for maximum security. Fortunately the `has` function works the same in server-side operations just like it does on the front end.

For any server action that makes changes to the database, you’ll use `has` to check the permissions like so:

```tsx {{ filename: 'src/app/app/actions.ts', ins: [8, 9, 33, 34, 57, 58, 92, 93, 134, 135, 167, 168, 194, 195] }}
'use server'

import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createTask(formData: FormData) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const title = formData.get('title') as string
  if (!title?.trim()) {
    throw new Error('Title is required')
  }

  const ownerId = orgId || userId
  const project_id = formData.get('project_id') as string | null

  await prisma.task.create({
    data: {
      title: title.trim(),
      owner_id: ownerId,
      project_id: project_id || null,
    },
  })

  revalidatePath('/app')
}

export async function toggleTask(taskId: string) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  const task = await prisma.task.findUnique({
    where: { id: taskId },
  })

  if (!task || task.owner_id !== ownerId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id: taskId },
    data: { is_completed: !task.is_completed },
  })

  revalidatePath('/app')
}

export async function updateTask(formData: FormData) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  const id = formData.get('id') as string
  const title = formData.get('title') as string
  const description = formData.get('description') as string

  if (!id || !title?.trim()) {
    throw new Error('Invalid input')
  }

  const task = await prisma.task.findUnique({
    where: { id },
  })

  if (!task || task.owner_id !== ownerId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id },
    data: {
      title: title.trim(),
      description: description?.trim() || null,
    },
  })

  revalidatePath('/app')
}

export async function createProject(formData: FormData) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  const name = formData.get('name') as string
  if (!name?.trim()) {
    throw new Error('Project name is required')
  }

  const project = await prisma.project.create({
    data: {
      name: name.trim(),
      owner_id: ownerId,
    },
  })

  revalidatePath('/app')
  return project
}

export async function getProjects() {
  const { userId, orgId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  return prisma.project.findMany({
    where: {
      owner_id: ownerId,
    },
    orderBy: {
      created_at: 'asc',
    },
  })
}

export async function updateProject(formData: FormData) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  const id = formData.get('id') as string
  const name = formData.get('name') as string

  if (!id || !name?.trim()) {
    throw new Error('Invalid input')
  }

  const project = await prisma.project.findUnique({
    where: { id },
  })

  if (!project || project.owner_id !== ownerId) {
    throw new Error('Project not found or unauthorized')
  }

  await prisma.project.update({
    where: { id },
    data: {
      name: name.trim(),
    },
  })

  revalidatePath('/app')
}

export async function deleteProject(projectId: string) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  // Delete all tasks associated with the project first
  await prisma.task.deleteMany({
    where: {
      project_id: projectId,
      owner_id: ownerId,
    },
  })

  // Then delete the project
  await prisma.project.delete({
    where: {
      id: projectId,
      owner_id: ownerId,
    },
  })

  revalidatePath('/app')
}

export async function deleteTask(taskId: string) {
  const { userId, orgId, has } = await auth()
  if (!userId || (orgId && !has?.({ permission: 'org:tasks:write' }))) {
    throw new Error('Unauthorized')
  }

  const ownerId = orgId || userId

  // Delete the task
  await prisma.task.delete({
    where: {
      id: taskId,
      owner_id: ownerId,
    },
  })

  revalidatePath('/app')
}
```

Users with the `Reader` role in a specific organization should now only be able to see tasks in that organization, but not edit them. Furthermore if they switch to another organization where they have the `Member` role, they can add and edit tasks.

## Optional - Add custom domains per org

Using Verified Domains, you can also configure your application to automatically invite users to a specific organization if they sign in with an email address that matches a specific domain. This is extremely helpful for HR or IT teams who need to onboard users into an application. It avoids having them manually create a user record.

As an example, let’s say I want to automatically invite any user who signs in with an `@clerk.dev` domain name into the Frontiers organization. I’ll start by toggling **Enable verified domains** in the organizations settings page shown earlier. I’ll also enable **Automatic invitation** and **Automatic suggestion** to streamline the experience.

![Enable verified domains](./image6.png)

In Kozi, I’ll access the organization settings by opening the `<OrganizationSwitcher />` and selecting Manage. In the modal, I can add a domain to the Verified domains list. I’ll be asked to verify ownership of the domain by entering my email address to receive a six digit code.

![Add verified domain](./image7.png)

Now whenever somebody joins the app with an `@clerk.dev` email address, the `<OrganizationSwitcher />` will automatically list the Frontiers organization as available for me to join and access.

## Conclusion

Multi-tenancy is one of those architectural decisions that pays off early and compounds over time. Whether you're supporting teams, companies, or entire departments, having the right structure in place makes it easier to manage access, enforce security, and scale your product with confidence.

With Clerk, adding multi-tenant support doesn’t require reinventing your auth stack. You get drop-in UI components, APIs, and sensible defaults that help you stay focused on your product, not boilerplate.

As your app grows, so will the complexity of your users and their organizations. This guide gives you a solid foundation to handle that complexity with clarity.

---

# Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant
URL: https://clerk.com/blog/multi-tenant-vs-single-tenant.md
Date: 2025-06-27
Category: Company
Description: What's the difference between multi-tenant and single-tenant SaaS architecture? This guide breaks down the pros, cons, and use cases of each model—so you can choose the right B2B SaaS architecture for your app. Learn how multi-tenancy scales efficiently, when single-tenancy is the better fit, and which modern tools make tenant isolation easier than ever.

Building a B2B SaaS application comes with many early decisions. One of the most critical is choosing your SaaS architecture model - namely, whether to use a multi-tenant or single-tenant approach. This choice impacts everything from cost and scalability to security and maintenance. In this article, we'll clarify:

- What is B2B SaaS
- What is multi-tenancy
- How multi-tenant systems work versus single-tenant setups
- The pros and cons of multi-tenant and single-tenant systems
- How to choose the right model for your product's needs

We'll also explore hybrid strategies and introduce modern tools that can help implement tenant isolation and management. The goal is to help developers, early-stage startups, and founders understand the architectural tradeoffs, so you can make an informed decision for your SaaS.

## What is multi-tenant SaaS architecture?

In multi-tenant architecture, a single instance of your application and its underlying infrastructure is shared by multiple customer organizations (tenants). Each tenant's data and accounts are logically isolated, so that customers only see their own data - but they are all running on the same application and database in a shared environment. For example, think of tenants like residents in an apartment building: everyone shares the same structure and utilities, but each apartment is separate and secured from others. This shared model means tenants *share resources like servers, databases, and application instances*, which greatly improves cost efficiency and scalability. New customers can be onboarded quickly since they use the same base infrastructure. Maintenance is easier on the provider's side and development updates ship faster, because there's just one system to update for all customers.

However, multi-tenancy also requires a strong design for tenant isolation. Since data from different customers lives side-by-side in one system, the application must ensure that one tenant cannot access another tenant's data. Techniques like including a tenant ID column on every table, applying Row-Level Security (RLS) policies in the database, or segregating tenants at the schema or database level are commonly used to enforce this isolation. When implemented properly, each tenant's records remain invisible to any other tenant. The trade-off is that individual tenants have less control over the environment - they can't demand unique customizations that affect only their instance without impacting others. Multi-tenant SaaS providers often solve this by offering configuration options that are universally supported rather than one-off custom code per client.

**Key characteristics of multi-tenancy:**

- **Shared application & DB:** Multiple customers use the same application instance and database with logical partitioning for each tenant. The shared resources also mean a noisy neighbor (one tenant over-consuming resources) could impact others if not managed
- **Cost efficiency:** Infrastructure and maintenance costs are amortized across tenants, making it cheaper per customer
- **Scalability:** The vendor can scale the single application to accommodate more tenants easily - adding more computing resources benefits all customers, without spinning up new instances for each
- **Maintenance:** Updates and bug fixes are applied centrally. When the software is updated, all tenants get the new version at once, simplifying maintenance at scale
- **Customization limits:** Tenants generally run on a uniform codebase. Deep customization for one client is limited, since changes affect everyone. Some SaaS allow per-tenant configuration or feature flags, but not separate forks of the code
- **Security considerations:** Extra care is needed to isolate data and requests. A bug in access control could potentially expose data across tenants, so robust authorization checks are critical.

In summary, multi-tenancy is like a high-rise with many apartments: efficient and cost-effective, but with shared infrastructure. It's widely used in modern B2B SaaS platforms because it supports serving many customers on a common, scalable platform.

## What is single-tenant SaaS architecture?

In single-tenant architecture, each customer (tenant) gets their own dedicated instance of the application and database. There is no sharing of resources between tenants - each runs in an isolated environment. This is akin to each customer having their own house, rather than an apartment in a shared building. Because of this isolation, single-tenancy provides a high degree of control and security: a tenant's data and performance are not affected by any other customer, and there's minimal risk of data accidentally leaking between tenants by design. Customers often prefer this model in scenarios where data must be kept completely separate for compliance or when they require extensive customizations.

With a single-tenant SaaS, onboarding a new customer means provisioning a new deployment of your software (potentially a new server, database, and application instance). Each tenant might even get their own subdomain or dedicated environment. Many on-premises enterprise software offerings or managed cloud offerings use single-tenancy, essentially giving each client their own managed copy of the software.

**Key characteristics of single-tenancy:**

- **Dedicated resources:** Each customer has their own application instance, database and sometimes even separate hardware. No other tenant's data resides in that database.
- **Strong isolation:** Because of the physical and logical separation, there is very strong data isolation and security by default. There's no chance of one tenant accidentally querying another tenant's data because it's simply not in the same system. This isolation also eliminates “noisy neighbor” issues - one tenant's heavy usage can't degrade another's performance because their environments are separate.
- **Customization:** With a dedicated environment, clients can often be granted more customization. The provider (or the client) can tweak configurations, custom plugins, or even custom code for that one tenant without affecting others. This is useful for enterprise clients with unique needs.
- **Higher cost:** The downside is cost and overhead. Serving each customer on their own stack means more infrastructure and maintenance per customer, often leading to higher costs that may only be justifiable for high-paying clients. Higher costs can be offset by leveraging in-house developers to manage maintenance and updates. There are fewer economies of scale; 10 customers could mean maintaining 10 separate sets of resources.
- **Maintenance burden:** Updates and fixes need to be applied to each tenant's instance individually or via an automated process that iterates through them. This can make rolling out new features or patches slower and more labor-intensive, since compatibility has to be ensured for each isolated instance. If one tenant decides to stay on an older version, you may end up maintaining multiple versions of the app.
- **Provisioning complexity:** Setting up a new tenant is more complex and time-consuming, as it involves provisioning new servers or containers, running separate installations, etc. Automation can help, but it's inherently more work than simply adding a record in a multi-tenant database.

In summary, single-tenancy gives each customer their own silo - offering maximum control, security, and potential for customization, at the cost of extra overhead. It's often favored by large enterprises or regulated industries that are willing to pay for isolation.

## Multi-tenant vs. single-tenant: Key differences and tradeoffs

The fundamental difference between these models is shared vs. isolated resources. Multi-tenancy is a shared environment (tenants coexist on the same system) whereas single-tenancy is isolated and each tenant stands alone. This leads to a number of tradeoffs in cost, security, flexibility, and more. As mentioned in previous sections, a common analogy is apartments vs. houses: in a multi-tenant “apartment” setup, tenants share utilities and infrastructure; in a single-tenant “house”, each tenant has everything to themselves.

![Multi-tenant apartments vs. single-tenant houses](./apartments-vs-house.png)

Let's break down the key differences between multi-tenant and single-tenant SaaS architectures across several important factors:

| **Aspect**                | **Single-Tenant (One Customer per Instance)**                                                                                                                                                                        | **Multi-Tenant (Many Customers per Instance)**                                                                                                                                                      |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Infrastructure Cost**   | High - dedicated resources for each customer (cost not shared)                                                                                                                                                       | Low - shared infrastructure amortizes cost across tenants                                                                                                                                           |
| **Security & Isolation**  | Very high - data fully isolated in separate systems. Minimal risk of cross-tenant access.                                                                                                                            | High (with proper design) - data is logically isolated, but sharing infrastructure means stricter controls are needed. No other tenant can see your data, but all rely on common security measures. |
| **Customization**         | Easy - each tenant's environment can be customized (even code-level changes) without affecting others.                                                                                                               | Harder - changes affect all tenants, so customization must be via configuration or not at all. Requires smart layering (feature flags, theming, etc.) for per-tenant tweaks.                        |
| **Scalability**           | Difficult - scaling to more customers means spinning up more instances; operational overhead grows linearly with tenants.                                                                                            | Easier - a shared system scales more efficiently; resources are added to one pool that benefits all tenants. Easier to onboard new customers on the same platform quickly.                          |
| **Updates & Maintenance** | Per customer - updates need to be applied to each instance separately. Version drift is possible if some tenants delay updates.                                                                                      | Global - updates deploy to the shared application, so all tenants get the changes at once. Maintenance is centralized (one environment to manage).                                                  |
| **Typical Use Cases**     | Best for heavily regulated or extremely security-conscious apps, and high-paying enterprise clients who need isolation or custom deployments. Often seen in healthcare, finance, or on-premise enterprise offerings. | Best for most modern SaaS targeting a broad market. Ideal when serving many customers with a uniform service (e.g. SMBs, mid-market) where cost efficiency and ease of scaling are crucial.         |

As the comparison shows, single-tenant architecture shines in security, isolation, and flexibility for customization - at the cost of higher infrastructure expenses and maintenance work per client. Multi-tenant architecture excels in efficient resource utilization, scalability, and easier management, but requires careful attention to security and can limit per-tenant customization.

Let's highlight a few of these tradeoffs:

- **Cost Efficiency:** Multi-tenancy is generally far more cost-efficient for the provider (and often the customer) because resources are shared. You're not running 100 separate servers for 100 customers; you might be running a few servers that handle all 100. This lowers hosting costs significantly. Those savings often translate into more affordable pricing for customers as well. Single-tenancy, by contrast, means each customer needs an allocation of compute, memory, storage, etc. that isn't shared, which is why single-tenant or dedicated-hosting plans tend to come at a premium price.
- **Security & Compliance:** While both models can be made secure, single-tenant offers peace of mind through physical isolation, meaning no other users on your database or server. This can simplify compliance with stringent regulations (GDPR, HIPAA, etc.), since data residency and access can be controlled per-customer environment. Multi-tenant systems must enforce strict *logical* isolation to achieve the same effect. That means robust authentication, authorization, and possibly encryption of tenant data. It's entirely possible to make a multi-tenant app highly secure, but it requires careful engineering. The shared nature of multi-tenancy also means trust in the vendor's security measures is essential. A minor misconfiguration could, in worst cases, expose data across tenants, so the margin for error is smaller. For highly regulated industries or customers that demand complete data isolation, a single-tenant (or a hybrid with dedicated databases) might be non-negotiable.
- **Performance & “Noisy Neighbors”:** In a multi-tenant environment, tenants share resources, so one customer's behavior can potentially affect others. For example, if Tenant A suddenly executes a very expensive operation or experiences a traffic spike, it might consume disproportionate CPU/DB resources and slow down Tenant B's experience, the classic “noisy neighbor” problem. Good multi-tenant design mitigates this (through rate limiting, auto-scaling, query optimization, etc.), but it's an inherent risk. In single-tenancy, there are no noisy neighbors. Each tenant has their own dedicated slice of resources, so one tenant's workload can't throttle another. On the other hand,, in single-tenancy, if a customer's instance is idle, those resources sit unused (wasted capacity), whereas multi-tenant pooling would dynamically make that capacity available to others.
- **Customization & Flexibility:** Single-tenant systems allow tailoring the software environment per client. For example, one client could be on a custom version with specific modules enabled, unique branding, or even custom features developed just for them. This is often important for enterprise contracts. Multi-tenant apps usually avoid one-off custom code per tenant. There's one codebase serving all, so customization is typically limited to configuration options or cosmetic branding. Major deviations requested by one customer either have to be built in a configurable way for all customers or politely declined. This keeps the multi-tenant codebase simpler and the upgrade path easier (since you don't have to maintain divergent code), but it can be a deal-breaker for clients who want a highly tailored solution. In early-stage SaaS, supporting heavy customization for individual clients can also dramatically increase complexity, so many startups choose multi-tenancy and enforce uniformity (sometimes at the cost of losing a few big-customization-seeking deals).
- **Maintenance & Deployment:** A multi-tenant SaaS means one deployment to rule them all, simplifying DevOps. You deploy one set of code, run one database (perhaps scaled out with replicas or partitions, but logically one cluster), and monitor one system. Backup, monitoring, and updates are centralized. With single-tenancy, you have to manage potentially dozens or hundreds of deployments. Automation tools (infrastructure as code, container orchestration, etc.) can help manage many instances, but it's inherently more complex. If you discover a critical bug, you might need to roll out the patch to every customer instance. If you update the base software, you have to ensure all those separate environments are updated (and hope none have unique modifications that make the update tricky). Maintaining thousands of separate databases or instances can become a significant operational burden without heavy automation. Many teams eventually look to consolidate or automate such setups because of this overhead.

In practice, most early-stage SaaS startups lean toward multi-tenancy as the default, because it maximizes scalability and minimizes cost and maintenance efforts. It's the model behind successful SaaS products like Salesforce, Slack, or HubSpot. A single codebase serving many clients, with robust tenant isolation built in. That said, *single-tenancy has its place*. If your product deals with extremely sensitive data or if you're targeting enterprise customers that demand dedicated environments (or need on-premise deployments), you may opt for single-tenant or at least offer it as an option.

## Choosing the right model for your SaaS

How should you decide between single-tenant and multi-tenant (or some mix of the two) for your application? Ultimately, it comes down to your product's audience, requirements, and your capacity to manage complexity. Here are some questions and considerations to guide the decision:

- **What is the relationship between your users and their data?** If each customer's data is completely separate and you have many customers with similar needs (classic B2B SaaS), a multi-tenant design makes a lot of sense. You can serve all of them on one platform while keeping data segmented by tenant. On the other hand, if each customer's use of the software is highly unique or requires heavy customization, single-tenant might be more appropriate so you can tailor each instance. Also consider whether customers might ever need to share data across tenants (usually not in B2B SaaS, but in some cases like a multi-org collaboration, it could happen). Cross-tenant data sharing is easier in a multi-tenant app and quite difficult if each tenant is completely isolated on separate systems.
- **Are you selling to large enterprises with unique needs or strict policies?** Big enterprise clients (especially in industries like finance, healthcare, government) often have strict security and compliance requirements. They might even require an isolated environment by policy. Single-tenancy or a hybrid (such as a dedicated database or instance for that client) could be necessary to close those deals. Also, enterprises often request custom features or integrations. A single-tenant deployment might accommodate that without affecting other customers. Conversely, if your target market is startups and small/medium businesses who generally are fine with a standard offering and care more about cost, a multi-tenant SaaS with a one-size-fits-most approach will be more cost-effective and manageable.
- **Do you need fast iteration and uniform updates for all customers?** If you're in a fast-moving space where you plan to deploy new features frequently to all users, multi-tenancy gives you one pipeline. You deploy updates and everyone is on the latest version. This is great for a product-led growth model where everyone should have the latest and greatest features. Single-tenant models can slow down iteration because you might have different customers on different versions, or you have to carefully roll out changes per instance. If offering a consistent, up-to-date experience across the board is a priority, multi-tenancy is attractive.
- **Are infrastructure costs and operational simplicity important at your stage?** Early on, you likely want to minimize DevOps overhead and cost. Multi-tenancy lets you maximize utilization of your resources. You won't be running 10 separate low-traffic servers for 10 customers. You might run one cluster that all 10 share, perhaps at a fraction of the cost. If you're concerned about cloud costs, multi-tenancy is generally more economical. Single-tenancy can become costly, but sometimes those costs can be passed to the customer (e.g., enterprise clients paying for a dedicated environment). If you do go single-tenant, ensure you have the pricing or funding to support potentially under-utilized resources per customer. Also consider if you have the engineering resources to automate deployment and monitoring for many instances. Without automation, a single-tenant approach can quickly overwhelm a small team.

After answering these questions, you may find that the decision is not strictly black-and-white. Many SaaS companies start with a multi-tenant core for the majority of customers, but adapt with some single-tenant elements as needed. For example, you might build a multi-tenant app for most, but offer a premium “dedicated instance” to enterprise customers who require it, effectively a hybrid approach. The good news is that modern software design allows a spectrum of tenancy models. Even within a multi-tenant system, you can achieve a high degree of isolation (for security or performance) by using techniques like separate databases or schemas per tenant, without giving up the efficiencies of a shared application.

In fact, it's possible to get the best of both worlds in many cases: keep your app multi-tenant for ease of development/deployment, but isolate certain resources per tenant. In the next section, we'll look at some of these hybrid strategies and the tools that make them feasible.

## Best tools for tenant isolation

One reason to feel more confident about multi-tenancy today is that there are powerful tools and frameworks that help with tenant isolation and management. You don't have to reinvent the wheel or build a multi-tenant architecture entirely from scratch. Many common challenges (authentication, data partitioning, access control) are solved by modern platforms. Here are a few examples of tools and approaches that support multi-tenant (and hybrid) SaaS architectures:

- [\*\*Clerk](https://clerk.com) (Authentication & User Management):\*\* Handling authentication and user accounts in a multi-tenant app can be complex, especially if users belong to organizations (tenants) and might switch between them. Clerk is a modern auth platform that provides built-in support for [organization-based multi-tenancy](/docs/organizations/overview). It offers pre-built components for managing organizations, switching between orgs, and role-based access control within a multi-tenant application. In other words, Clerk provides the infrastructure for managing users across multiple tenant organizations so you can focus on your app's functionality. For a frontend or full-stack developer, using a service like Clerk means you can easily implement features like inviting users to an organization, organization-specific roles/permissions, and even SSO, without building that from scratch.
- [\*\*Supabase](https://supabase.com/) (Backend with Row Level Security):\*\* Supabase is an open-source Firebase alternative built on PostgreSQL. One effective feature for multi-tenancy is Postgres [Row-Level Security (RLS)](https://supabase.com/docs/guides/database/postgres/row-level-security), which Supabase exposes in a developer-friendly way. RLS allows you to enforce that each query can only see or modify rows belonging to the requester's tenant, at the database level. For example, you can set a policy that `tenant_id` on a row must match the current user's tenant ID for any SELECT/UPDATE to succeed. This means even if a developer makes a mistake in code (like forgetting to add a tenant filter in a query), the database itself won't return another tenant's data. Using Supabase (or vanilla Postgres with RLS) can give you defense-in-depth for data isolation in a multi-tenant schema. It effectively lets you have a single database for all tenants while ensuring strict partitioning of data access. Supabase also handles many other backend concerns (scalability, auth integration, etc.), which can lower the barrier to adopting a secure multi-tenant design.
- [\*\*Prisma](https://www.prisma.io/) (ORM for Multi-Database or Filtered Access):\*\* Prisma is a popular Node.js ORM that can simplify database interactions. It can be helpful in a multi-tenant context in a couple of ways. First, if you choose a separate database per tenant approach (sometimes called the “silo” model), Prisma can manage multiple connections or clients. For instance, dynamically switching the database connection based on the tenant context in your app. The Prisma client can be instantiated with different connection strings at runtime (some teams maintain a map of tenant ID to DB connection). This makes it feasible to implement a database-per-tenant model without a ton of raw SQL boilerplate. Second, if you use a shared database with tenant IDs, Prisma's query capabilities let you easily add a tenant filter to every query (you might use middleware or default scopes). This reduces the chance of forgetting a `WHERE tenant_id =`... clause. Essentially, modern ORMs like Prisma (and others like Sequelize, TypeORM) are aware of multi-tenancy patterns and have documentation and community examples for implementing them. This saves you from writing a lot of repetitive code to enforce tenant separation in queries. Additionally, Prisma's type safety can prevent accidentally mixing up IDs, and so on.
- [\*\*Neon](https://neon.com/) (Serverless Postgres with Branching):\*\* Neon is a cloud Postgres service that offers intriguing features for multi-tenant and hybrid models. Notably, Neon supports [fast branching of databases](https://neon.com/docs/guides/multitenancy). You can create a new database branch quickly and cheaply. This can enable a database-per-tenant strategy with much less overhead than traditionally expected. In classic single-tenancy, having a separate database for each customer was seen as expensive and hard to scale (imagine running thousands of database servers). But Neon's *serverless Postgres* approach (and similar services) can spin up databases on demand and scale them based on usage. Neon's architecture is designed to handle many databases (tenants) efficiently, so you could isolate each tenant at the database level without incurring massive cost or ops burden. Their documentation even highlights the “database per tenant, data isolation without overhead” use case. This means you can achieve the holy grail of each tenant in a separate database (fully isolated, solving noisy neighbors and a lot of compliance issues) while letting Neon handle the scaling, connection pooling, and management behind the scenes. It's an example of how cloud innovations are blurring the line between multi and single tenancy. You can mix and match to get isolation and efficiency.

The overarching theme is that developers today don't need to build multi-tenancy from scratch. Authentication, authorization, and data security (all critical for multi-tenant SaaS) are available as services or frameworks. This not only accelerates development but also often results in a more secure and robust implementation (since these tools are battle-tested). As an early-stage team, leveraging these can let you focus on your core product while still achieving strong tenant isolation.

The bottom line is: choose the architecture that best fits your current needs, knowing that you have tools and options to adjust as you grow. For a young SaaS startup targeting a broad market, multi-tenancy will likely provide a faster path to a scalable and affordable service. As you gain larger customers or encounter specific needs, you can introduce single-tenant elements or tighter isolation where necessary. Modern platforms like Clerk, Supabase, Prisma, and Neon are making it easier than ever to enforce tenant isolation within a multi-tenant framework, giving you confidence that you're not trading off security or reliability for cost and simplicity.

## Conclusion

Choosing between multi-tenant and single-tenant architecture is a foundational decision for a SaaS product. Multi-tenancy offers scalability, cost-efficiency, and ease of management by serving all customers on a unified system. Single-tenancy provides robust isolation, security, and flexibility by giving each customer their own siloed environment. There is no one-size-fits-all answer. The right choice depends on your target customers, the sensitivity of data, compliance requirements, and resources available to your team.

For many B2B SaaS startups, a well-designed multi-tenant architecture is a great default, allowing you to scale up users quickly and deliver a consistent product experience to all. It usually aligns with a fast-moving development cycle and keeping costs under control. But as we've seen, what is multi-tenancy today is not an all-or-nothing proposition; you can achieve a high degree of tenant isolation even in a shared environment using modern techniques and tools. If you do have use cases that call for more isolation (or you're targeting enterprise clients who demand it), you can adopt a hybrid approach. Perhaps dedicating certain resources per tenant or offering a single-tenant option for those who need it.

Architectural tradeoffs are part of every SaaS journey. The good news is that with cloud platforms and frameworks available in 2025, you have a rich toolkit to implement whichever model (or combination) you choose, without reinventing the wheel. By carefully considering your product's needs and leveraging the right tools (from identity management to databases), you can design a SaaS architecture that balances security, performance, and cost-effectiveness. In the end, delivering value to your customers reliably is what matters. Both multi-tenant and single-tenant architectures can achieve that, as long as you understand the nuances and implement them thoughtfully.

---

# Postmortem: June 26, 2025 service outage
URL: https://clerk.com/blog/postmortem-jun-26-2025-service-outage.md
Date: 2025-06-26
Category: Company
Description: Learn more about our service outage, including the timeline of events and our next steps.

On June 26, 2025, all Clerk services were down from 6:16 UTC to 7:01 UTC, caused by an outage of our compute infrastructure that impacted all Clerk customers.

We are deeply sorry for this outage. Clerk is a critical infrastructure component for our customers, and we take our reliability and uptime seriously. We know that any amount of downtime is unacceptable, and regardless of the cause, our system’s reliability is our responsibility and we fell short of our standards and your expectations.

![Graph of request throughput to our services during the outage. (GMT+3)](./graph.png)

*Graph of request throughput to our services during the outage. (GMT+3)*

## Timeline of events

- 6:16 UTC: Downtime begins and the team starts its investigation
- 6:20 UTC: The team determines that there was neither a deploy coincident with the failure,  nor a spike in traffic
- 6:28 UTC: The team identifies that our Google Cloud Run containers are in a continuous restart loop, and receiving `SIGINT` shutdown signals immediately on start
- 6:32 UTC: The team decides to begin preparing a new release, to test if the `SIGINT`s are related to the particular container
- 6:40 UTC: A fresh container is prepared and deployed, and it also immediately receives a `SIGINT`
- 6:41 UTC: Unable to find a root cause, a P1 incident is filed with Google and we begin speaking with their support
- 6:49 UTC: We receive the first indication that there is an incident at Google: *“I’ve inspected your Cloud Run service and we suspect that you’re being impacted by the internal incident. Please allow me some time to confirm more on this while I reach out to the Specialist”*
- 6:50 UTC: We ask Google which incident, since none has been posted on its status page
- 6:55 UTC: Google responds: *”Yes, this seems to be not yet confirmed hence I’m checking with the Cloud Run Specialist to confirm the same”*
- 7:01 UTC: Service is restored
- 7:32 UTC: We receive the first official confirmation of an incident from Google, via an event from their Service Health API:
  ```text
  {
    @type: "type.googleapis.com/google.cloud.servicehealth.logging.v1.EventLog"
    category: "INCIDENT"
    description: "We are experiencing an  issue with Cloud Run beginning at Wednesday, 2025-06-25 23:16 PDT.
    
    Our engineering team continues to investigate the issue.
    
    We will provide an update by Thursday, 2025-06-26 00:45 PDT with current details.
    
    We apologize to all who are affected by the disruption."
    detailedCategory: "CONFIRMED_INCIDENT"
    detailedState: "CONFIRMED"
    impactedLocations: "['us-central1']"
    impactedProductIds: "['9D7d2iNBQWN24zc1VamE']"
    impactedProducts: "['Cloud Run']"
    nextUpdateTime: "2025-06-26T07:45:00Z"
    relevance: "RELATED"
    startTime: "2025-06-26T06:16:42Z"
    state: "ACTIVE"
    symptom: "The impacted customers in the us-central1 region may observe the service issues while using Cloud Run DirectVPC."
    title: "Cloud Run customers are experiencing an issue in us-central1 region"
    updateTime: "2025-06-26T07:32:13.864860Z"
    workaround: "None at this time."
  }
  ```

## What specifically went wrong?

We architected Clerk to be resilient against failures in Google Cloud availability zones, but not entire regions. [Cloud Run is stated to provide zonal redundancy](https://cloud.google.com/run/docs/zonal-redundancy), which implies that this incident was caused by a full regional failure.

On the other hand, if this was truly a regional failure, we would expect many more services to be impacted than just Clerk. While there was [some discussion on Hacker News](https://news.ycombinator.com/item?id=44384860), the blast radius of this event is surprisingly small for a regional failure.

We are awaiting more information from Google about exactly which system failed, and will update this post when it’s received.

**Update (June 27, 12:40AM UTC):** Google has notified us that their root cause analysis would be published by June 30.

**Update (July 8):** Google provided an RCA Summary, titled *"Cloud Run customers are experiencing an issue in “us-central1” region"* (Event ID: `MLFZZXV`)

- On June 25th 23:16 PDT, Cloud Run Direct VPC workloads in the `us-central1` region experienced downtime for a duration of 51 minutes (June 26 00:07 PDT).
- Workloads in our cloud regions are served out of multiple partitions. Every app deployment is randomly assigned to a primary partition (this cannot be controlled by customers, and is not visible to them). In this incident, only one partition was impacted. As a result approximately 15% of Direct VPC workloads in “us-central1” experienced downtime.
- Workloads in a serving partition are typically served from multiple capacity pools. Our capacity management system uses a load balancer configuration for each capacity pool to signal whether or not the capacity in the pool is online. However, for safety reasons the capacity management system is designed to fail open — that is, if the load balancer configuration is not present, customer workloads can still serve from this capacity.
- This incident was the result of the above tooling implementation alongside the design of our scaling for Direct VPC workloads. Post mitigation and confirmation of the issue’s resolution, our product and SRE teams have deep dived into the architecture which led to this incident and is committed to improving our service to ensure there is no recurrence of this issue.

> **Clerk's interpretation** This RCA indicates that a design flaw with Google Cloud Run's zonal redundancy caused our traffic to fail completely, instead of failover into a different capacity pool. This outcome is what we anticipated, and our remediation of regional failovers should prevent similar failures in the future.

## Remediations

When incidents like this happen, we immediately turn our attention toward preventing their recurrence. Regardless of the root cause, it is our responsibility to build a service that is resilient to failures within our infrastructure providers. To that end, we are starting the following remediations:

### Regional failover for compute (immediate)

This incident could have been mitigated with a failover that shifted our Cloud Run traffic to a different region when `us-central1` began failing. Work is starting on this immediately.

### Multi-cloud redundancy for compute

Although Google Cloud Platform (GCP) was remarkably stable for Clerk’s early years, we have faced three major server disruptions since May 2025 that we attribute to GCP incidents. This shows that we need to explore additional redundancy outside of a single cloud vendor.

We will begin investigating multi-cloud redundancy for our compute infrastructure. This would make Clerk resilient to complete service failures of Cloud Run, as well as failure of Google’s Cloud Load Balancer.

### Additional service isolation and redundancy for session management

Any incident in our Session Management service has an outsized impact on our customers, since it results in complete downtime of their service.

Following an incident in February, we isolated our Session Management service from our User Management service, ensuring that bugs in our User Management codebase would not impact the availability of our Session Management service.

Unfortunately, in the event of a compute outage at origin like we saw in this incident, both services still come down.

To further mitigate session management failures, we are exploring architectural changes that will allow Clerk to continue issuing session tokens for a greater variety of incidents. Though a longer-term project, this will include bringing distributed storage and compute to our Session Management service.

## Looking ahead

This list of remediations we are exploring is not exhaustive, and doesn’t represent a final state for our efforts to make Clerk as resilient as possible. We will continue to invest in stability and scalability to make sure our customers can rely on Clerk as a critical service provider.

This was a serious outage, and we know that businesses rely on Clerk. We are again deeply sorry for the impact on our customers and will continue working to improve our reliability going forward.

For any questions, please [contact support](https://clerk.com/contact).

---

# How to Design a Multi-Tenant SaaS Architecture
URL: https://clerk.com/blog/how-to-design-multitenant-saas-architecture.md
Date: 2025-06-18
Category: Guides
Description: Learn how to design a multi-tenant SaaS architecture that scales from your first 10 users to your next 10,000.

Designing for multiple customers in a single application sounds simple—until it's not.

[Multi-tenancy](/b2b-saas) is the process of supporting multiple organizations or customers in a single application. It requires deliberate architectural choices around authentication, authorization, data storage, and performance. As your product grows, so do the expectations around isolation, access control, and performance. Building a solid multi-tenant strategy is the foundation for scale.

In this guide, we’ll walk through the core principles of multi-tenancy, popular database models, [authentication](/user-authentication) flows, and the tools that help you ship a secure, flexible SaaS product faster.

## Key Principles of Multi-Tenant Design

### Data Isolation

The most important rule of multi-tenancy is this: **one tenant should never see another tenant’s data**. This is often enforced at the application layer, but can also be enforced at the database level using Postgres's Row-Level Security (RLS) mechanism. Platforms like Supabase and Neon support this natively.

For stricter isolation, such as in compliance-driven industries, some apps take it further by assigning **a separate database per tenant**—a pattern we’ll explore in more detail shortly.

### Auth Separation

In more security-sensitive applications, you may want to isolate not just data but user identity as well. This could mean creating [**separate user pools** per tenant](/glossary#isolated-user-pool) or requiring tenant-specific authentication flows like custom sign-in URLs.

### Role Scoping

Once users are inside the app, you still need to control *what* they can do. Assigning roles and permissions per tenant is critical to ensure users only access the data and functionality they’re entitled to. This is especially important when users can belong to more than one tenant.

[Role Based Access Control (RBAC)](/glossary#role-based-access-control) is a scalable strategy for ensuring users have the right access within a tenant. It dictates that each user has a role that contains all of the permissions they need to perform their duties, and the authorization logic can check which permissions exist for the user before allowing them to perform the desired action on the system.

### Shared Infrastructure

Most SaaS products operate on **shared infrastructure**, where compute, storage, and codebases are reused across tenants. This reduces operational costs and simplifies deployments. However, one downside is that a noisy tenant (one with heavy usage or bad behavior) could affect others if isolation isn't thoughtfully implemented. This is why it's important to have a good understanding of the different database models and how they can be used to isolate data, as well as a good system to monitor and alert on tenant-level performance.

## Multi-Tenant Authentication Flows

### Making Authentication Tenant-Aware

In a multi-tenant SaaS app, authentication often needs to be aware of which tenant the user is associated with, sometimes before they’ve even signed in. Some tenants may require specific login methods or domain restrictions that your system needs to enforce.

There are a couple of common patterns to handle this:

The system that handles user authentication can preemptively determine which tenant the user belongs to based on their username or domain. For example, if someone signs in with `brian@clerk.dev`, the system can detect the domain and automatically associate the login with the `clerk.dev` organization. At Clerk, we call this “**verified domains**” and it allows new users to sign up with their own domain and automatically be associated with the correct tenant. This makes the experience seamless while maintaining tenant-level security controls.

Another approach is **explicit tenant selection**, where the user is prompted to select their tenant before authenticating. This can be handled via a subdomain, URL path, or even a dropdown based on past login history. Once selected, your app can enforce tenant-specific auth logic for that session.

### Managing Tenant Context for Logged-In Users

If users in your app can belong to more than one organization, you’ll need a way to determine which tenant context they’re operating in at any given time.

This can be tracked using a field on the user account that stores their active tenant, or through a dedicated mapping table in the database that links users to the tenants they belong to along with their associated roles and permissions.

When using [JWT-based authentication](/blog/combining-the-benefits-of-session-tokens-and-jwts), details about the active tenant in which the user is operating can be stored in the token claims. When verified, the system can trust the claims and automatically associate the request with that tenant. Clerk’s B2B tools use this approach, storing the user’s active organization and their permissions directly in the token.

This ensures your app knows not just *who* is logged in—but *what they can do* and *where they are* in the multi-tenant hierarchy.

## Common Database Patterns

The way you model and isolate tenant data has huge implications for scalability and maintainability. Here are the most common strategies:

### Shared DB / Shared Schema

All tenants share the same database and the same table structure. Each record includes a tenant ID to segment the data. This is the easiest setup to manage and makes it straightforward to run queries across tenants—such as for analytics or internal metrics. However, it also introduces a higher risk of accidental data leakage if tenant IDs aren’t properly enforced. Without database-level controls like RLS, you’ll be relying solely on application logic to enforce boundaries.

![Multi-tenant database structure diagram](./multi.png)

### Shared DB / Isolated Schemas

In this model, all tenants share a single database, but each tenant has its own schema. This provides a stronger logical boundary between tenants than a shared schema. You get more security at the database level while still avoiding the overhead of managing many separate databases. That said, you’ll need to apply database changes to every schema, which can be tedious if not automated. Additionally, not all tooling or ORMs support multiple schemas cleanly.

### Isolated DB per Tenant

With this approach, each tenant is given a completely separate database. It offers the highest level of isolation and is often required by enterprise customers in regulated industries. This setup allows you to fine-tune performance and resources per tenant. However, it comes with a significant maintenance cost as migrations and schema changes need to be deployed to every database instance. If you are using a shared application layer, you’ll also need a routing mechanism in your application to connect each user to the correct database.

![Database per tenant diagram](./single.png)

### Hybrid Models

Some SaaS platforms use a mix of the above models. For example, small teams and startups may be placed on a shared schema, while enterprise customers receive isolated databases as part of a premium plan. This hybrid approach gives you the flexibility to scale tenant isolation based on customer needs, without overengineering from day one.

## Modeling Tenants in Your Database

### Structuring Tables for Multi-Tenant Access

The way you design your tables plays a big role in protecting tenant data. Any table that stores tenant-specific records should include a `tenant_id` field, and often a `created_by_user_id` as well. This provides a clear trail of ownership and supports granular permission enforcement.

An example of a `tasks` table with these attributes would look like this:

```sql
CREATE TABLE tasks (
 task_id SERIAL PRIMARY KEY,
 title VARCHAR(255) NOT NULL,
    description TEXT,
 done BOOLEAN DEFAULT FALSE,
 created_by_user_id TEXT NOT NULL,
 tenant_id TEXT NOT NULL
);
```

You’ll also want a way to track which users belong to which tenants and what roles they have. This can be done with mapping tables that link users to tenants along with their access level. Here is an example of these tables:

```sql
-- This table tracks which users
CREATE TABLE user_tenants (
 user_id TEXT NOT NULL,
 tenant_id TEXT NOT NULL
);

-- This would track the role a user has in each tenant
CREATE TABLE user_roles (
 user_id TEXT NOT NULL,
    role VARCHAR(50) NOT NULL,
 tenant_id TEXT NOT NULL
);

-- This table tracks which permissions belong to which role
CREATE TABLE role_permissions (
    role VARCHAR(50) NOT NULL,
 tenant_id TEXT NOT NULL,
 permission_id INT NOT NULL
);

-- This would contain the permission name (ex: tasks.read, tasks.write)
CREATE TABLE permissions (
 permission_id SERIAL PRIMARY KEY,
 permission_name VARCHAR(50) NOT NULL UNIQUE
);
```

Before allowing any sensitive operation, like writing or deleting data, your app should verify the user's permissions for the active tenant. Whether you do that in application logic or using [database-level RLS](https://supabase.com/docs/guides/database/postgres/row-level-security), this check is key to maintaining secure multi-tenant boundaries.

### Associating Requests with the Right Tenant

To properly enforce tenant isolation, every request must be explicitly tied to a tenant. Let’s take a practical look at the two proposed strategies from earlier, using the database structure from the previous section.

**Store the active tenant with the user or session record in the database**

When storing the user’s active tenant ID in the database, a query to the `tasks` table returning all tasks for a user actively in the `org_1234` tenant would look like this:

```sql
SELECT *
  FROM tasks
  WHERE tenant_id = 'org_1234';
```

Now let’s also consider the permissions a user has when inserting a record into the `tasks` table. You’d first want to check to make sure the user has a role with the `tasks.write` permission:

```sql
SELECT 1
FROM user_tenants ut
JOIN user_roles ur ON ut.user_id = ur.user_id AND ut.tenant_id = ur.tenant_id
JOIN role_permissions rp ON ur.role = rp.role AND ur.tenant_id = rp.tenant_id
JOIN permissions p ON rp.permission_id = p.permission_id
WHERE ut.user_id = 'user_123'
  AND p.permission_name = 'tasks.write'
  AND ut.tenant_id = 'org_123'
LIMIT 1;

```

Assuming the above check passes, the following query could then be executed:

```sql
INSERT INTO tasks (
 title,
  description,
 done,
 created_by_user_id,
 tenant_id
) VALUES (
  'Task Title',
  'This is the description of the task.',
 FALSE,
  'user_1234',
  'org_1234'
);
```

**Store the active tenant information in the JWT**

If you store the user’s role and permissions in the JWT, you will check the values of the verified token claims in code before executing the query. For example, Clerk issues tokens with claims that would look like the following.

Note the organization values in the `o` claim and the list of permissions stored in the `perms` claim:

```sql
{
  "azp": "http://localhost:3001",
  "exp": 1749142876,
  "fea": "o:articles",
  "fva": [
    2,
    -1
 ],
  "iat": 1749142816,
  "iss": "https://modest-hog-24.clerk.accounts.dev",
  "jti": "106e6c2a3d141e64dbcf",
  "nbf": 1749142806,
  "o": {
    "fpm": "3",
    "id": "org_1234",
    "per": "read,write",
    "rol": "admin",
    "slg": "echoes"
 },
  "perms": [
    "org:tasks:read",
    "org:tasks:write"
 ],
  "pla": "o:free_org",
  "role": "authenticated",
  "sid": "sess_2y66UuWq2epuWYYmMfkkT59SeA7",
  "sub": "user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7",
  "v": 2
}
```

> \[!NOTE]
> In this example, `perms` was added manually into the session claims in the Clerk dashboard.

The following JavaScript example checks for the `org:tasks:write` permission before inserting a task using the `sub` claim as the user ID and the `o`.`id` as the tenant ID:

```jsx
const { sessionClaims } = await auth() // Using the Clerk `auth` helper function

if (sessionClaims?.perms.includes('org:tasks:write')) {
  const query = `
 INSERT INTO tasks (title, description, done, created_by_user_id, tenant_id)
 VALUES ($1, $2, $3, $4, $5) RETURNING task_id;
 `

  const values = [title, description, done, sessionClaims.sub, sessionClaims.o.id]

  const res = await client.query(query, values)
}
```

## Tools That Help

### Clerk

[Clerk](/company) is a complete user management solution that integrates seamlessly with multi-tenant apps. Through our B2B toolkit, we support organizations (tenants), verified domains, and fine-grained access control through custom roles and permissions. We also support enterprise authentication flows such as SAML and OIDC, multifactor authentication, and passkeys, and can enforce identity providers on a per-tenant basis.

### Database options

Databases are the backbone of tenant isolation. Choosing the right provider and schema strategy impacts not only performance but also how you scale and segment customers.

[**Supabase**](https://supabase.com/) is a great choice for shared-database models. It offers built-in Row-Level Security (RLS), letting you enforce per-tenant access at the database layer itself. Because it's built on Postgres, you can use policies, views, and triggers to implement sophisticated tenant-aware queries while still keeping things performant.

[**Neon**](https://neon.tech/) provides a compelling model for apps that need stronger isolation. It allows you to spin up isolated branches of a database that can be tied to specific tenants, allowing for independent data storage, migrations, and even teardown if needed. You can pair Neon with Vercel or Supabase for shared frontend and auth infrastructure while maintaining hard tenant boundaries at the data level.

[**PlanetScale**](https://planetscale.com/) offers horizontal scaling through Vitess and supports schema branches, though its approach to isolation is more opinionated. It works well for global apps with high throughput demands but requires careful planning when implementing tenant-specific patterns.

### Automating Tenant Infrastructure with AWS or GCP

For teams adopting a **per-tenant infrastructure model**, cloud providers like AWS and GCP offer automation tools to make that feasible at scale.

With **AWS**, you can use CloudFormation, CDK, or Terraform to provision dedicated resources per tenant—databases, S3 buckets, Lambda functions, and even isolated VPCs if required. Services like EventBridge or Step Functions can orchestrate the entire flow: when a new tenant signs up, your system triggers a pipeline that creates their environment, configures access, and notifies your app.

**GCP** offers similar functionality through tools like Deployment Manager, Workflows, and Cloud Functions. You can set up Cloud SQL instances for per-tenant databases, deploy Cloud Run services for tenant-specific APIs, or isolate workloads with separate projects or namespaces. Pub/Sub can act as the event bus that kicks off provisioning workflows based on user actions or internal triggers.

In both cases, treating infrastructure as code makes tenant provisioning consistent, repeatable, and secure. For enterprise-grade SaaS, this approach not only meets isolation and compliance requirements—it becomes a selling point for larger customers who expect guarantees around performance and data segregation.

## Conclusion

Designing for multi-tenancy is about more than just scaling. It’s about trust, flexibility, and maintainability. As your user base grows and your customer’s needs evolve, your architecture should support that growth without adding friction or risk.

The right combination of tools with the proper strategy can help you avoid scaling issues down the road. Using the knowledge and recommended tools outlined in this article, you are now well-equipped to design a multi-tenant SaaS architecture that scales from your first 10 users to your next 10,000.

---

# What is multi-tenancy and why it matters for B2B SaaS
URL: https://clerk.com/blog/what-is-multi-tenancy-and-why-it-matters-for-B2B-SaaS.md
Date: 2025-06-17
Category: Insights
Description: Learn what multi-tenancy is, why it matters for B2B SaaS apps, and how it shapes your architecture decisions.

Let's assume you’re building a SaaS product for engineering teams to manage their cloud repositories and deployment workflows. We’ll call it **“HubGit.”**

You already have some early customers on your waitlist, each with its own team of developers, repositories, and even CI/CD pipelines. Naturally, you want every developer at these companies to authenticate securely, see only their organization’s code and workflows, and collaborate exclusively with their colleagues. But you don’t want to build and maintain a separate app or backend or database for every organization.

That's where multi-tenancy comes in.

Multi-tenancy is the architecture behind most B2B SaaS products today and it's what makes them scalable and cost-effective as their customer base grows. When Slack serves millions of workspaces, or when Shopify powers hundreds of thousands of stores, they're likely not running separate applications for each customer. They're running one powerful multi-tenant system that delivers isolated experiences for every single tenant.

> Ready to build your B2B SaaS with multi-tenant authentication? [Learn more about our B2B SaaS solution](/b2b-saas).

In this article, we’ll explore what multi-tenancy is, why it matters for B2B SaaS, and how to design for it. Specifically across our multi-tenant series, we’ll cover:

- Common architecture patterns, database strategies, and authentication factors/challenges.
- Practical steps for implementing multi-tenant authentication flows that work in production.
- Examples of real-world applications using multi-tenancy

## What is multi-tenancy?

At its core, [multi-tenancy](https://clerk.com/glossary/multi-tenancy) is an architectural design pattern that allows a single instance of an application to serve multiple customers (or "tenants") while keeping their data, configurations, and workflows isolated from one another. Each tenant acts as a separate entity, with unique users, settings, and authentication factors.

![Multi-tenancy architecture diagram](./multitenancy.png)

Take Shopify, for example. When a store owner signs up for Shopify, they're not just getting a traditional user account. They're getting an entire isolated tenant/organization account where their products, orders, customer data, payment settings, themes, and even custom apps are completely separate from every other Shopify store. This demands a full multi-tenant architecture, with tenant-isolated authentication flows where users might belong to multiple organizations, custom security policies per tenant, isolated databases or schemas, and even different compliance requirements for each business.

However, it’s important to mention that not every SaaS app needs multi-tenancy. If you're building a SaaS app that caters directly to individuals and would never cater to groups of individuals, you don't have to worry about multi-tenancy. You literally don't need it. But if you're building a B2C SaaS for shared use cases, or building a B2B SaaS that you plan to sell all the way up to enterprise at some point, the foundation of your architecture has to be multi-tenant.

## Why multi-tenancy for B2B SaaS?

Multi-tenancy is a foundational decision that shapes how SaaS products serve B2B customers with wildly different requirements as they scale.

Think of it this way: businesses are inherently complex. They have multiple departments with employees who need different levels of permissions, and these teams often grow, and members change over time.  Multi-tenancy helps you model these real-world organizational structures and requirements in a clean, programmatic way, from inviting teammates and assigning roles, to isolating usage/data at the org level. It’s designed to handle the complexities of how businesses actually work.

But here's where it gets really interesting. When you have multiple tenants who probably have different business requirements relying on a multi-tenant infrastructure, they're all pushing it to grow in multiple directions. The eCommerce company needs better payment processing. The healthcare startup needs stricter compliance features. The manufacturing company needs bulk data imports. While each tenant may be pushing for improvements in the context of their isolated portion, everyone ends up benefiting from these "isolated improvements" because they all share the same infrastructure.

![Why multi-tenancy for B2B SaaS](./image.png)

There are even more reasons why multi-tenancy is the bread and butter of [SaaS architecture](https://clerk.com/docs/guides/multi-tenant-architecture). Let’s break them down:

- **Faster feature rollouts:** With a single codebase serving all tenants, every feature you launch is immediately available across your customer base. You write code once, and every tenant gets it immediately. Compare this to single-tenant deployments where you're maintaining separate codebases and coordinating dozens of different rollouts. That's a nightmare!
- **Cost efficiency:** Running one shared application that serves multiple tenants is fundamentally cheaper than managing separate applications and deployments for each tenant. You have less infrastructure to monitor, fewer environments to maintain, and simpler disaster recovery SLAs.
- **True scalability**: With multi-tenancy, adding a new customer doesn't mean provisioning new isolated servers or cloud environments. It's often just creating a new organization record in your database. As you grow, your architecture doesn't fundamentally change. Your core multi-tenant design will stay the same whether you're serving 100 tenants or 100,000.

## What are the risks of not going multi-tenant?

Many developers think they can start building their B2B SaaS with a B2C architecture and "add multi-tenancy later," but this approach creates fundamental data model problems that are exponentially harder to fix as you scale. Let's explore some of the technical debt and engineering challenges you'll face if you try to avoid multi-tenancy from early on:

- **Database migrations:** Every schema change would require coordinating migrations across dozens or hundreds of separate databases since tenants are practically isolated. What should typically be a simple column addition to the DB becomes a complex activity requiring unique execution for each tenant, because one failed migration can leave you with inconsistent schemas across your DB with no clean way to recover.
- **Monitoring and debugging complexity:** When issues arise, you can't just check one set of logs or metrics. You're hunting across multiple databases, separate application instances, and isolated environments to understand what went wrong. Root cause analysis becomes a an unending job across disconnected systems, making critical bug fixes take days instead of hours.
- **Resource scaling becomes impossible:** Without shared infrastructure, scaling requires provisioning new servers, databases, and environments for each tenant individually. Auto-scaling becomes impossible when you're managing hundreds of separate deployments. Performance optimization means profiling and tuning dozens of different environments instead of optimizing one shared system.
- **Data backup and disaster recovery:** Each tenant needs separate backup strategies, disaster recovery procedures, and data retention policies. In the event of any disaster, you're not restoring one system, you're coordinating recovery across multiple isolated environments with different states and dependencies.

## Example features of multi-tenant SaaS apps

Let’s look at some practical examples of how multi-tenancy could show up in SaaS applications. These examples highlight how multi-tenancy supports the dynamic needs of B2B customers while still letting your SaaS serve every organization from one shared infrastructure:

- **Collaborative workspaces:** If you’re building a B2B SaaS, your app should natively support collaborative workspaces (sometimes called organizations, teams, or tenants) where users can be invited or request to join based on their roles and permissions. Think of tools like [Slack](https://slack.com/help/articles/115004151203-Workspaces-overview), where each workspace operates as an isolated tenant with its own users, data, and integrations, or [Notion](https://www.notion.com/help/category/sharing-and-collaboration), which allows granular sharing and collaboration settings across teams. Your app must securely manage user invitations, role assignments, organization settings, and seamless tenant switching—all while maintaining strict data isolation.
- **Organizational/Admin dashboards:** Every B2B organization typically has an admin or owner who needs to manage everything from user accounts and memberships to billing, integrations, and usage analytics. Your B2B SaaS must provide a robust account management portal—not just for admins, but also for end users to manage their membership status, security settings, billing details, and MFA preferences. A great example of this is [Stripe’s account management portal](https://docs.stripe.com/dashboard/basics), which sets a high bar for clarity and functionality. Admins should be able to send invites, assign roles, review join requests, and remove users with ease.
- **Roles and permissions:** Supporting bespoke multi-tenant authentication and authorization goes well beyond basic RBAC. Each organization often has its own roles, permissions, and workflows that don’t fit standard, pre-defined templates. While your B2B SaaS should offer default roles like admin and member, it also needs to support custom roles that developers can define to meet the specific needs of each tenant. [Clerk’s roles and permissions system](https://clerk.com/docs/organizations/roles-permissions) is a strong example of how to enable fine-grained access control at the organization level.
- **Org-specific auth flows and settings:**  From custom authentication methods to unique UI and branding requirements, organizations often need configuration settings that are isolated from other tenants. Your B2B SaaS must support these custom policies at the org level—whether it’s enforcing SSO, requiring email verification, or restricting sign-ups to certain domains. A good starting point for building this kind of tenant-aware auth experience is Vercel’s [Next.js + Clerk authentication starter](https://vercel.com/templates/next.js/clerk-authentication-starter), which provides a ready-to-use foundation for customizing auth per organization.

## Up Next: Designing multi-tenant SaaS architectures

Now that we've established why multi-tenancy matters for B2B SaaS, our next post will explore how to design and build these systems. You'll learn how to architect a multi-tenant SaaS platform that scales from your first 10 users to your next 10,000 and beyond.

---

# How OAuth Works
URL: https://clerk.com/blog/how-oauth-works.md
Date: 2025-06-13
Category: Engineering
Description: A practical guide to OAuth Scoped Access that walks through the Authorization Code Flow with real code examples, security best practices, and clear explanations of how third-party app integrations actually work.

OAuth is confusing. There's no getting around it - the specification is complex, the terminology is overloaded, and the security considerations are numerous. But it doesn't have to be intimidating once you understand the core concepts.

This guide focuses on practical understanding over abstract theory. While Clerk handles most OAuth complexity for you with a built-in authorization server, understanding the fundamentals will make you a better developer and help you debug issues when they arise.

Here's what we'll cover:

- **OAuth Scoped Access** - letting third-party apps access user data without sharing passwords
- **The Authorization Code Flow** - the step-by-step process that makes it work
- **Real implementation details** - actual code you can run, not just theory
- **Security essentials** - PKCE, state parameters, and other protections

There are plenty of OAuth articles out there, but many focus on abstract concepts without showing you how the pieces actually fit together. This guide takes a different approach - we'll walk through a complete implementation so you can see exactly how OAuth works in practice.

## What is OAuth?

[OAuth](https://oauth.net/2/) stands for "Open Authorization", and it is a set of standard specifications designed by the [IETF](https://www.ietf.org/) that address how a user can grant a third party application access to some of their data/resources, without providing their login directly.

As an example, imagine that you are a brand manager at a trendy coffee shop, and you have been tasked with writing marketing content and scheduling it to go out at certain times throughout the week. There are many "content planning" apps that will allow you to do this across several social platforms -- LinkedIn, Twitter/X, Facebook, etc -- let's imagine we're using a fictional one called "Content Planner". Content Planner needs to make posts on your behalf on social platforms.

Content Planner *could* just ask you for your email and password for each social platform, but this is less than ideal for several reasons:

- **Restricting access:** You probably do not want to give Content Planner full access to your account -- only the ability to make posts, but restrict access to send DMs, change your password, or delete your account. If Content Planner has a security incident, it would be best to minimize the damage. [Principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), right?
- **Avoiding bot protection issues:** If Content Planner did have your credentials, it would need to sign in as you. Many apps and authentication providers will automatically detect and block any sort of scripted login attempts as a bot, to prevent fraud and bot attacks (shameless plug: Clerk [will do this for your app automatically](/docs/security/bot-protection)). So, it would be tough for Content Planner to actually sign in as you even if you did provide your email/password.
- **Avoiding MFA issues:** It's becoming increasingly common as a security measure to require more than just an email/password. Sending an [OTP](/docs/custom-flows/email-sms-otp) to your phone or email, getting an [authentication code from an app](/docs/custom-flows/email-password-mfa), using a [passkey](/docs/custom-flows/passkeys), etc. are all common methods of increasing the security of your users' accounts (shameless plug once again, each of these are a simple toggle with Clerk). In these cases, an email and password alone wouldn't be sufficient anyway.

What we need here is a legitimate avenue through which you, as a user of Content Planner, can tell Twitter (going to refer to it as Twitter for this post, I know it's "X" now though), LinkedIn, Facebook, etc that you would like to grant access to Content Planner to make posts on your behalf. This type of interaction is exactly what OAuth was designed to handle.

## Common OAuth Terminology

Let's start with some common terminology. This will help to communicate concepts clearly through the rest of the piece.

- **Client**: This is a very generic term that is common in software discussions. When discussing OAuth, however, it has a very specific meaning that is important to really internalize: *A Client is an entity that wants to get something from another entity*.
  Referencing our above example, Content Planner is the Client, since it wants to get permission to make posts from Twitter, Facebook, LinkedIn, etc.
- **Authorization Service**: This refers to the service that is responsible for signing the user in and out and delegating access to the user's account to third parties. For example, users signing into Content Planner with their Twitter account would use Twitter's authorization service. This may or may not be a *server*, though you will often see it referenced as a "authorization server", including in OAuth specs, but we're going to call it a service in this piece, because it's more accurate, and less confusing. This is sometimes also referred to as an "Identity Provider" or "IdP".
- **Resource Service**: This refers to the service that has the resources the client wants access to. Referencing our above example again for Twitter, this would be the Twitter app itself, which is what has the ability to post things. As mentioned above, this may or may not run on a separate server from the authorization service, and you will likely see it referred to as a "resource server" in other writing about OAuth, but we're sticking with "service" in this piece for accuracy. This is sometimes also referred to as an "Service Provider" or "SP".
- **OAuth Access Token**: A string of random characters (which is generally what a "token" is in the realm of web development) or [JWT](https://datatracker.ietf.org/doc/html/rfc7519) that is given to the *Client* by the *Authorization Service* if the OAuth process is completed successfully. The Client can then send this Token to the *Resource Service* as a form of proof that it should be allowed to access what it's trying to access.

With all of this covered, we should be able to piece together a very basic overview of how OAuth works:

![oauth.png](./oauth-flow-full.jpg)

## Other OAuth use cases

We're going to get deeper into the details of how all of this works in a minute, but be aware that OAuth is broader than this. The diagram above is just one way of using OAuth, and there are several others. OAuth as a protocol can be used for creating users and signing in with a regular username and password, for signing in with one of those number codes that you sometimes do with TV apps, for single sign on (where you "Sign in with Google"), for authorizing requests between two servers that don't even involve a human, and much more. In fact, there are *30+ RFCs* describing modifications and extensions to OAuth.

This makes the term "OAuth" fairly confusing in general. When someone refers to using OAuth, are they talking about signing in to their app using a third-party app? Are they talking about building authentication for their app? Or are they talking about granting access to resources on a user's behalf as described above? This confusion makes it really hard to research and learn about OAuth. In order to further clarify this, we are introducing more specific terms for each of these three flows, that we are hoping will be adopted more widely for the benefit of all developers on the OAuth learning journey:

1. **OAuth Scoped Access** - scoped 3rd party data access via OAuth (the one we described above)
2. **OAuth SSO** - sign on to an app through a third-party app, using OAuth (like Sign in with Google, etc)
3. **OAuth User Management** - OAuth as a user registration and sign in/out mechanism (a full authorization service built with OAuth)

In this post, we're going to discuss *OAuth Scoped Access*, the same flow we have been describing since the start.

## The OAuth authorization code flow in action

Now that we have a broad idea of the scope and goals of OAuth, let's get into the specifics for what is likely the most common OAuth flow, called the ["Authorization Code Flow"](https://oauth.net/2/grant-types/authorization-code/), which allows for OAuth scoped access. You are now stepping into the shoes of the developer of the Content Planner app, tasked with *implementing* an OAuth flow with Twitter. We will walk through how to write a Client that interacts with an Authorization Service. We'll use the same example we have been using above, with Content Planner as the Client and Twitter as the Authorization/Resource Service.

> \[!NOTE]
> *All references to Twitter and its API are fictional, just for example purposes.*

1. You make a request to Twitter's API to make a post on your user's behalf. This will fail, since you do not have permission to do so. However, Twitter will return a `www-authenticate` header alongside the 401, which has the value `Bearer resource_metadata=http://api.twitter.com/.well-known/oauth-protected-resource` (as defined by [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)).

2. You can visit this URL and get back information about *where to go* to get an OAuth flow going with Twitter. Here's what you might see as the response:

   ```json
   {
     "resource": "https://api.twitter.com",
     // ⚠️ This line below is the important part!
     "authorization_servers": ["https://auth.twitter.com"],
     "token_types_supported": ["urn:ietf:params:oauth:token-type:access_token"],
     "token_introspection_endpoint": "https://auth.twitter.com/oauth/token",
     "token_introspection_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
     "service_documentation": "https://docs.twitter.com/oauth",
     "authorization_data_types_supported": ["oauth_scope"],
     "authorization_data_locations_supported": ["header"],
     "key_challenges_supported": [
       {
         "challenge_type": "urn:ietf:params:oauth:pkce:code_challenge",
         "challenge_algs": ["S256"]
       }
     ]
   }
   ```

3. You can now select an authorization server (though `authorization_servers` is an array, realistically you are nearly always just going to get just one back), and make a request to that authorization server for its metadata, which outlines the OAuth endpoints. This should be found at our authorization server URL we got from the metadata file above, `https://auth.twitter.com`, then we tack on the established path, `/.well-known/oauth-authorization-server` according to [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414). Making a request to this path should return something along these lines:

   ```json
   {
     "issuer": "https://twitter.com",
     // ⚠️ This line below is the important part!
     "authorization_endpoint": "https://auth.twitter.com/oauth/authorize",
     "token_endpoint": "https://auth.twitter.com/oauth/token",
     "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
     "token_endpoint_auth_signing_alg_values_supported": ["RS256", "ES256"],
     "userinfo_endpoint": "https://auth.twitter.com/oauth/userinfo",
     "jwks_uri": "https://auth.twitter.com/.well-known/jwks.json",
     "registration_endpoint": "https://auth.twitter.com/oauth/register",
     "scopes_supported": ["openid", "profile", "email"],
     "response_types_supported": ["code"],
     "service_documentation": "https://docs.twitter.com/oauth",
     "ui_locales_supported": ["en-US"]
   }
   ```

   > \[!NOTE]
   > It should be noted that the steps above are not required, nor does every service that has implemented OAuth support them. Normally, all you need to get started with OAuth is the `authorization_endpoint` seen above, so if you know that endpoint already the steps above can be skipped. They are still recommended though - in the case the service makes changes to their endpoints in any way, following the above steps would allow your app to automatically "heal" through this.

4. Now that we have the authorization endpoint, we can kick off the "Authorization Code Flow" in earnest. The way an OAuth flow begins is normally that your user clicks a button or takes some action within your app. In this case, we'll imagine there's a "Connect with Twitter" button in the interface for Content Planner, and the user clicks that button.

   ![connect-twitter.png](./authorization-flow-kickoff.png)

   As the app developer, we can decide what that link looks like, and we'd like for it to point to the *authorization endpoint*, but also provide some extra details according to the OAuth spec. Let's write that code (in React, just for the example):

   ```jsx
   function OAuthConnection() {
     // we just got this from the authorization server metadata
     const authorizeEndpoint = 'https://auth.twitter.com/oauth/authorize'

     // we'll talk about these these below, promise
     const params = {
       response_type: 'code',
       client_id: 'abc123',
       redirect_uri: `https://contentplanner.app/oauth_callback`,
       scope: 'email profile tweet:write tweet_stats:read',
       state: 'random-value',
     }

     // this just takes the params and tacks them on to the authorize endpoint
     // as a querystring
     const authorizeUrl = `${authorizeEndpoint}?${new URLSearchParams(params).toString()}`

     return (
       <div>
         <p>Connect Content Planner to your X account</p>
         <a href={authorizeUrl}>Connect!</a>
       </div>
     )
   }
   ```

   First, let's talk about the "params" specified above, to make sure we understand what's actually going on there:

   - `response_type` - we [mentioned earlier](#other-o-auth-use-cases) that are several different types of OAuth flows, which makes OAuth a rather confusing thing in general. Now we are starting to pay this debt - since there can be several different flows available from the same authorize endpoint, we need to supply this parameter to specify which type of flow we want to run. In this case, if you recall, we're after the *"Authorization Code Flow"*, and the param *"code"* is what tells OAuth that this is what we want.

   - `client_id` - OAuth specifies that *Clients* must *register* with the *Authorization Service* in order for the flow to work. This is so that when the user goes through the flow in which they grant access, the Authorization Service is able to make it clear *whom* the user is granting access to. You may have done something like this before. Normally, this involves making an account with the Resource Service, going into some sort of "developer settings" and creating an "OAuth Application" or "OAuth Client", where you put in a name, a redirect url, sometimes an avatar, etc. When you have created an OAuth client, the UI will provide you with a **Client ID** and **Client Secret**. We'd take that Client ID and use it for this parameter.

   For our example, this would mean logging into Twitter, going into their developer settings, creating an OAuth Client through their UI, and pasting the Client ID in here (or more commonly, pulling it from an environment variable). It's worth noting that OAuth also includes an optional extension called "dynamic client registration" that authorization services can implement, in which there is an API endpoint through which an OAuth Client can be created, rather than through an application's UI, but this isn't super common at the moment. [We'll talk about that more later](#dynamic-client-registration).

   - `redirect_uri` - When our user clicks on the "connect" link, it will send them out to the Authorization Service for Twitter -- after they are done, Twitter needs to send them back to Content Planner. This is the URL that they are sent back to.

   ![redirect-uri.png](./redirect-uri.png)

   - `scope` - *Authorization Services* may define "scopes" which determine what users are able to grant access to. As a *client*, you can then request access to the scopes you need via this param when hitting the authorize endpoint. Here, we're asking for some basic info about the user, as well as the ability to write tweets and read the tweet stats, so we can see how well our scheduled tweets performed.

   - `state` - This is for security, and ensures that the initial authorization request matches up with the response that was sent back. The base purpose of this param is for the *Client* to generate it, then for the *authorization service* to send it back as a query string when it goes to the `redirect_uri`. The *client* then checks it against the initial value to make sure it matches. Leaving this out can expose users to [CSRF](https://owasp.org/www-community/attacks/csrf) attack shenanigans where an attacker can begin an OAuth flow with their own account, then get someone else to click a link that will complete it with *the victim's* account, letting the attacker take over the victim's account. The state param can also be used to pass some data safely through the OAuth redirect flow, since we know we will get it back on the other side.

   Clicking the `authorizeUrl` link will bring users to a "consent screen", which might look something like this:

   ![OAuth consent screen](./consent-screen.png)

   This screen clearly lays out what permissions you are granting as a user and whom you are granting them to. It utilizes the `scopes` that we passed in the query to clarify for the user what they are allowing, and to whom. If the user accepts, they will be sent back to the `redirect_uri` specified in the params, with `state` and `code` as query parameters. The redirect might look something like this:

   ```
   https://contentplanner.app/oauth_callback?code=h89sf89d8hfsd&state=random-value
   ```

5. So now we need to handle the `oauth_callback` route. The next step in the OAuth flow is to take the "code" here, which is referred to as an "Authorization Code" and exchange this for an actual OAuth Token. Let's write that code (express endpoint as an example):

   ```tsx
   app.get('/oauth_callback', async (req, res) => {
     const qs = new URLSearchParams(req.query)
     const code = qs.get('code')
     const state = qs.get('state')
     const error = qs.get('error')

     // Handle user denial or other errors
     if (error) {
       return res.status(400).send(`OAuth error: ${error}`)
     }

     // we'd want to store the state we sent to the authorize endpoint
     // somewhere so we can compare the two here
     if (state !== originalStateValue) {
       return res.status(400).send('State param mismatch')
     }

     // this is the token endpoint we got from the metadata in step 3
     const response = await fetch(tokenEndpoint, {
       method: 'POST',
       headers: {
         'Content-Type': 'application/x-www-form-urlencoded',
       },
       body: new URLSearchParams({
         client_id: process.env.CLIENT_ID,
         client_secret: process.env.CLIENT_SECRET,
         code,
         grant_type: 'authorization_code',
         redirect_uri: `https://contentplanner.app/oauth_callback`,
       }).toString(),
     }).then((res) => res.json())

     // normally we'd store these somewhere secure for use with
     // making requests to the resource server!
     const accessToken = res.access_token
     const refreshToken = res.refresh_token

     res.json({ success: 'true' })
   })
   ```

   > \[!NOTE]
   > You may be asking - why does this happen in two steps? Why return an "authorization code" and not just send back the OAuth token to save time and resources? We skimmed over this detail in the initial diagram for clarity, but this extra "authorization code for access token exchange" is a required part of the authorization code flow.
   >
   > The answer here is security. For me, understanding 'security' things is easiest by hearing an explanation of how an attacker would be able to exploit this if it were done a different way. So in that spirit, let's imagine that, in the name of efficiency, we decide that we're going to return the OAuth token straight into the redirect URL, and skip the authorization code.
   >
   > In this case, the user is in the browser when they hit the "accept" button on the consent screen, and the authorization service then issues a redirect back to the `redirect_uri`, including the token. The OAuth token now appears fully in the user's browser history, it's visible at least briefly in the URL bar, it's accessible to browser extensions, it shows up in CDN logs, ISP logs, server logs, etc -- these are all problematic as they could leak and make the sensitive OAuth token available to attackers. Additionally, because the Client ID is not a sensitive or secret value and is already exposed in the browser, an attacker with access to it could obtain an OAuth token for your service, bypassing client verification via the client secret.
   >
   > It is generally for this reason that we need both a Client ID and Client Secret - the Client ID can be public without issue, but the Client Secret cannot, which is why we never send it through the browser, and only utilize it when making a direct request from our Client to the Authorization Service, as we can see happening in the code example above.
   >
   > Ensuring that the Token exchange happens as a *server to server* connection makes it more secure, since by avoiding the browser, the sensitive Access Token has fewer places that it appears where it could be extracted by an attacker.

6. Now that we have the OAuth Access Token, we can use it to make a request to the Resource Service. In this example, at the scheduled time, Content Planner could hit Twitter's API in order to send the tweet. As long as we include a valid Access Token with the right scopes, it should work just fine. It might look something like this:

   ```tsx
   await fetch('https://api.twitter.com/tweet', {
     method: 'POST',
     headers: {
       Authorization: 'Bearer <my_access_token>',
     },
     body: JSON.stringify({ text: 'Developers developers developers...' }),
   })
   ```

   Because we have included the Access Token with the request, Twitter will allow Content Planner to post this tweet on our behalf, even though it doesn't have our account sign-in details. Hooray!

Sometimes, it can help to actually write this code to fully lock it in to your brain. If that's the case, I'd encourage you to give it a shot! [Here's the implementation that I wrote](https://github.com/clerk/test-oauth-client) to do this, in case it's helpful at all. It didn't take too long and is very little code.

## Common OAuth questions

Understanding the Authorization Code Flow is a huge step forward in understanding OAuth and how it works overall. But there are also a bunch of other details that I was curious about in my own journey to learn about OAuth, which you might be too, so let's address them.

### What happens if you already finished the flow, but now want to request more scopes?

There's no concept of modifying an existing OAuth Access Token. If you want a token with more scopes, you'd go through the flow again from the start, but request more scopes as parameters to the authorization endpoint. You could then replace the existing Access Token with the new one with more scopes.

### Does the Access Token expire? What happens if/when it does?

Yes, normally, they do, as do most types of access tokens. When you get back an Access Token at the end of the flow, the Authorization Service is expected to also return a *Refresh Token.* It has a much longer expiration than the Access Token and can be used to request a new Access Token. Generally, this works as such:

1. You make a request with an Access Token that has just expired, and get back a 401 response from the Resource Service.
2. You assume this is because the Access Token has expired, and make a request to the "Token Endpoint" (you may have noticed this in the authorization server metadata above), with a valid Refresh Token, and it will return a new Access Token and Refresh Token pair.
3. Now you replay the original request with your new Access Token

If the Refresh Token expires due to the user not using the service for a long time, they will need to re-establish the connection by going through the Authorization Code Flow again.

Here's an example of how a token refresh call might look:

```ts
const refreshResponse = await fetch(tokenEndpoint, {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: currentRefreshToken,
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET, // if confidential client
  }).toString(),
})

// Both tokens are replaced for security
const { access_token, refresh_token } = await refreshResponse.json()
```

### Can tokens be opaque tokens or JWTs? What's the difference?

Opaque tokens are random strings that need to be verified on each request with the authorization server. Because they "phone home" to the authorization server to verify on each request, opaque tokens can be instantly revoked, but are also slower since they add an extra step to each request that includes one. JWTs are digitally signed by the issuer, and can be verified without ever contacting the authorization server by [verifying the signature](/docs/backend-requests/resources/tokens-and-signatures#digital-signatures). However, because of this, they cannot be revoked, and in order to stay secure normally have shorter expiration times. Either token type *can* be used as an OAuth Access Token; which one is preferred is up to the developer. Ory provides a very good overview of the tradeoffs [in their article here](https://www.ory.sh/docs/oauth2-oidc/jwt-access-token).

### What if you want to revoke access?

It's expected that an authorization server has a revocation endpoint, which, if hit with a valid OAuth Access Token, will revoke that token's access and make it useless. This, in combination with clearing out any existing stored Access/Refresh Tokens should be enough to remove an OAuth grant and require a fresh new connection if needed.

## Public clients and PKCE

So far, we've been assuming that Content Planner is running on a server where we can safely store the `client_secret` and use it during the token exchange step. This type of OAuth client is called a **confidential client** because it can keep secrets... well, secret.

But what if you want to build a mobile app or a [single-page web application](https://en.wikipedia.org/wiki/Single-page_application) (SPA) that runs entirely in the browser? In these cases, there's no secure server-side environment to store the `client_secret`. Any secret you embed in a mobile app can be extracted by someone with the right tools, and anything in a browser-based app is visible to browser extensions and anyone who opens the developer tools. These are called **public clients** because they cannot securely store secrets.

This creates a problem with our authorization code flow. Remember in step 5 above, we made a server-to-server request that included the `client_secret` to exchange the authorization code for an Access Token? If we can't safely store a `client_secret`, how do we prove to the authorization service that we're the legitimate client and not an attacker who intercepted someone else's authorization code?

The answer is **PKCE** (Proof Key for Code Exchange, pronounced "pixie"), defined in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). PKCE replaces the `client_secret` with a dynamically generated proof that only the Client who started the OAuth flow can provide.

Here's how it works: instead of relying on a pre-shared secret, the Client generates a random value called a **code verifier** at the start of each OAuth flow, along with a **code challenge** (which is a hashed version of the code verifier). The Client sends the code challenge when starting the authorization flow, then proves it started the flow by providing the original code verifier during the token exchange.

Let's see how this changes our code example. First, we need to generate the PKCE values when creating our authorization URL:

```tsx
// Helper function to generate a random code verifier
function generateCodeVerifier() {
  const array = new Uint8Array(32)
  crypto.getRandomValues(array)
  return btoa(String.fromCharCode(...array))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '')
}

// Helper function to create code challenge from verifier
async function generateCodeChallenge(verifier: string) {
  const encoder = new TextEncoder()
  const data = encoder.encode(verifier)
  const digest = await crypto.subtle.digest('SHA-256', data)
  return btoa(String.fromCharCode(...new Uint8Array(digest)))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '')
}

async function OAuthConnection() {
  const authorizeEndpoint = 'https://auth.twitter.com/oauth/authorize'

  // Generate PKCE values using Web Crypto API
  const codeVerifier = generateCodeVerifier()
  const codeChallenge = await generateCodeChallenge(codeVerifier)

  // Store code verifier in browser storage
  sessionStorage.setItem('pkce_code_verifier', codeVerifier)

  const params = {
    response_type: 'code',
    client_id: 'abc123', // we still need this, but no secret required
    redirect_uri: `https://contentplanner.app/oauth_callback`,
    scope: 'email profile tweet:write tweet_stats:read',
    state: 'random-value',
    // PKCE parameters
    code_challenge: codeChallenge,
    code_challenge_method: 'S256', // indicates we used SHA256 hashing
  }

  const authorizeUrl = `${authorizeEndpoint}?${new URLSearchParams(params).toString()}`

  return (
    <div>
      <p>Connect Content Planner to your X account</p>
      <a href={authorizeUrl}>Connect!</a>
    </div>
  )
}
```

Then, in our callback handler, instead of sending a `client_secret`, we send the `code_verifier`:

```ts
// This would run when the user is redirected back to your SPA
async function handleOAuthCallback() {
  const urlParams = new URLSearchParams(window.location.search)
  const code = urlParams.get('code')
  const state = urlParams.get('state')
  const error = urlParams.get('error')

  if (error) {
    console.error(`OAuth error: ${error}`)
    return
  }

  if (state !== originalStateValue) {
    console.error('State param mismatch')
    return
  }

  // Retrieve the code verifier from browser storage
  const codeVerifier = sessionStorage.getItem('pkce_code_verifier')

  if (!codeVerifier) {
    console.error('No code verifier found')
    return
  }

  const tokenEndpoint = 'https://auth.twitter.com/oauth/token'

  const response = await fetch(tokenEndpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      client_id: 'abc123',
      code,
      grant_type: 'authorization_code',
      redirect_uri: `https://contentplanner.app/oauth_callback`,
      code_verifier: codeVerifier, // PKCE proof instead of secret
    }).toString(),
  }).then((res) => res.json())

  const accessToken = response.access_token
  const refreshToken = response.refresh_token

  // Clean up the stored code verifier
  sessionStorage.removeItem('pkce_code_verifier')

  // Store tokens securely in your SPA (consider using secure storage)
  sessionStorage.setItem('access_token', accessToken)
  sessionStorage.setItem('refresh_token', refreshToken)

  console.log('OAuth flow completed successfully!')
}
```

The security here works because an attacker who intercepts the authorization code still can't use it — they would need the original `code_verifier` to complete the token exchange, and only the client that started the flow has that value.

> \[!NOTE]
> You might wonder: if the `code_verifier` is stored in the browser (like in sessionStorage), couldn't an attacker just steal that instead?
>
> The key difference is *timing* and *access*. The `code_verifier` only exists for the brief duration of the OAuth flow and is likely only accessible to the specific application that created it. A `client_secret`, on the other hand, would be permanently embedded in the app code where it could be extracted by anyone. Additionally, each PKCE flow uses a unique `code_verifier`, so even if one was somehow compromised, it couldn't be reused for other users or future flows.
>
> While these restrictions make it more difficult for an attacker to compromise the OAuth flow, it's not impossible. For this reason, public clients are inherently less secure than confidential clients, and this should be acknowledged when making decisions about which type of client to use.

PKCE is becoming mandatory for many OAuth providers, even for confidential clients, because it provides an additional layer of security. It's considered a best practice to use PKCE for all OAuth flows when possible, regardless of whether your client can store secrets or not. In fact, the most up to date OAuth spec, [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13), requires that PKCE be used for all authorization code flows.

## Dynamic Client Registration

We briefly mentioned above the concept that it was possible to register an OAuth Client with your Authorization Service via API, rather than manually through their UI. This is not required, and many Authorization Services do not implement this capability, but it is possible, and is referred to as *dynamic client registration*, as defined by [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591).

If an Authorization Service implements Dynamic Client Registration, this means that anyone can register an OAuth client through a public API endpoint. There are a variety of scenarios where this might be needed, largely centered around when many different OAuth clients need to be created in a self-serve manner.

However, it does come with some substantial security risks:

- Dynamic client registration creates an unauthenticated, public endpoint that is accessible to anyone on the internet. This means there is no way to trace or verify the identity of who creates OAuth clients, allowing attackers to create Clients anonymously without leaving any audit trail. This lack of accountability makes it extremely difficult to track malicious activity back to its source.
- Attackers can exploit this open access to create thousands of OAuth Clients over time. Even with rate limiting measures in place, determined attackers can slowly build up large numbers of Clients by spreading their registration attempts across extended periods. This proliferation makes legitimate Clients increasingly difficult to identify among potentially fraudulent ones, especially since Client names and metadata can be set to anything, making proper identification significantly harder for administrators.
- The ability to freely register Clients opens the door to sophisticated social engineering attacks. Attackers can create Clients with legitimate-sounding names and descriptions, potentially tricking users into authorizing malicious applications that appear trustworthy. These deceptive Clients can request broad scopes while maintaining their facade of legitimacy, making it extremely difficult for both users and administrators to distinguish legitimate Clients from malicious imposters attempting to harvest user data or gain unauthorized access.
- Implementing dynamic client registration increases the administrative burden required for monitoring and cleanup activities. This additional complexity makes security audits and compliance reviews more challenging, as administrators must sift through potentially thousands of dynamically created clients. The system requires ongoing vigilance to identify and remove malicious Clients, creating a operational overhead that many organizations may not be prepared to handle effectively.

That being said, as long as you have evaluated and accepted the risks, dynamic client registration can be an essential feature in some scenarios.

## OAuth & OIDC

There's another standard that is built on top of and often used with OAuth called ["OpenID Connect"](https://openid.net/developers/how-connect-works/) (OIDC). This standard is written and maintained by a different standards committee, the [OpenID Foundation](https://openid.net/foundation/).

The key addition to OAuth is that when you return the access and refresh tokens, so long as an `openid` scope is included, you should also return another property called `id_token`, which is a JWT that has some basic user information like name, email, profile photo, etc. This helps the *Client* to know who the user is that just went through the flow. Without OIDC, if the *Client* needed to get any details about the user, it would need to use the Access Token to make another request to some API endpoint that returns user info (OIDC also standardizes a ["user info" endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) for this purpose). With OIDC, this step can be skipped since the user info is returned as part of the initial OAuth response.

There's more in the OIDC specification that expands out more details on session management if you're using OAuth for user login, but we won't address those in this piece, since it's focused on OAuth scoped access.

## Using OAuth with Clerk

Now that you understand how OAuth scoped access works under the hood, you might be ready to make this happen for one of your applications. If you're using Clerk, we have a robust OAuth authorization server built in for you automatically, that's ready to use.

You can navigate to ["Config > OAuth applications" in the Clerk dashboard](https://dashboard.clerk.com/last-active?path=oauth-applications) to register an OAuth application. This gives you back the `client_id` and `client_secret` we discussed, along with all the proper endpoint configurations. For scenarios where you need the dynamic client registration we discussed earlier (like multi-tenant SaaS platforms or developer marketplaces), Clerk also supports this - though it's optional and needs to be explicitly enabled due to the security considerations we covered.

And remember all those security considerations we covered - CSRF protection via the state parameter, PKCE for public clients, secure token storage? Clerk supports all of these automatically, with the most security-conscious implementation out of the box.

If you are looking to build out an OAuth feature for your Clerk application, check out [our step-by-step guide on how to do so](/docs/oauth/oauth-scoped-access). We also have [a minimal demo OAuth client](https://github.com/clerk/test-oauth-client) that can be used to quickly test against Clerk's built-in authorization server.

---

# Synchronize user data from Clerk to Supabase
URL: https://clerk.com/blog/sync-clerk-user-data-to-supabase.md
Date: 2025-06-06
Category: Guides
Description: Learn how to synchronize user data from Clerk to Supabase with webhooks and Supabase Functions

Most web applications aren’t built for just one user.

Whether you’re building admin dashboards, team-based tools, or collaborative writing platforms, you’ll eventually need access to other users in your system.

When you’re using Clerk, there are a few ways to make that happen—each with its own tradeoffs around performance, accuracy, and infrastructure. In this article, you’ll learn how to access user information using Clerk’s **Backend API**, and how to **sync Clerk users to your Supabase database** using Webhooks and Supabase Functions.

> \[!NOTE]
> This article will focus on Next.js and Supabase. We also have [another guide](/blog/read-user-data-guide) on our blog that is a more framework-agnostic method of accessing other user’s data.

## Accessing Users with the Backend API

Clerk’s **Backend API (BAPI)** allows you to securely retrieve data about the data stored within your Clerk application using your own server environment. This includes data about other users, whether that is individual users or members of an organization.

Since Clerk is the source of truth when it comes to information about users in your system, the biggest benefit of accessing user data through the Clerk Backend API is that it will always retrieve the most up-to-date version of that information. When a user updates their record (or an admin updates it for them), the API will reflect those changes immediately, ensuring that your application always has access to the latest profile, metadata, and status values without relying on a separate syncing process.

The following code snippet demonstrates how to call our Backend API with JavaScript to retrieve details about a user:

```tsx
const userId = 'user_123'
const response = await clerkClient.users.getUser(userId)
```

Requests can also be made directly to the API as shown [in our docs](/docs/reference/backend-api).

As with any external API, BAPI usage is subject to rate limits. To prevent hitting those limits, it’s a good idea to add a caching layer, either on the server or in the client.

## Syncing User Data with Webhooks and Supabase Functions

Another approach is to proactively sync user data to your database by using Clerk **Webhooks**.

Once configured, Clerk can send data to your application any time specific events occur—like a user being created or updated. Each time an event is triggered, Clerk will send an HTTP POST request to a defined endpoint, containing the relevant payload.

The following sample code demonstrates the payload that is sent along with a `user.updated` event that is triggered whenever a user record is modified:

```json
{
  "data": {
    "birthday": "",
    "created_at": 1654012591514,
    "email_addresses": [
      {
        "email_address": "example@example.org",
        "id": "idn_29w83yL7CwVlJXylYLxcslromF1",
        "linked_to": [],
        "object": "email_address",
        "reserved": true,
        "verification": {
          "attempts": null,
          "expire_at": null,
          "status": "verified",
          "strategy": "admin"
        }
      }
    ],
    "external_accounts": [],
    "external_id": null,
    "first_name": "Example",
    "gender": "",
    "id": "user_29w83sxmDNGwOuEthce5gg56FcC",
    "image_url": "https://img.clerk.com/xxxxxx",
    "last_name": null,
    "last_sign_in_at": null,
    "object": "user",
    "password_enabled": true,
    "phone_numbers": [],
    "primary_email_address_id": "idn_29w83yL7CwVlJXylYLxcslromF1",
    "primary_phone_number_id": null,
    "primary_web3_wallet_id": null,
    "private_metadata": {},
    "profile_image_url": "https://www.gravatar.com/avatar?d=mp",
    "public_metadata": {},
    "two_factor_enabled": false,
    "unsafe_metadata": {},
    "updated_at": 1654012824306,
    "username": null,
    "web3_wallets": []
  },
  "event_attributes": {
    "http_request": {
      "client_ip": "0.0.0.0",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
    }
  },
  "object": "event",
  "timestamp": 1654012824306,
  "type": "user.updated"
}
```

On the Supabase side, you can create a **Supabase Function** to receive the webhook payload and write the data directly to your database. This gives you local access to user data, which is helpful for scenarios like:

- Joining on user metadata in custom queries
- Performing analytics or reporting
- Enforcing RLS (Row-Level Security) policies that rely on user attributes

The main benefit of this approach is that your application no longer has to reach out to Clerk in real time—user data is already where you need it. The tradeoff is that syncing is asynchronous, meaning there may be a slight delay between when an event is triggered and when the data becomes available. It also introduces more infrastructure and code you’ll need to manage.

## Practical Example

Let’s take a look at how to implement both of these strategies using a real-world project. **Quillmate** is an open-source writing platform built with Next.js and Supabase.

> \[!NOTE]
> You can clone the repository from [it’s home on GitHub](https://github.com/bmorrisondev/quillmate).

To follow along, make sure you have Docker Desktop installed, which is used to build and deploy the function to Supabase.

### Create and configure the Supabase Edge Function

Start by creating the function by running the following command from the root of the project:

```bash
pnpx supabase functions new clerk-webhooks
```

This should create an empty function located at `supabase/functions/clerk-webhooks`. In your terminal, navigate to that directory and run the following command to add the `@clerk/backend` library to the function:

```bash
pnpx deno add npm:@clerk/backend
```

Now open the `index.ts` file from that directory and replace all of the contents with the following code that automatically adds and updates records for users, organizations, and memberships:

```tsx {{ filename: 'supabase/functions/clerk-webhooks/index.ts' }}
import { createClient } from 'npm:@supabase/supabase-js'
import { verifyWebhook } from 'npm:@clerk/backend/webhooks'

Deno.serve(async (req) => {
  // Verify webhook signature
  const webhookSecret = Deno.env.get('CLERK_WEBHOOK_SECRET')

  if (!webhookSecret) {
    return new Response('Webhook secret not configured', { status: 500 })
  }

  const event = await verifyWebhook(req, { signingSecret: webhookSecret })

  // Create supabase client
  const supabaseUrl = Deno.env.get('SUPABASE_URL')
  const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
  if (!supabaseUrl || !supabaseServiceKey) {
    return new Response('Supabase credentials not configured', { status: 500 })
  }
  const supabase = createClient(supabaseUrl, supabaseServiceKey)

  switch (event.type) {
    case 'user.created': {
      // Handle user creation
      const { data: user, error } = await supabase
        .from('users')
        .insert([
          {
            id: event.data.id,
            first_name: event.data.first_name,
            last_name: event.data.last_name,
            avatar_url: event.data.image_url,
            created_at: new Date(event.data.created_at).toISOString(),
            updated_at: new Date(event.data.updated_at).toISOString(),
          },
        ])
        .select()
        .single()

      if (error) {
        console.error('Error creating user:', error)
        return new Response(JSON.stringify({ error: error.message }), { status: 500 })
      }

      return new Response(JSON.stringify({ user }), { status: 200 })
    }

    case 'user.updated': {
      // Handle user update
      const { data: user, error } = await supabase
        .from('users')
        .update({
          first_name: event.data.first_name,
          last_name: event.data.last_name,
          avatar_url: event.data.image_url,
          updated_at: new Date(event.data.updated_at).toISOString(),
        })
        .eq('id', event.data.id)
        .select()
        .single()

      if (error) {
        console.error('Error updating user:', error)
        return new Response(JSON.stringify({ error: error.message }), { status: 500 })
      }

      return new Response(JSON.stringify({ user }), { status: 200 })
    }

    case 'organization.created': {
      // Handle organization creation
      const { data, error } = await supabase
        .from('organizations')
        .insert([
          {
            id: event.data.id,
            name: event.data.name,
            created_at: new Date(event.data.created_at).toISOString(),
            updated_at: new Date(event.data.updated_at).toISOString(),
          },
        ])
        .select()
        .single()

      if (error) {
        console.error('Error updating owner:', error)
        return new Response(JSON.stringify({ error: error.message }), { status: 500 })
      }

      return new Response(JSON.stringify({ data }), { status: 200 })
    }

    case 'organization.updated': {
      const { data, error } = await supabase
        .from('organizations')
        .update({
          name: event.data.name,
          updated_at: new Date(event.data.updated_at).toISOString(),
        })
        .eq('id', event.data.id)
        .select()
        .single()

      if (error) {
        console.error('Error updating owner:', error)
        return new Response(JSON.stringify({ error: error.message }), { status: 500 })
      }

      return new Response(JSON.stringify({ data }), { status: 200 })
    }

    case 'organizationMembership.created': {
      const { data, error } = await supabase
        .from('members')
        .insert([
          {
            id: event.data.id,
            user_id: event.data.public_user_data?.user_id,
            organization_id: event.data.organization?.id,
            created_at: new Date(event.data.created_at).toISOString(),
            updated_at: new Date(event.data.updated_at).toISOString(),
          },
        ])
        .select()
        .single()

      if (error) {
        console.error('Error updating member:', error)
        return new Response(JSON.stringify({ error: error.message }), { status: 500 })
      }

      return new Response(JSON.stringify({ data }), { status: 200 })
    }

    case 'organizationMembership.updated': {
      const { data, error } = await supabase
        .from('members')
        .update({
          user_id: event.data.public_user_data?.user_id,
          organization_id: event.data.organization?.id,
          updated_at: new Date(event.data.updated_at).toISOString(),
        })
        .eq('id', event.data.id)
        .select()
        .single()

      if (error) {
        console.error('Error updating member:', error)
        return new Response(JSON.stringify({ error: error.message }), { status: 500 })
      }

      return new Response(JSON.stringify({ data }), { status: 200 })
    }

    default: {
      // Unhandled event type
      console.log('Unhandled event type:', JSON.stringify(event, null, 2))
      return new Response(JSON.stringify({ success: true }), { status: 200 })
    }
  }
})
```

Finally, deploy the new function with the following command:

```bash
pnpx supabase functions deploy
```

Now open the Supabase dashboard for your project and navigate to **Edge Functions** from the left navigation. Copy the URL of the function.

![The Supabase Edge Functions page with the URL of the function highlighted](./image1.png)

Next, click the name of the function to open it, select the **Details** tab, disable **Enforce JWT Verification**, then **Save changes.** This functionality will interfere with verifying the payload using the Clerk Webhook Secret, which will be configured in the next section.

![Disable JWT verification in the Supabase Edge Functions Details page](./image2.png)

### Configure the Clerk Webhook

Next, head over to the Clerk dashboard for your application and navigate to **Configure > Webhooks**. Click **Add Endpoint**.

![The Clerk Webhooks page with the Add Endpoint button highlighted](./image3.png)

In the form that appears, paste in the URL of the Supabase Edge Function from the previous steps into the **Endpoint URL** field, enable the `user.created` event, and click **Create** at the bottom of the form.

![The Clerk Webhooks page with the Endpoint URL field highlighted](./image4.png)

The configuration for the created webhook endpoint will automatically open up. Click the eye icon near the **Signing Secret** to reveal the secret and copy the value.

![The Clerk Webhooks page with the Signing Secret field highlighted](./image5.png)

Now head back the Supabase dashboard and navigate to **Edge Functions** > **Secrets**. Add a new secret named `CLERK_WEBHOOK_SECRET` and paste in the value you copied from the Clerk dashboard.

![The Supabase Edge Functions Secrets page with the CLERK\_WEBHOOK\_SECRET secret being added](./image6.png)

That’s it! From now on, any new user created in this Clerk application will automatically be synchronized over to the Supabase database. You can actually test it from the Clerk dashboard by opening the webhook, accessing the Testing tab, selecting the `user.created` event, and clicking **Send Example**.

For other events, you’d simply enable the desired events and update the switch statement to parse the data for those events.

## Conclusion

If you need to sync user data from Clerk to Supabase, Edge Functions are a great match to Clerk’s webhooks. When properly configured, you can build the necessary logic to conditionally process incoming event data into any table in your Supabase database and make user data available to join on for analytics, reporting, and other necessary workloads.

---

# Add subscriptions to your SaaS with Clerk Billing
URL: https://clerk.com/blog/add-subscriptions-to-your-saas-with-clerk-billing.md
Date: 2025-05-20
Category: Company
Description: Learn how to quickly monetize your SaaS with subscriptions powered by Clerk Billing.

Monetizing your application is often the next logical step after building something users love.

Subscription plans are a common strategy to build sustainable revenue into your SaaS, enabling premium features for individual users or teams with active plans. However, implementing subscriptions from scratch can be time-consuming and error-prone due to the complexity of the underlying infrastructure and logic.

**That's why we built Billing**.

Just as Clerk streamlines authentication and user management, it now does the same for subscriptions. You get a polished UI that allows your users to easily select and manage their preferred plan, as well as helper functions to easily gate access to premium features, all without writing custom billing logic from scratch.

In this article, you'll learn what Clerk Billing is, how it works, and how to implement it in a real-world application.

## What Is Clerk Billing?

[**Clerk Billing**](/billing) is our latest offering that brings subscription management directly into your existing authentication stack. With just a few clicks in the [Dashboard](https://dashboard.clerk.com/), you can define subscription plans and their associated features, which are displayed in your application with the drop-in [`<PricingTable />`](/docs/components/pricing-table) component.

Clerk integrates directly with your Stripe account, letting Stripe handle the actual payment processing while Clerk handles the user interface and entitlement logic. During development, you can even work in a sandbox environment without requiring a Stripe account. This mirrors the way Clerk handles SSO, where development instances use shared credentials until you're ready to go live.

## How Billing Works

You'll start in the [Clerk dashboard](https://dashboard.clerk.com/), where you define your **plans** and add **features** to them.

**Features** are essentially flags that indicate what a user has access to. For example, if your “Pro” plan includes advanced analytics, you'd create an “Analytics” feature and assign it to the plan. Features can be shared across multiple plans, allowing you to build a pricing structure thats increases access to your application as the selected plan increases.

You can configure your plans to be billed monthly or offer discounts for annual subscribers. Once your plans are created, you're ready to display them in your app.

### The `<PricingTable />` component

The core UI component used with Billing is the [`<PricingTable />`](/docs/components/pricing-table). It's a single-line component that renders a fully functional plan selector and payment form inside your app.

![The PricingTable component](./pricingtable.png)

When a user selects a plan, a modal drawer will open to collect their payment details. It's a smooth and familiar experience for users and requires no custom form building on your part.

Users can also subscribe to new plans and manage existing subscriptions directly through the [`<UserProfile />`](/docs/components/user/user-profile) component. The new Billing tab also includes invoice history and linked payment sources. This centralizes authentication, profile management, and billing into one cohesive experience.

### Verifying the User's access

One of the most powerful features in Clerk is the [`has()`](/docs/references/backend/types/auth-object#has) helper. Originally built to power B2B access controls, it checks whether a user has a specific role or permission. With Billing, it now supports checking a user's plan or feature (entitlement) access.

```tsx
const { has } = await auth()

const hasPremiumPlan = has({ plan: 'gold' })
const hasWidgets = has({ feature: 'widgets' })
```

This makes it incredibly easy to gate access to premium content or features with a single, readable function call.

### Managing Subscriptions

Once users are subscribed, you can manage their subscriptions directly from the Clerk dashboard. There's a new [**Subscriptions**](https://dashboard.clerk.com/last-active?path=billing/plans) tab where you can search for users, view their subscription status, and even cancel plans if needed.

Cancelled plans won't immediately remove access, they'll simply stop renewing, giving your users access until the current billing cycle ends. You can also view a user's plan details at a glance, which is especially useful for support and admin workflows.

## Implementing Billing in Quillmate

To demonstrate how easy it is to implement Billing into an application, I'm going to add subscriptions to **Quillmate**, a web-based writing platform built with Next.js, Clerk, and Supabase. The Pro plan for Quillmate offers an AI assistant that users can access while writing new articles. If the user is not a subscriber, they will be prompted to subscribe when they attempt to access the chat assistant.

> \[!NOTE]
> You can access the completed version of the project on [GitHub](https://github.com/bmorrisondev/quillmate/tree/billing).

### Creating the plans

I'll start in the [Clerk dashboard](https://dashboard.clerk.com/) and navigate to **Configure > Subscription Plans**, then click **Add User Plan**.

![The Subscription Plans configurate page with a red arrow pointing to the "Add User Plan" button](./1-creating_subscription_plans.png)

In the next screen I'll name the plan (which will auto-populate the slug), add a description, and set the monthly price.

![The plan configuration page](./2-naming-plans.png)

Before saving I'll scroll down a bit to create the feature that's associated with the plan by click **Add Feature**. I'll name the feature “AI Assistant” and provide a description before saving. Take note of the slug as it will be used in the code to check if the user can access this feature.

![The plan configuration page with a red arrow pointing to the "Add Feature" button](./3-add_feature.png)

### Add the pricing table

Now that my plan and feature are created, I can start updating the code. The first thing I'm going to do is create a new page that shows the available subscriptions. This will be a page that users can access at `/subscribe`. The page itself contains some promo text, but the main thing to note is the [`<PricingTable />`](/docs/components/pricing-table) component which is all I need to render the available plans and features:

```tsx {{ filename: 'src/app/subscribe/page.tsx', ins: [1, 47] }}
import { PricingTable } from '@clerk/nextjs'
import React from 'react'
import Link from 'next/link'

function SubscribePage() {
  return (
    <>
      {/* Navigation Bar */}
      <nav className="fixed top-0 left-0 z-20 flex w-full items-center justify-between border-b border-gray-200 bg-white/70 px-4 py-3 backdrop-blur-md dark:border-gray-800 dark:bg-gray-950/70">
        <Link
          href="/"
          className="bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-lg font-semibold text-transparent transition-opacity hover:opacity-80"
        >
          ← Back to Home
        </Link>
      </nav>
      {/* Main Content */}
      <div className="flex min-h-svh items-center justify-center bg-gradient-to-b from-white to-gray-50 pt-20 dark:from-gray-950 dark:to-gray-900">
        <div className="flex w-full max-w-2xl flex-col items-center gap-8 p-8 text-center">
          <h1 className="bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-4xl font-bold text-transparent">
            Unlock AI Superpowers
          </h1>
          <p className="text-xl text-gray-600 dark:text-gray-300">
            Become a member to access exclusive AI features, priority support, and early access to
            new tools. Join our growing community and take your productivity to the next level!
          </p>
          <ul className="mx-auto mb-4 w-full max-w-md text-left text-base text-gray-700 dark:text-gray-200">
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Unlimited AI queries and content generation</span>
            </li>
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Early access to new AI-powered features</span>
            </li>
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Priority email & chat support</span>
            </li>
            <li className="flex items-center gap-2">
              ✅ <span>Member-only resources and tutorials</span>
            </li>
          </ul>
          <div className="mb-4">
            <span className="text-lg font-semibold text-blue-600 dark:text-blue-400">
              Ready to get started? Choose your plan below:
            </span>
          </div>
          <div className="flex w-full justify-center">
            <PricingTable />
          </div>
        </div>
      </div>
    </>
  )
}

export default SubscribePage
```

This page shows a list of available plans and features for Quillmate users:

![The Quillmate subscribe page](./4-quillmate_plans.png)

Selecting **Get Started** under the plan will simply open a drawer where I can enter my payment information to be processed by Stripe!

![The subscribe page with the checkout drawer open](./5-checkout.png)

### Protecting the AI Chat feature

For each article, Quillmate has a floating action button in the lower right that users can click to access the assistant. This feature should only be available to users who subscribe to the Pro plan, or more specifically, a plan with the “AI Assistant” feature.

The code for the floating action button has a simple check that uses the `has` helper from the Clerk SDK to check if the current user has a plan that includes the `ai_assistant` feature, which is the slug of the feature created earlier in this guide:

```tsx {{ filename: 'app/(protected)/components/ChatFAB.tsx', ins: [14, 17] }}
'use client'
import { useState } from 'react'
import { ChatBubbleOvalLeftEllipsisIcon } from '@heroicons/react/24/outline'
import { useAuth } from '@clerk/nextjs'
import SubscriptionModal from './SubscriptionModal'
import ChatInterface from './ChatInterface'

interface ChatFABProps {
  articleId: string
  articleContent: string
}

export default function ChatFAB({ articleId, articleContent }: ChatFABProps) {
  const { has } = useAuth()
  const [open, setOpen] = useState(false)

  const canUseAi = has?.({ feature: 'ai_assistant' })

  return (
    <>
      {/* FAB */}
      <button
        onClick={() => setOpen(true)}
        className="fixed right-4 bottom-12 z-50 rounded-full bg-blue-600 p-4 text-white shadow-lg hover:bg-blue-700 focus:outline-none"
        style={{ display: open ? 'none' : 'block' }}
        aria-label="Open Chat"
      >
        <ChatBubbleOvalLeftEllipsisIcon className="h-7 w-7" />
      </button>

      {/* Subscription Modal */}
      {open && !canUseAi && <SubscriptionModal onClose={() => setOpen(false)} />}

      {/* Chatbox */}
      {open && canUseAi && (
        <ChatInterface
          onClose={() => setOpen(false)}
          articleId={articleId}
          articleContent={articleContent}
        />
      )}
    </>
  )
}
```

As a best practice, we also want to protect any backend API calls that are used by the chat feature. The `has` function can also be used on server-side code as well:

```ts {{ filename: 'src/app/api/chat/route.ts', ins: [3, 7, 10] }}
import { CoreMessage, generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export async function POST(req: Request) {
  const { has } = await auth()

  if (!has({ feature: 'ai_assistant' })) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
  }

  const { messages }: { messages: CoreMessage[] } = await req.json()

  const { response } = await generateText({
    model: openai('gpt-4'),
    system: 'You are a helpful assistant. Format all responses as markdown.',
    messages,
  })

  return NextResponse.json({ messages: response.messages })
}
```

When a user is subscribed, they are then able to access the AI chat:

Users can also manage their plan directly from the [`<UserButton />`](/docs/components/user/user-button) component in the new Billing tab:

![The UserButton component with the Billing tab open](./6-user_button.png)

## Conclusion

[Clerk Billing](/billing) takes all the usual friction out of implementing subscriptions—no need to wire up your own Stripe forms, manage customer data, or create custom logic for checking user plans. It's fully integrated into your authentication layer, shares the same DX principles as the rest of Clerk, and gets you from “idea” to “monetized” in record time.

Whether you're just validating a pricing model or launching a full-featured SaaS product, Clerk Billing is built to get you there faster, with fewer moving parts.

---

# Getting started with Clerk Billing
URL: https://clerk.com/blog/intro-to-clerk-billing.md
Date: 2025-05-14
Category: Company
Description: Learn how to build a complete billing experience with Clerk and Stripe, from subscriptions and usage-based pricing to role-based access—no custom UI or webhooks required.

In this episode of Stripe Developer Office Hours, Clerk CTO and co-founder Braden Sidoti shares how you can build a complete billing experience—without webhooks, custom UIs, or Stripe session management. Instead of abstracting Stripe Billing, Clerk connects directly to your Stripe account: Stripe handles payments, and Clerk takes care of the user interface, entitlement logic, and session-aware billing flows.

You'll learn how to set up subscriptions, usage-based pricing, and org-level billing using Clerk's prebuilt components and APIs. Braden also walks through how Clerk supports role-based access, secure upgrades, and customer self-service, all tightly integrated with your existing auth layer.

The conversation also touches on why Clerk approaches infrastructure this way, how to go from prototype to production without glue code, and how tying billing to identity can simplify everything from user onboarding to plan upgrades. If you're looking to ship payments faster and with less complexity, this is a blueprint worth exploring.

### Try Clerk Billing Today

Clerk Billing works in every [country supported by Stripe](https://stripe.com/global) and syncs directly with your existing Clerk application.

---

# Multi-tenant analytics with Tinybird and Clerk
URL: https://clerk.com/blog/tinybird-and-clerk.md
Date: 2025-05-02
Category: Company
Description: How to use Clerk's Tinybird JWT template to secure Tinybird APIs for fast, easy, and secure user-facing analytics in your multi-tenant application.

When you run analytics for internal use, you often don't think much about role-based access control or multi-tenancy. You just connect a BI tool to your database or data warehouse and start running some queries.

But when you're serving analytics to your end users in your product or application, then you have to think about multi-tenancy, rate limiting, and access control down to the database level.

In traditional databases, this can be pretty challenging. It's why products like Clerk are popular; they abstract the complexities of auth and access control, typically to the transactional database that stores information about users, their IDs, and their metadata.

Adding real-time, user-facing analytics to the mix presents some additional challenges. Using Clerk JWT templates and Tinybird real-time analytics APIs with row-level security policies addresses those complexities.

Here's what you'll learn in this post:

1. How Tinybird APIs are secured using static tokens or JWTs
2. How to use the Clerk JWT template for Tinybird
3. How to modify Tinybird API definitions to support Clerk JWTs
4. How to create a React context provider for auth to Tinybird APIs
5. How to update Clerk Middleware to set the token

> \[!NOTE]
> Everything covered below can be gleaned from the [open source Tinybird Clerk JWT template](https://www.tinybird.co/templates/clerk-jwt).

## Getting familiar with Tinybird

[Tinybird](https://www.tinybird.co/) is an analytics backend for your application. You use Tinybird to build [real-time data APIs](https://www.tinybird.co/docs/publish/api-endpoints) over large amounts of data such as logs, event streams, or other time series data. Tinybird gives you the tooling and infrastructure to store, query, and serve analytics and metrics to end users of your application without having to fuss with the complexities of a real-time analytics database.

And when it comes to authentication and multi-tenancy, Tinybird offers some nice perks: Every API you build with Tinybird can be secured by either static tokens or [JSON Web Tokens (JWTs)](https://www.tinybird.co/docs/forward/get-started/administration/auth-tokens#json-web-tokens-jwts). Within those tokens, you can define security policies that limit access based on user metadata.

For example, here are three Tinybird JWTs with claims that limit access at the user, team, or organization level. You'll notice that these look almost identical, but with the `fixed_params` object modified to support the security policy we want to implement.

**User level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "user_123_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "user_id": "user123"
      }
    }
  ]
}
```

**Team level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "team_abc_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "team_id": "team_abc"
      }
    }
  ]
}
```

**Organization level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "org_acme_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "organization_id": "org_acme"
      }
    }
  ]
}
```

Tinybird APIs are defined using SQL queries (called "pipes" in Tinybird parlance), extended with a [Jinja](https://jinja.palletsprojects.com/en/stable/)-like templating functions to add [advanced logic](https://www.tinybird.co/docs/cli/advanced-templates) or [query parameters](https://www.tinybird.co/docs/forward/work-with-data/query-parameters).

For example, consider the pipe called `my_api_endpoint` referenced in the above JWTs, which might look this:

```sql
SELECT
    toStartOfDay(timestamp) AS day,
    sum(some_number) AS total
FROM app_events
WHERE 1
{% if defined(user_id) %}
    AND user_id = {{String(user_id)}}
{% elif defined(team_id) %}
    AND team_id = {{String(team_id)}}
{% else %}
    AND organization_id = {{String(organization_id)}}
{% end %}
```

This pipe uses Tinybird's templating language to define three query parameters: `user_id`, `team_id`, and `organization_id` for the API endpoint. When supplied in the request, those parameters will trigger the query to filter.

For example, the following request would trigger a query against the database filtering only by events belonging to `user123` and return the response:

```bash
curl -d https://api.tinybird.co/v0/pipes/my_api_endpoint?user_id=user123&token=<static_token>
```

Of course, this is not a particularly secure implementation. We're passing both the `user_id` and the static token in the URL. If we were making a request directly from the browser, this would be insecure; the token would be compromised, and the `user_id` could easily be modified to access another user's data.

JWTs solve this for us. They're not stored server-side, so they're less likely to leak. They're validated by the backend service using a secret key and contain an embedded expiration time. The JWT contains data about the requesting party and is passed to the server in the request headers. Nothing gets exposed, the data returned is properly scoped, everybody wins.

Let's see how to use Clerk's JWT templates to secure a Tinybird API.

## Using Clerk JWTs to secure Tinybird endpoints

Everything I share below references the [Tinybird Clerk JWT template](https://www.tinybird.co/templates/clerk-jwt), which includes an open-source code example, video tutorial, and live demo. Feel free to go straight there and follow the guide, or follow along here.

### Setting up the Clerk JWT template

Go to the Clerk dashboard, and select **Configure** > **JWT Template**. Select the **Tinybird JWT template**.

![The Create Jwt Template modal in the Clerk dashboard](./image1.png)

Tinybird JWTs must be signed using the admin token for the workspace where the Tinybird resources are hosted. In Clerk, make sure to enable **Custom signing key** with **HS256** signing algorithm, and paste in the Workspace admin token:

![A screenshot of the Tinybird Clerk JWT template in the Clerk dashboard](./image2.png)

You can also set an optional token lifetime. Tinybird's API endpoints will return a 403 if requested with an expired token.

Modify the claims as needed for your application:

```json
{
  "name": "frontend_jwt",
  "scopes": [
    {
      "type": "PIPES:READ",
      "resource": "<YOUR-TINYBIRD-PIPE-NAME>",
      "fixed_params": {
        "organization_id": "{{org.slug}}",
        "user_id": "{{user.id}}"
      }
    }
  ],
  "workspace_id": "<YOUR-TINYBIRD-WORKSPACE-ID>"
}
```

The example above uses the Clerk shortcodes `org.slug` and `user.id` to reference the unique identifiers for the organization and user stored in Clerk. These get passed to the Tinybird resources secured by the JWT.

### Bonus: Rate limiting

Rate limiting can be an important part of multi-tenant architectures to prevent one or a few users from monopolizing resources. Tinybird JWTs support rate limiting through a `limits` claim:

```json {{ prettier: false }}
{
    "limits": {
        "rps": 10
    },
    …
}
```

When you specify a `limits.rps` field in the payload of the JWT, Tinybird uses the name specified in the payload of the JWT to track the number of requests being made. If the number of requests per second goes beyond the limit, Tinybird starts rejecting new requests and returns an "HTTP 429 Too Many Requests" error.

### Configuring your Tinybird APIs

The only thing you need to do is double-check (or update) your Tinybird APIs and ensure logic exists to filter on the passed parameters. This logic is customizable so you can handle requests in a way that makes sense for your use case. In a multi-tenant architecture, it often makes sense to filter by default at the organization level, making an `organization_id` required in the endpoint, then optionally adding user-level filtering for resources where a user might need to see their specific data:

```sql
SELECT * FROM table
WHERE organization_id = {{String(organization_id, required=True)}}
{% if defined(user_id) %}
    AND user_id = {{UUID(user_id)}}
{% end %}
```

## Configuring a frontend-only app for multi-tenant analytics

Once you've created the JWT template in Clerk and implemented the filtering logic in your Tinybird APIs, it's relatively simple to implement the logic in your application. The example below is for a TypeScript/Next.js app, but can easily be extended to any other language or framework.

### Basic project structure

The core components of this implementation in Next.js are:

- `TinybirdProvider.tsx` - A React context provider that manages the Tinybird JWT token
- `page.tsx` - The main page component that demonstrates token usage

### TinybirdProvider component

The `TinybirdProvider` component is a React context provider that manages the authentication token needed to access Tinybird's API endpoints. This provider automatically fetches and stores the user's token (provided by Clerk on sign-in) in its state and makes it available to any component in the app through the `useTinybirdToken` hook (by using a hook, we can avoid prop drilling across components).

```tsx {{ filename: 'src/app/providers/TinybirdProvider.tsx' }}
'use client'

import { useSession } from '@clerk/nextjs'
import { createContext, useContext, useState, ReactNode, useEffect } from 'react'

interface TinybirdContextType {
  token: string
  setToken: (token: string) => void
}

const TinybirdContext = createContext<TinybirdContextType | undefined>(undefined)

export function TinybirdProvider({ children }: { children: ReactNode }) {
  const [token, setToken] = useState('')
  const { session } = useSession()

  useEffect(() => {
    if (!session) return

    async function populateToken() {
      const token = await session?.getToken({ template: 'tinybird' })
      if (!token) return
      setToken(token)
    }

    populateToken()
  }, [session])

  return <TinybirdContext.Provider value={{ token, setToken }}>{children}</TinybirdContext.Provider>
}

export function useTinybirdToken() {
  const context = useContext(TinybirdContext)
  if (context === undefined) {
    throw new Error('useTinybirdToken must be used within a TinybirdProvider')
  }
  return context
}
```

The provider automatically fetches the token when a user signs in and updates it when the session changes.

### Using the token in your application

To use the Tinybird token in your application, simply wrap your app with the `TinybirdProvider` and use the `useTinybirdToken` hook where needed:

```tsx {{ filename: 'src/app/layout.tsx' }}
import { TinybirdProvider } from './providers/TinybirdProvider'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ClerkProvider>
          <TinybirdProvider>{children}</TinybirdProvider>
        </ClerkProvider>
      </body>
    </html>
  )
}
```

In any component that needs to make Tinybird API calls, use the `useTinybirdToken` hook to get the token and make requests:

```tsx {{ filename: 'src/app/components/MyComponent.tsx', prettier: false }}
import { useTinybirdToken } from './providers/TinybirdProvider'

function MyComponent() {
  const { token } = useTinybirdToken()

  const fetchData = async () => {
    const response = await fetch('https://api.tinybird.co/v0/pipes/your_pipe.json', {
      headers: {
        Authorization: `Bearer ${token}`
      }
    })
    // Handle response
  }

  return (
    // Your component JSX
  )
}
```

This implementation provides a clean, efficient way to handle multi-tenant analytics with Clerk and Tinybird, while keeping the authentication logic on the client side. The provider automatically manages the token, and components can easily access it through the `useTinybirdToken` hook.

## Demo

Want to see how this works in practice? Check out this [live Clerk JWT demo for a Tinybird API.](https://clerk-tinybird.vercel.app/) If you want to check out the code, you can find it in [this repository](https://github.com/tinybirdco/clerk-tinybird).

## Get started

Building real-time analytics features into your application is pretty simple with Clerk and Tinybird. Just create a JWT template, add filtering parameters to your Tinybird APIs, and use Clerk middleware to set the token header in the request.

If you'd like to build a user-facing analytics MVP, [sign up for Tinybird](https://cloud.tinybird.co/signup) (it's free, no time limit) and follow the quick start. You'll be able to build your first API in a few minutes and have it secured in your application just as quickly using Clerk.

---

# How Huntr Migrated 250K Users to Clerk: A Scalable Auth Solution for Startups
URL: https://clerk.com/blog/huntr-testimonial.md
Date: 2025-05-01
Category: Testimonial
Description: Huntr shares how migrating to Clerk gave them transparent pricing, responsive support, and a developer-first experience.

*This customer testimonial from [Huntr.co](http://huntr.co/) — an AI-powered job search SaaS — details how and why they migrated over 250,000 users from their previous authentication provider to Clerk's developer-first authentication platform. The move was driven by a need for developer-first tools, transparent pricing, and responsive support that could scale with their product and team.*

## Background

In 2023, our team at Huntr hit a crossroads. We had built significant momentum, supporting over 250,000 job seekers on our [resume builder](https://huntr.co/product/ai-resume-builder) and AI-powered job search management platform. Then came the notification. Our authentication provider at the time informed us of a pending **$50,000 price increase** as we graduated from their startup plan.

As a fast-growing, bootstrapped SaaS company, that kind of jump was simply untenable. We were actively hiring and investing in growth and absorbing such a significant hit to our cash flow wasn't an option.

We realized we needed a new provider, an auth solution that wouldn't penalize our growth, understood the pace of a startup, and felt invested in our success. That's when we discovered Clerk.

## Why Huntr chose Clerk for scalable authentication

We successfully [migrated **250,000 user accounts** to Clerk](/docs/deployments/migrate-overview). Looking back, it's undeniably one of the best infrastructure decisions we've ever made.

> “Switching from \[our previous provider] to Clerk was honestly one of the best moves we've made at Huntr. As a growing SaaS, we instantly felt the difference in how Clerk supports startups like us.”
>
> *- Rennie Haylock, Founder and CEO of Huntr.co*

## Challenges with our previous auth provider

While our prior authentication provider served us reliably in our early days, challenges emerged as we scaled. Both technically and strategically, the relationship began to feel strained. Support became more distant, processes slowed down, and their pricing structure felt rigid. Critically, it seemed their focus had shifted firmly towards enterprise clients, leaving startups like ours feeling misaligned.

> “We realized we needed an auth platform that matched how we actually build—fast, iterative, and user-focused. What we had in place felt out of sync with that. So we started looking for something that felt more modern and developer-friendly.”
>
> *- Haylock*

## How Clerk supports startups - A true partnership

From our very first interaction, Clerk felt different. They weren't just another vendor; they acted like a partner invested in our success. Clerk's co-founder, Braden, personally engaged with us, taking the time to understand our specific use case. He immediately provided actionable support and pointed us to the exact documentation we needed.

> “Clerk, however, was a breath of fresh air. Braden jumped right in, quickly understanding our specific use cases and offering real, actionable support. They gave us the docs we needed and hands-on help that made our migration straightforward.”
>
> *- Haylock*

The migration itself was remarkably smooth, largely thanks to Clerk's incredible support team. What we anticipated would be a major undertaking turned out to be far more manageable. Furthermore, Clerk's [pricing](/pricing) was transparent and genuinely **startup-friendly**. They offered a long-term plan that gave us confidence, eliminating the anxiety of potential price shocks as our user base continued to grow.

> “Clerk's startup-friendly approach stood out to us. Whenever we hit technical snags during the migration, they were right there with quick, helpful responses. They even proactively offered us a clear, attractive long-term pricing structure, which was a huge relief as we scaled up.”
>
> *- Haylock*

## How Clerk earned Huntr's trust through support and transparency

Beyond the features and pricing, Clerk earned our trust through their accountability. A minor billing error occurred on their end early in our relationship. Instead of deflecting or delaying, they took immediate ownership, corrected the mistake, and issued a prompt refund without any hassle. This level of integrity solidified our confidence in them as a partner.

> “One thing that genuinely impressed us was how Clerk handled a billing hiccup. An unexpected error popped up, and they didn't waste time or make excuses—they immediately owned it, fixed it, and issued a refund without any hassle.”
>
> *- Haylock*

## Scaling with confidence: Clerk's long-term impact at Huntr

We've now been powered by Clerk for over a year. They've been instrumental in helping us scale efficiently, stay lean, and maintain the development velocity crucial for a startup. In a world increasingly driven by AI, where Huntr is focused on building infrastructure to help job seekers land jobs faster, the last thing we need is to worry about our authentication stack. Clerk handles it seamlessly.

If you're a startup founder evaluating your authentication options, Clerk truly understands the startup journey. They provide the speed, flexibility, fairness, and partnership that growing companies need.

> “Overall, Clerk gets startups. They're flexible, quick to respond, and dependable. We highly recommend Clerk to other SaaS companies looking for a true partner in authentication.”
>
> *Sam Wright, Head of Operations and Partnerships, Huntr.co*

## TL;DR

- Huntr migrated 250K user accounts from a legacy auth provider to Clerk
- Their previous provider introduced rigid pricing and slow support
- Clerk offered transparent pricing, fast dev support, and smoother developer workflows
- The switch helped Huntr scale efficiently without auth-related bottlenecks

>

---

# How to take your Clerk application to production
URL: https://clerk.com/blog/how-to-take-your-clerk-app-to-prod.md
Date: 2025-04-25
Category: Guides
Description: Learn how to launch your app with a production instance of Clerk to maximize security and user limits.

When you're building a modern web app, authentication is one of the first things you’ll want to wire up, and Clerk makes that easy and fast!

When you first create a Clerk application, we initialize a development instance of our platform help you get up and running in minutes. But when it's time to launch, you’ll need to switch to a production instance for maximum security and higher user limits. That transition comes with a few key differences and steps to follow.

In this article, you’ll learn what separates development from production, how to configure a Clerk production instance, and how to upgrade your social login providers for production use.

## What is the difference between dev and prod?

Clerk’s development instances are optimized for speed and simplicity. They let you integrate authentication and user management quickly, without worrying about configuration or infrastructure. By default, they’re set up to be as flexible as possible, allowing HTTP connections and using shared OAuth credentials across all Clerk apps.

Production instances, by contrast, prioritize security and isolation. They require HTTPS, and each instance must use its own credentials for identity providers. Rather than relying on cross-domain redirects, Clerk authenticates users directly on your domain using DNS records, reducing the risk of cross-site scripting attacks and improving user trust.

## Migrating to Production: Step by Step

The first step in going to production is securing a custom domain. This is the address where users will access your app, and it’s also how Clerk will verify the authenticity of authentication requests.

### Step 1: Create a production instance

Head to the [Clerk Dashboard](https://dashboard.clerk.com/). At the top of the page, you’ll see a toggle labeled **Development**—click it and select **Create production instance** from the dropdown menu.

![Clerk dashboard showing how to switch from a development instance to a production instance. The dropdown menu labeled “Development” is open, with a red arrow pointing to the “Create production instance” option. This step is part of promoting your app to production in Clerk.](./img1.png)

Clerk gives you the option to clone your dev settings or start with a fresh configuration. If you choose to clone, note that some sensitive settings like SSO connections, integrations, and custom paths won’t carry over. These need to be configured again for security reasons.

### Step 2: Configure DNS

Once your instance is created, Clerk will show you a checklist of remaining steps to make your production environment fully functional. Among these steps is configuring DNS records. These records tell the internet that Clerk is authorized to manage auth on your domain and are required for login, email delivery, and other user-facing features to work properly.

![Clerk production instance dashboard displaying the initial setup checklist. It highlights required actions such as setting custom OAuth2 credentials and configuring CNAME records before deployment. Metrics for total users, active users, and deployment status (frontend not deployed, backend operational) are shown below.
](./img2.png)

### Step 3: Update your API keys

With the instance and DNS in place, you’ll need to update your application with the new API keys. Each Clerk instance—development or production—has its own set of keys, located under **Configure** > **API Keys**. Production keys are prefixed with `pk_live` and `sk_live` for the publishable and secret key respectively.

![Clerk dashboard showing the API keys configuration page for a production instance. The screen displays the public "publishable key" (pk\_live\_...) for frontend use and a masked secret key under “default.” It also shows relevant deployment URLs, including the Frontend API, Backend API, and JWKS endpoint. A warning at the bottom reminds users that Clerk support will never ask for secret keys.](./img3.png)

### Step 4: Update SSO Connections

Social login is a common part of many apps, and Clerk makes it easy to integrate providers like Google, GitHub, and Discord. In development mode, Clerk uses shared OAuth credentials behind the scenes, so you don’t need to register anything manually. This saves time, especially when testing across multiple identity providers.

However, in production, each provider needs to recognize and trust *your* app specifically. That means you’ll need to register an application with each identity provider and generate your own client ID and secret. Once created, you can plug those into the Clerk dashboard for each enabled provider in **Configure** > **SSO connections**.

Clerk maintains a helpful set of [guides for each OAuth provider](/docs/authentication/social-connections/oauth), walking you through the process of registering your app and setting up redirect URIs. These are worth bookmarking, especially if your app supports multiple login options.

## Practical Example: Taking Quillmate Live

In this section, I’ll walk you through how I migrated **Quillmate**—an open source online writing platform built with Next.js, Clerk, and Supabase—from development to production.

Quillmate is deployed on Vercel, and I’ve registered `quillmate.site` with Namecheap as my domain.

> \[!NOTE]
> The order of the steps in this portion of the guide deviates slightly from what’s listed above to follow the launch checklist in the Clerk dashboard which may vary from app to app.

### Step 1: Create a production instance

I’ll start by selecting the **Create production instance** from the toolbar as described below and opting to clone my instance settings, which will enable email and Google login strategies:

![Clerk dashboard showing the “Create production instance” button in the toolbar. The “Development” toggle is selected, and a red arrow points to the button. This step is part of promoting your app to production in Clerk.](./img4.png)

When prompted, I’ll enter `quillmate.site` as the application domain and click **Create instance**:

![The Create production instance modal open with the domain quillmate.site entered in the input field. The “Create instance” button is highlighted.](./img5.png)

I’m then brought to the Overview tab of my production instance, which contains the checklist of the tasks I need to perform to finish setting up the instance.

![Clerk production instance dashboard displaying the initial setup checklist. It highlights required actions such as setting custom OAuth2 credentials and configuring CNAME records before deployment. Metrics for total users, active users, and deployment status (frontend not deployed, backend operational) are shown below.](./img6.png)

### Step 2: Configure Google SSO

Selecting any task here will bring you to the dashboard page where you can complete the task, so I’ll start by selecting **Set social connection (OAuth) credentials**. This will bring me to my list of enabled SSO connections.

![The list of enabled SSO connections for the production instance. The “Google” connection is highlighted.](./img7.png)

You’ll need to create OAuth credentials for each provider listed. I’m using only Google to demonstrate the process, so I’ll click the cog icon to open the configuration model for that provider.

In the modal, **Use custom credentials** is already enabled (since they are required for production instances). There is also a link to our docs, which describes the process of configuring Google SSO with Clerk, but I’ll also cover this process here.

![The Google SSO configuration modal open with the “Use custom credentials” option enabled. The “Client ID” and “Client Secret” fields are highlighted.](./img8.png)

To create the Google OAuth credentials, you’ll access the [Google Cloud Console](https://console.cloud.google.com/) and [create a new project](https://console.cloud.google.com/projectcreate) if you don’t already have one you want to work with. I have a project named “Quillmate Demo” that I’m using. I’ll use the menu to navigate to **APIs & Services > OAuth consent screen** and select **Get started**.

![The OAuth overivew tab in Google Cloud with Get Started button highlighted.](./img9.png)

The following screen will present a series of steps where you’ll need to populate information in each step. The fields are pretty self-explanatory, so here is how I populated mine:

1. App Information
   1. App name: Quillmate Demo
   2. User support email: My email address
2. Audience
   1. External
3. Contact Information
   1. Email address: My email address
4. Finish
   1. I agree to the Google API Services: User Data Policy.

Once I click **Create**, I’ll once again use the menu to navigate to **APIs & Services > Credentials** and select **Create credentials > OAuth client ID**.

![The API & Serviecs menu item in Google Cloud Console with the “Credentials” submenu open and the “Create credentials” button highlighted.](./img10.png)

In the Create OAuth client ID page, I’ll set the Application type to Web application and give it a name, Quillmate Demo, in my case. Under Authorized JavaScript origins, I’ll enter “[https://quillmate.site”](https://quillmate.site) since that’s the only URL used to access this demo, but if you use more than one URL, you’ll need to enter them all here. Finally, for Authorized redirect URIs, I’ll paste in the value displayed in the Clerk dashboard.

The final version of the screen looks like so:

![The Create OAuth client ID page in Google Cloud Console.](./img11.png)

Once I click **Create** at the bottom of the screen, I’ll receive my client ID and secret:

![The OAuth client created modal](./img12.png)

> \[!NOTE]
> Your client secret should be protected like any secret, as anyone who obtains it can impersonate your application to steal user credentials.

Then I can copy those values into the Clerk dashboard and click **Update** to complete the configuration.

![The OAuth client ID and secret copied into the Clerk dashboard.](./img13.png)

### Step 3: Configure DNS

To complete the setup in the Clerk dashboard, I’ll head back to the **Overview** tab and select **Finish your setup** from the list of tasks.

![The Finish your setup task in the Clerk dashboard.](./img14.png)

This will redirect me to the Domains section, where five records will need to be created in my DNS provider to point the necessary subdomains to Clerk for user management, as well as configure email security so we can send messages on behalf of your domain. Every provider is going to be different, but the following image represents the entered records on my registrar:

![DNS records for quillmate.site in my DNS provider.](./img15.png)

Once entered, I’ll head back to the Clerk dashboard and click Validate configuration near the top of the page, which will prompt us to check the DNS records:

![The Validate configuration button in the Clerk dashboard.](./img16.png)

Once verified, Clerk will generate the SSL certificates required for a secure connection to our services.

> \[!NOTE]
> Setting DNS records can take time to propagate, sometimes up to 24 hours in rare circumstances.

### Step 4: Updating the domain and API keys in Vercel

As mentioned earlier, I am hosting Quillmate on Vercel. In this section, I’ll set the custom domain with Vercel as well as update the Clerk API keys being used to point to the production instance of the Clerk application.

I have already deployed the development version of my application to Vercel, so I’ll start by selecting it from the list of applications I’m hosting. I’ll then navigate to **Settings > Domains** and click **Add**.

![The Domains section in Vercel with the “Add” button highlighted.](./img17.png)

Then I’ll enter the domain `quillmate.site` and click **Add Domain**.

![The Add Domain modal in Vercel with the domain quillmate.site entered in the input field. The “Add Domain” button is highlighted.](./img18.png)

Once added, I’ll need to update my DNS again to match what Vercel expects, specifically pointing the root of the domain to 76.76.21.21.

![The Domain configuration page in Vercel for quillmate.site.](./img19.png)

Once added to my DNS configuration, I can refresh the domain in Vercel to verify the configuration and generate the SSL certificates to access my site at `https://quillmate.site`.

Now that the application is accessible at my domain, I can update the API keys. These are located in the Clerk Dashboard under **Configure > API Keys**. Since I’m using Next.js, I’ll select that option from *Quick Copy* and then click the **Copy** button.

![The API keys configuration page in the Clerk dashboard with the "Next.js” option selected and the “Copy” button highlighted.](./img20.png)

Back in the Vercel dashboard, I’ll navigate to **Settings > Environment Variables** to access the current environment variables. I’ll need to modify the `CLERK_SECRET_KEY` and `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` variables to only work in non-production environments. To do this, I’ll click the three dots next to each variable and select **Edit**. I can then edit the **Environments,** uncheck the **Production** environment, and click **Save**.

![The Environment Variables section in Vercel.](./img21.png)

Once this is done for both variables, I’ll scroll up to the section where I can add new variables. Using the **Environments** drop-down once more, I can uncheck all but the **Production** environment, paste in the copied values from the Clerk dashboard, and click **Save**.

![The Environment Variables section in Vercel with the new variables added.](./img22.png)

> \[!NOTE]
> For added security, you may consider adding the CLERK\_SECRET\_KEY variable separately and setting it as a Sensitive variable to prevent team members from reading

Once I save, I’ll get the option to **redeploy** my application, which is required for the new variables to take effect.

## Conclusion

Moving your Clerk app to production is more than just flipping a flag—it’s about setting up a secure and trustworthy authentication layer that runs under your domain. Development mode is perfect for quick iteration, but production mode ensures that users have a seamless and secure experience.

By creating a production instance, updating your DNS, configuring your own OAuth credentials, and deploying with the right keys, you’ll have a secure, production-ready app with Clerk that creates a great experience for your users.

> \[!TIP]
> Learn more about taking your Clerk app to production [in our docs](/docs/deployments/overview).

---

# A practical guide to testing Clerk Next.js applications
URL: https://clerk.com/blog/testing-clerk-nextjs.md
Date: 2025-04-11
Category: Guides
Description: An example-packed guide to writing effective tests for Clerk applications, covering everything from integration testing with React Testing Library to end-to-end testing using Playwright.

We understand that writing tests isn't the most exciting part of development. That's why they are often shelved as "tech debt" or pushed to the bottom of the priority list. But it's not just about motivation - writing good tests is *hard*.

You might wonder:

- What do I test?
- How do I test it?
- Should I write integration tests or end-to-end tests?
- How do I mock Clerk?

This post addresses these challenges directly to help you develop a meaningful testing strategy for your Next.js application using Clerk.

By the end, you'll be equipped to test critical authentication flows by mocking Clerk's API in integration tests.

You'll also learn how to incorporate end-to-end tests and simulate real-world user interactions with your application to make sure that your application works (and continues to work) as it should in production.

## Meet Pup Party

This post demonstrates how to add comprehensive tests to a sample application called Pup Party.

Pup Party allows dog owners to rate and discover dog-friendly cafes and restaurants. Users can evaluate venues as "Pup-approved" or "Pup-fail" based on criteria such as dog-friendliness, ambiance, and the quality of dog treats.

To follow along and implement tests step-by-step, download the starter code and follow the set up instructions [here](https://github.com/bookercodes/testing-clerk-nextjs-apps-example).

Alternatively, you can study the complete source code, including tests, [here](https://github.com/bookercodes/testing-clerk-nextjs-apps-example/tree/finished).

## Choosing the right testing approach

Before we dive in, it's important to take a moment to think about what exactly you should be testing and the types of tests that will be most effective. This planning step is crucial, as it guides you in creating tests that are not only meaningful but also efficient to run and maintain.

I've seen teams fall into three common traps:

1. Over-relying on unit tests that focus too much on internals
2. Struggling to scale and manage integration tests effectively due to a reliance on brittle test data and overly complex test logic
3. Over-investing in E2E tests, leading to slow, flaky test suites that bog down development

So, what's the right approach?

In this post, we turn to [Kent C. Dodds](https://kentcdodds.com/) and his [testing trophy](https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications):

- Write mostly integration tests
- Supplement with well-placed E2E tests
- Don't overdo it

Following Kent's testing philosophy, this post primarily focuses on integration tests and later addresses E2E tests for critical user paths.

## Integration tests

To write integration tests, you will be using two essential tools:

- **[Jest](https://jestjs.io)**: A test runner and assertion framework to structure and execute your integration tests.
- **[React Testing Library (RTL)](https://testing-library.com/docs/react-testing-library/intro/)**: A popular testing framework for React applications that encourages testing components from the user's perspective, while still supporting the use of [Jest mocks](https://jestjs.io/docs/mock-functions) where necessary.

### Set up Jest and RTL

Start by installing these dev dependencies:

```bash {{ filename: 'Terminal' }}
npm install --save-dev jest \
  @types/jest \
  @testing-library/react \
  @testing-library/jest-dom \
  @testing-library/dom \
  @testing-library/user-event \
  jest-environment-jsdom \
  next-router-mock
```

Add a `test:jest` script to `package.json`:

```json {{ filename: './package.json', ins: [6], prettier: false }}
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint",
  "test:jest": "jest",
},
```

Add a `jest.config.ts` file to the root of your project:

```typescript {{ filename: './jest.config.ts' }}
import type { Config } from 'jest'
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
  dir: './',
})

const config: Config = {
  clearMocks: true,
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  // Map next-router-mock to the next/navigation module it mocks
  // Learn more - https://github.com/scottrippey/next-router-mock
  moduleNameMapper: {
    '^next/navigation$': 'next-router-mock',
  },
}

export default createJestConfig(config)
```

Create a `jest.setup.ts` file in the root of your project to globally import @testing-library/jest-dom, which enhances Jest with custom matchers for more intuitive and readable DOM assertions:

```typescript {{ filename: './jest.setup.ts' }}
import '@testing-library/jest-dom'
```

In the ` ./tsconfig.json` file, add `"jest"` to the `types` array:

```json {{ filename: './tsconfig.json', ins: [3] }}
{
  "compilerOptions": {
    "types": ["jest"]
  }
}
```

Finally, create a ` ./__tests__` directory in your root folder. Within this directory, add an empty `index.test.tsx` file - this is where you'll set up your mocks, configure helpers, and write your integration tests in the next section.

#### Set up your mocks and helpers

In your integration tests, you should avoid making actual network calls, including to Clerk. Network calls can introduce variability and slow down your test suite, making it harder to achieve consistent and fast test results. Instead, you should [mock](https://stackoverflow.com/a/2666006) these libraries to control their behavior in your tests and keep your integration test suite lean.

> \[!IMPORTANT]
>
> It's a best practice to avoid writing integration tests for the internals of third-party libraries like Clerk, as they already have their own tests.

Below is the code to mock @clerk/nextjs, which allows you to simulate its behavior and focus on testing your application's logic without relying on external dependencies. Additionally, a helper function called `renderWithProviders` is defined. This function takes an `isSignedIn` argument, allowing you to simulate authenticated and unauthenticated user states in your tests.

```tsx {{ filename: './__tests__/index.test.tsx' }}
import { render, screen, waitFor } from '@testing-library/react'
import { ReactNode } from 'react'
import SubmitReviewPage from '../app/submit-review/page'
import { ClerkProvider, useAuth } from '@clerk/nextjs'
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider/next-13.5'
import { userEvent } from '@testing-library/user-event'

jest.mock('@clerk/nextjs', () => {
  const originalModule = jest.requireActual('@clerk/nextjs')
  return {
    ...originalModule,
    useAuth: jest.fn(() => ({ userId: null })),
    SignIn: () => <div data-testid="clerk-sign-in">Sign In Component</div>,
    ClerkProvider: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  }
})

const TestProviders = ({
  isLoggedIn = false,
  children,
}: {
  isLoggedIn?: boolean
  children: ReactNode
}) => {
  ;(useAuth as jest.Mock).mockReturnValue({ userId: isLoggedIn ? 'user-id' : null })

  // Here, we wrap our component in the ClerkProvider and MemoryRouterProvider to provide the necessary context for our tests.
  // MemoryRouterProvider is used to mock the Next.js router,
  // which is necessary for testing components that use the router.
  return (
    <MemoryRouterProvider>
      <ClerkProvider>{children}</ClerkProvider>
    </MemoryRouterProvider>
  )
}

const renderWithProviders = (ui: ReactNode, isLoggedIn?: boolean) => {
  return render(<TestProviders isLoggedIn={isLoggedIn}>{ui}</TestProviders>)
}
```

> \[!NOTE]
>
> The details about next-router-mock are outside the scope of this post, but you can learn more about it in the library's [documentation](https://github.com/scottrippey/next-router-mock) for a deeper understanding.

Now that you have Jest and RTL configured, along with your mocks and test context provider, it's time to write some meaningful integration tests!

You will implement three integration tests for Pup Party. For each test scenario, you'll read a definition of the requirements in plain text before implementing code to programmatically test the expected behavior and protect against regressions.

### Test case 1: Unauthenticated users cannot submit a review

If an unauthenticated user tries to access the submission page, they should be redirected to the sign-in page.

Here's the test implementation, along with comments explaining each notable line.

Add it to the bottom of index.test.tsx:

```tsx {{ filename: './__tests__/index.test.tsx', ins: [[3, 31]] }}
// Add below the previous snippet at the bottom of the file

describe('Submit Review Page', () => {
  // Grouping tests related to unauthenticated user scenarios
  describe('When a user is unauthenticated', () => {
    const isLoggedIn = false

    it('redirects them to sign in when they try to access the review submission page', async () => {
      // Render the SubmitReviewPage component with the user not logged in
      renderWithProviders(<SubmitReviewPage />, isLoggedIn)

      // Wait for the sign-in element to appear, indicating a redirect to sign-in
      waitFor(
        () => {
          expect(screen.getByTestId('clerk-sign-in')).toBeInTheDocument()
        },
        { timeout: 5000 }, // Timeout after 5 seconds if the element doesn't appear
      )

      // Ensure the review submission prompt is not visible, confirming the redirect
      waitFor(
        () => {
          expect(
            screen.queryByText('Review how dog-friendly this restaurant is!'),
          ).not.toBeInTheDocument()
        },
        { timeout: 5000 }, // Timeout after 5 seconds if the element is still visible
      )
    })
  })
})
```

In this test, an unauthenticated user's experience on the `<SubmitReviewPage />` is simulated by setting ` isSignedIn` to ` false` and passing it as the second argument to our ` renderWithProviders` function.

The [`waitFor`](https://testing-library.com/docs/dom-testing-library/api-async/#waitfor) utility from React Testing Library is used to verify that the UI updates correctly after state changes, specifically ensuring the user is redirected to the sign-in page and cannot access the review submission content.

> \[!TIP]
> You can see the complete test file [here](https://github.com/bookercodes/testing-clerk-nextjs-apps-example/blob/finished/__tests__/index.test.tsx) and reference it if you're following along and need help figuring out where things go.

### Test case 2: Authenticated users can successfully submit a valid review

When a signed-in user submits valid details in the review form, the form should process successfully and then display a confirmation message.

Here's the test implementation:

```tsx {{ filename: './__tests__/index.test.tsx', ins: [[6, 37]] }}
describe('Submit Review Page', () => {
  describe('When a user is authenticated', () => {
    const isLoggedIn = true

    // Add beneath "it" function in previous snippet
    it('allows them to submit a review successfully', async () => {
      const user = userEvent.setup()

      renderWithProviders(<SubmitReviewPage />, isLoggedIn)

      expect(
        await screen.findByText('Welcome to the Dog Friendly Restaurant Reviews form!'),
      ).toBeInTheDocument()

      expect(screen.queryByTestId('clerk-sign-in')).not.toBeInTheDocument()

      const reviewInput = await screen.findByRole('textbox', {
        name: /review/i,
      })
      const ratingInput = await screen.findByRole('spinbutton', {
        name: /rating/i,
      })

      await user.click(reviewInput)
      await user.type(reviewInput, 'Great place!')
      await user.click(ratingInput)
      await user.type(ratingInput, '5')

      await user.click(screen.getByText('Submit'))

      // expect the form to be cleared
      expect(screen.getByLabelText('Review')).toHaveValue('')
      expect(screen.getByLabelText('Rating')).toHaveValue

      // expect a success toast
      expect(await screen.findByText('Form submitted successfully!')).toBeInTheDocument()
    })
  })
})
```

In the code above, `isSignedIn` is set to `true` to simulate the experience of an authenticated user accessing Pup Party.

The `userEvent` utilities are used to mimic user actions, such as clicking into a form input, typing, hitting "Submit", and viewing a success notification.

> \[!TIP]
> Use React Testing Library's selectors like [`findByRole`](https://testing-library.com/docs/queries/byrole/) and [`getByLabelText`](https://testing-library.com/docs/queries/bylabeltext/) to create more robust tests.
>
> This approach, compared to using selectors like `getElementById` or `querySelector`, avoids relying on rigid DOM structures in your queries and assertions. By focusing on the roles and labels, you reduce the likelihood of introducing test failures from UI layout changes. It also promotes the use of semantic HTML, thereby enhancing your application's accessibility.

### Test case 3: Authenticated users must submit valid reviews

Requirements for the submission form:

- When authenticated users add a rating that is less than zero, they should see a "Rating must be a number above 0" error appear next to the rating field
- When authenticated users add a written review that is less than 5 characters long, they should see a "Review must be at least 5 characters" error appear next to the review field
- If authenticated users submit the form without correcting the errors, the form fields should retain the original values along with their error messages, and an additional "Please fix the errors in the form!" will be displayed to the user

Here's the test code that checks these requirements:

```tsx {{ filename: './__tests__/index.test.tsx', ins: [[5, 41], [43, 80]] }}
describe('Submit Review Page', () => {
  describe('When a user is authenticated', () => {
    const isLoggedIn = true

    it('displays error messages when the user adds a rating that is less than 0', async () => {
      const user = userEvent.setup()

      renderWithProviders(<SubmitReviewPage />, isLoggedIn)

      expect(
        await screen.findByText('Welcome to the Dog Friendly Restaurant Reviews form!'),
      ).toBeInTheDocument()

      expect(screen.queryByTestId('clerk-sign-in')).not.toBeInTheDocument()

      // Note the use of findByRole, as well as async/await. Role lookup is based on the role attribute, which is used to describe the purpose of an element.
      // This is useful for accessibility.
      const reviewInput = await screen.findByRole('textbox', {
        name: /review/i,
      })
      const ratingInput = await screen.findByRole('spinbutton', {
        // Note: can also find by Label!
        name: /rating/i,
      })

      await user.click(reviewInput)
      await user.type(reviewInput, 'Great place!')
      await user.click(ratingInput)
      await user.type(ratingInput, '-1')

      expect(await screen.findByText('Rating must be a number above 0')).toBeInTheDocument()

      await user.click(screen.getByText('Submit'))

      // Note the use of getByLabelText to find the input field by its label text, which helps with accessibility.
      expect(screen.getByLabelText('Review')).toHaveValue('Great place!')
      expect(screen.getByLabelText('Rating')).toHaveValue(-1)

      // expect an error message
      expect(await screen.findByText('Please fix the errors in the form!')).toBeInTheDocument()
    })

    it('displays error messages when the user adds a review that is less than 5 characters', async () => {
      const user = userEvent.setup()

      renderWithProviders(<SubmitReviewPage />, isLoggedIn)

      // Note here the user of findByText based on the page's text content as viewed by the user.
      expect(
        await screen.findByText('Welcome to the Dog Friendly Restaurant Reviews form!'),
      ).toBeInTheDocument()

      // Note the use of queryByTestId to check for non-existence.
      expect(screen.queryByTestId('clerk-sign-in')).not.toBeInTheDocument()

      // Note the use of findByRole, as well as async/await.
      const reviewInput = await screen.findByRole('textbox', {
        name: /review/i,
      })
      const ratingInput = await screen.findByRole('spinbutton', {
        // Note: can also find by Label!
        name: /rating/i,
      })

      await user.click(reviewInput)
      await user.type(reviewInput, 'ok')
      await user.click(ratingInput)
      await user.type(ratingInput, '5')

      expect(await screen.findByText('Review must be at least 5 characters')).toBeInTheDocument()

      await user.click(screen.getByText('Submit'))

      // Expect the form to be cleared
      expect(screen.getByLabelText('Review')).toHaveValue('ok')
      expect(screen.getByLabelText('Rating')).toHaveValue(5)

      // Expect an error message
      expect(await screen.findByText('Please fix the errors in the form!')).toBeInTheDocument()
    })
  })
})
```

> \[!NOTE]
> While the previous examples used a single test, here you've created separate tests for each validation error case, making it easier to identify and debug specific failures.

This code simulates a user interaction where an invalid review is submitted. It sets up a user event, renders the `<SubmitReviewPage />` for an authenticated user, and then mimics user actions of submitting a valid review but an invalid rating.

The `async` and `await` syntax is used to ensure that each action and corresponding assertion happens in the correct order, as some operations are asynchronous and need to complete before the next action or assertion takes place.

### Running the integration tests

Execute your integration tests by running `test:jest`:

```bash {{ filename: 'Terminal' }}
npm run test:jest
```

This script, defined earlier, will run the tests and display the results in your terminal:

![./jest-results.png](./jest-results.png)

## End-to-end tests

Having implemented integration tests, let's turn our attention to end-to-end (E2E) tests.

> \[!NOTE]
> Integration tests focus on verifying that specific components interact correctly with each other, while E2E tests validate complete user workflows across an entire application in production-like environments.
>
> Both are necessary because integration tests provide faster, more targeted feedback about component interactions during development, while E2E tests ensure the complete system functions properly from a user's perspective before release.
>
> [Learn more](https://microsoft.github.io/code-with-engineering-playbook/automated-testing/e2e-testing/) about end-to-end tests.

Unlike integration tests that mock Clerk, E2E tests interact with Clerk directly to simulate production conditions.

To write E2E tests, you will be using two essential tools:

- **[Playwright](https://playwright.dev/)**: A powerful automation framework that allows you to perform end-to-end testing across multiple browsers. It offers comprehensive features for simulating user interactions and validating web applications.
- **[@clerk/testing](https://www.npmjs.com/package/@clerk/testing)**: This utility offers helpful tools specifically designed for testing Clerk applications.

### Set up Playwright and @clerk/testing

Start by create a new directory called `./e2e` - this is where you'll write your E2E in the upcoming section.

Install `@clerk/testing` as a dev dependency:

```bash {{ filename: 'Terminal' }}
npm install @clerk/testing --save-dev
```

Initialize and install Playwright:

```bash {{ filename: 'Terminal' }}
npm init playwright
```

Choose the following options when prompted:

```bash {{ filename: 'Terminal' }}
> Do you want to use TypeScript or JavaScript? → TypeScript
> Where to put your end-to-end tests? → e2e
> Add a GitHub Actions workflow? → N
> Install Playwright browsers? → Y
```

> \[!NOTE]
>
> `npm init playwright` automatically installs Playwright as a dev dependency and creates a default `playwright.config.ts` file

Replace the contents of `./playwright.config.ts` to configure Playwright for end-to-end testing:

```typescript {{ filename: './playwright.config.ts' }}
import { defineConfig } from '@playwright/test'

// Set the port for the server
const PORT = process.env.PORT || 3000

// Set webServer.url and use.baseURL with the location of the WebServer
// respecting the correct set port
const baseURL = `http://localhost:${PORT}`

export default defineConfig({
  // Look for tests in the "e2e" directory
  testDir: './e2e',
  // Set the number of retries for each, in case of failure
  retries: 1,
  // Run your local dev server before starting the tests.
  webServer: {
    command: 'npm run dev',
    // Base URL to use in actions like `await page.goto('/')`
    url: baseURL,
    // Set the timeout for the server to start
    timeout: 120 * 1000,
    // Reuse the server between tests
    reuseExistingServer: !process.env.CI,
  },
  use: {
    // Base URL to use in actions like `await page.goto('/')`.
    baseURL,

    // Collect trace when retrying the failed test.
    // See https://playwright.dev/docs/trace-viewer
    trace: 'retry-with-trace',
  },
})
```

Define an additional script to run E2E tests:

```json {{ filename: './package.json', ins: [7], prettier: false }}
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint",
  "test:jest": "jest",
  "test:playwright": "playwright test --ui"
},
```

To ensure your E2E tests don't inadvertently run when invoking the `test:jest` script - which could cause errors due to Playwright tests being incompatible with Jest - update `jest.config.ts` as follows:

```typescript {{ filename: './jest.config.ts', ins: [17] }}
import type { Config } from 'jest'
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
  dir: './',
})

const config: Config = {
  clearMocks: true,
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  // map next-router-mock to the next/navigation module it mocks
  // learn more - https://github.com/scottrippey/next-router-mock
  moduleNameMapper: {
    '^next/navigation$': 'next-router-mock',
  },
  testPathIgnorePatterns: ['<rootDir>/e2e/', '<rootDir>/.next/', '<rootDir>/node_modules/'],
}

export default createJestConfig(config)
```

#### Enable Clerk testing tokens

Clerk [testing tokens](/docs/testing/overview#testing-tokens) allow you to bypass Clerk's bot detection mechanisms, which can otherwise block automated test requests with "bot traffic detected" errors.

The @clerk/testing library makes accessing Clerk testing tokens easy by automatically obtaining one when your test suite starts. It then offers the `setupClerkTestingToken` function, which injects the token, enabling your tests to bypass Clerk's bot detection mechanisms without any hassle. The `clerk.signIn` method internally uses the `setupClerkTestingToken` helper, so there's no need to call it separately when using this method.

> \[!NOTE]
> While it's helpful to understand why this is necessary, @clerk/testing and the Playwright integration abstract away the details, so you don't have to manage tokens manually.

To configure Playwright with Clerk, create `./e2e/global.setup.ts` and call the `clerkSetup()` function:

```typescript {{ filename: './e2e/global.setup.ts' }}
import { clerkSetup } from '@clerk/testing/playwright'
import { test as setup } from '@playwright/test'

// Setup must be run serially, this is necessary if Playwright is configured to run fully parallel: https://playwright.dev/docs/test-parallel
setup.describe.configure({ mode: 'serial' })

setup('global setup', async ({}) => {
  await clerkSetup()
})
```

Next, make sure `global.setup.ts` is called from `./playwright.config.ts`:

```typescript {{ filename: './playwright.config.ts', del: [1], ins: [2, [34, 49]] }}
import { defineConfig } from '@playwright/test'
import { defineConfig, devices } from '@playwright/test'

// Set the port for the server
const PORT = process.env.PORT || 3000

// Set webServer.url and use.baseURL with the location of the WebServer
// respecting the correct set port
const baseURL = `http://localhost:${PORT}`

export default defineConfig({
  // Look for tests in the "e2e" directory
  testDir: './e2e',
  // Set the number of retries for each, in case of failure
  retries: 1,
  // Run your local dev server before starting the tests.
  webServer: {
    command: 'npm run dev',
    // Base URL to use in actions like `await page.goto('/')`
    url: baseURL,
    // Set the timeout for the server to start
    timeout: 120 * 1000,
    // Reuse the server between tests
    reuseExistingServer: !process.env.CI,
  },
  use: {
    // Base URL to use in actions like `await page.goto('/')`
    baseURL,

    // Collect trace when retrying the failed test.
    // See https://playwright.dev/docs/trace-viewer
    trace: 'retry-with-trace',
  },

  // Configure projects for major browsers
  projects: [
    {
      name: 'global setup',
      testMatch: /global\.setup\.ts/,
    },
    {
      name: 'Main tests',
      testMatch: /user-submits-review\.spec\.ts/,
      use: {
        ...devices['Desktop Chrome'], // or your browser of choice
      },
      dependencies: ['global setup'],
    },
  ],
})
```

#### Create a test Clerk user

Since E2E tests interact with the Clerk API and require user authentication, you must create a real Clerk user and supply your test runner with the user's credentials. These credentials allow your tests to sign in, simulate an authenticated user state, and ensure everything functions as expected in a real-world scenario.

Create a test user through the Clerk dashboard:

![./create-user.png](./create-user.png)

Add the username and password to `.env.local`. Also add your Clerk application's `SECRET_KEY` and `PUBLISHABLE_KEY` if you haven't already:

```bash {{ filename: './env.local' }}
E2E_CLERK_USER_USERNAME=xxxxxxxxx
E2E_CLERK_USER_PASSWORD=xxxxxxxxx
CLERK_SECRET_KEY=xxxxxxxxx
CLERK_PUBLISHABLE_KEY=xxxxxxxxx
```

> \[!IMPORTANT]
> Replace `xxxxxxxxx` with your actual credentials.
>
> If you have enabled usernames for your Clerk application, set `E2E_CLERK_USER_USERNAME` to a username. If your Clerk application does not support usernames, set it to an email address instead.

Next, update your `playwright.config.ts` file to load environment variables such that they can be accessed from your tests:

```typescript {{ filename: './playwright.config.ts', ins: [3, 4, 6, 7] }}
// At the top of the file
import { defineConfig, devices } from '@playwright/test'
import dotenv from 'dotenv'
import path from 'path'

// Read the .env.local file and set the environment variables
dotenv.config({ path: path.resolve(__dirname, '.env.local') })
```

You now have everything in place to write an end-to-end test.

### Test case: Users can authenticate and successfully submit a review

This test verifies a critical user flow in a Clerk-authenticated application: signing in, submitting a review, and signing out.

Since this is an end-to-end test, it will cover multiple points in the system, ensuring that real user interactions work as expected.

Create `./e2e/user-submits-review.spec.ts` and paste the following:

```typescript {{ filename: './e2e/user-submits-review.spec.ts' }}
import { test, expect } from '@playwright/test'
import { clerk } from '@clerk/testing/playwright'

test('user can sign in, submit a review and sign out', async ({ page }) => {
  await page.goto('/sign-in')

  // Clerk's signIn utility uses setupClerkTestingToken() under the hood, so no reason to call it separately
  await clerk.signIn({
    page,
    signInParams: {
      strategy: 'password',
      identifier: process.env.E2E_CLERK_USER_USERNAME!,
      password: process.env.E2E_CLERK_USER_PASSWORD!,
    },
  })

  await page.goto('/submit-review')

  await expect(page).toHaveURL('/submit-review')

  // Fill in the review form
  await page.getByLabel(/review/i).fill('Had a great experience!')
  await page.getByLabel(/rating/i).fill('5')
  await page.getByRole('button', { name: /submit/i }).click()

  await expect(page.getByText(/form submitted successfully/i)).toBeVisible()

  // Clerk's signOut utility uses setupClerkTestingToken() under the hood, so no reason to call it separately
  await clerk.signOut({ page })

  expect(page).toHaveURL('/')
})
```

> \[!TIP]
>
> The `clerk.signIn()` function automatically uses the `setupClerkTestingToken()` helper, so there's no need to call it manually.

The test starts by navigating to the sign-in page and using Clerk's `signIn` utility to authenticate a test user.

Once signed in, the test redirects to the review submission page, where it completes and submits a form, then verifies a success message. Finally, it signs out and confirms redirection to the home page.

Running this test in the future will help catch any errors introduced by code changes. If a change breaks the functionality, the test will fail, alerting you to the issue. If the test passes, you can be confident that this flow in your application is working as it should.

### Running the end-to end tests

Run `test:playwright` to execute your end-to-end tests:

```bash {{ filename: 'Terminal' }}
npm run test:playwright
```

The result:

![./playwright-results.png](./playwright-results.png)

## Conclusion

In the introduction, key questions were posed about testing in Clerk applications:

- What should be tested?
- How should it be tested?
- Should integration or end-to-end tests be used?
- How can Clerk be effectively mocked?

This post has provided comprehensive answers to these questions, guiding you through the process of developing a robust testing strategy against a practical application.

Equipped with this knowledge, you now have the tools to confidently implement a balanced testing strategy for any appliation using Clerk for [Next.js authentication](/nextjs-authentication).

For more detailed guidance on testing Clerk applications, be sure to check out the [Clerk documentation on testing](/docs/testing/overview).

---

# Implementing multi-tenancy into a Supabase app with Clerk
URL: https://clerk.com/blog/multitenancy-clerk-supabase-b2b.md
Date: 2025-03-31
Category: Guides
Description: Learn how to build B2B applications with Clerk and Supabase.

Collaborative software is on track to become a nearly $53 billion\* industry by 2032. Needless to say, adding collaborative, multitenant features into your application will position you to capture a piece of that revenue.

In recent articles, we’ve covered how Supabase’s architecture relies on Postgres RLS to secure data within the database. While team-based features can be complex to implement, Clerk’s B2B tools integrate seamlessly with Supabase, enabling multi-tenancy with minimal configuration changes. In this article, you'll learn exactly how the integration works.

To follow along, you should have a general understanding of Supabase and ideally have built an application with Clerk and Supabase. If you want to learn more about the integration, we have an [article explaining how Supabase Auth works and how Clerk can be used with Supabase](/blog/how-clerk-integrates-with-supabase-auth).

## Clerk Organizations

Clerk simplifies authentication and user management implementation, including team structures and granular permissions.

By enabling the Organizations feature in the Clerk dashboard, you empower your users to create teams where they can invite other team members to collaborate. Admin users can also manage roles and permissions at the individual user level, ensuring each user has the proper level of access.

Clerk applies its signature component-driven design to organizations management, offering pre-built UI elements that streamline team permission workflows.

One such component is the `<OrganizationSwitcher />`. By adding a single line to your codebase, you get a beautiful drop-down that allows users to create and manage their organizations associated members (if they have the administrator role).

![Organization Switcher](./org-switcher.png)

## Using organizations with Supabase

Supabase identifies the party making a request by parsing the claims of the incoming JWT.

For example, the following JSON represents the claims of a Clerk user making a request to Supabase, with the `sub` value representing the user ID:

```json
{
  "azp": "http://localhost:3000",
  "exp": 1748881855,
  "fea": "u:ai_assistant",
  "fva": [7142, -1],
  "iat": 1748881795,
  "iss": "https://modest-hog-24.clerk.accounts.dev",
  "jti": "27bb27d6f16174d6a556",
  "nbf": 1748881785,
  "pla": "u:pro",
  "role": "authenticated",
  "sid": "sess_2xjZ6z85O9Uu2mHhE73Z2JVkh1i",
  "sub": "user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7",
  "v": 2
}
```

By setting an active organization for that user (using the `<OrganizationSwitcher />` or [any other method](https://clerk.com/docs/organizations/overview#active-organization)), that user’s claims are modified to include information about the active organization in the `o` object, including the organization ID. The below claims represent a user with an active organization to compare how it differs from the above claims:

```json
{
  "azp": "http://localhost:3000",
  "exp": 1748881940,
  "fea": "o:articles",
  "fva": [7143, -1],
  "iat": 1748881880,
  "iss": "https://modest-hog-24.clerk.accounts.dev",
  "jti": "fb41714162af8da77347",
  "nbf": 1748881870,
  "o": {
    "id": "org_2rxR3osThxAoZXaE7mWeSj065IB",
    "rol": "admin",
    "slg": "echoes"
  },
  "pla": "o:free_org",
  "role": "authenticated",
  "sid": "sess_2xjZ6z85O9Uu2mHhE73Z2JVkh1i",
  "sub": "user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7",
  "v": 2
}
```

## Parsing the Organization ID value

[Our Supabase integration guide](/docs/guides/development/integrations/databases/supabase) walks you through using the `auth.jwt()` function to extract the `sub` value from the JWT claims of the user making the request. This same function can be used to access the `id` value of the `o` object as well.

Consider the following RLS policy that restricts users to accessing their own records in the `articles` table:

```sql
create policy "Users can view their own articles"
	on public.articles
	for select
using((auth.jwt() ->> 'sub'::text) = user_id);
```

Updating the `using` statement as follows will reference both the `sub` and `o`.`id` values.  When combined with `coalesce`, the policy will first check the `o`.`id` claim and fail back to the `sub` claim if `o` is unavailable (indicating that the user doesn’t have an active organization selected):

```sql
create policy "Users can view their own articles"
	on public.articles
	for select
using(
	coalesce(
    (auth.jwt() -> 'o'::text) ->> 'id'::text,
    (auth.jwt() ->> 'sub'::text)
	) = owner_id
);
```

### Using a dedicated function

If you need to create a number of RLS policies, constantly duplicating code to check for both the `sub` and `o`.`id` values can leave some room for human error. An alternate approach to simplify your policies would be to create a dedicated function that checks both values, returning the first available value.

The following snippet can be pasted in the Supabase SQL Editor to create that function:

```sql
-- Add requesting_owner_id function
create or replace function requesting_owner_id()
returns text as $$
    select coalesce(
        (auth.jwt() -> 'o'::text) ->> 'id'::text,
        (auth.jwt() ->> 'sub'::text)
    )::text;
$$ language sql stable;
```

Once created, the function can be used in your RLS policies like so:

```sql
create policy "Users can view their own articles"
  on public.articles
  for select
	using (requesting_owner_id() = user_id);
```

> \[!NOTE]
> If you are using the legacy method of integrating Clerk with Supabase, update the `requesting_user_id()` function body to achieve the same results.

You are welcome to modify the name of the function and relevant column names (I’m partial to `owner_id`) but this small change allows Supabase to automatically use the active organization, if set, with no further changes to the database while falling back to the user ID.

## A practical example

To demonstrate, let’s walk through an example using Quillmate, an open-source writing tool built with Next.js, Clerk, and Supabase. If you want to follow along, clone the [`article-add-orgs`](https://github.com/bmorrisondev/quillmate/tree/article-add-orgs) branch from GitHub and step through the readme to get the project running on your computer. Otherwise, feel free to continue reading.

### Enabling Organizations in the dashboard

Start by enabling organizations within the Clerk Dashboard by heading to **Configure** > **Settings**, then toggling **Enable organizations**. This simple toggle that enables the use of the organization components with this application.

![Enabling organizations in the Clerk dashboard](./enable-orgs.png)

### Add the `<OrganizationSwitcher />` to the sidebar

Next, add the `<OrganizationSwitcher />` to the sidebar near the bottom. This example also includes the `showOrgSwitcher` flag to the component props to indicate that the sidebar is used within a route for organizations. This will allow the sidebar to be used with URL-based organization switching, which is a method of setting the active organization by [using the organization slug directly within the URL](/docs/organizations/org-slugs-in-urls).

> \[!NOTE]
> Interested in a guide dedicated to switching organizations based on the slug? Let us know in [our feedback portal](https://feedback.clerk.com/roadmap?id=f95553cd-204d-43b8-b2b5-1f84ecf1bd59).

Update the `src/app/(protected)/components/sidebar.tsx` file to match the following:

```tsx {{ filename: 'src/app/(protected)/components/sidebar.tsx', ins: [6, 21, 33, 46, 67, [78, 86]], del: [5, 32, 45, 66], prettier: false }}
'use client'

import { Button } from "@/components/ui/button"
import { type Article } from '@/lib/models'
import { UserButton, useUser } from '@clerk/nextjs'
import { OrganizationSwitcher, useOrganization, UserButton, useUser } from '@clerk/nextjs'
import Link from "next/link"
import { useSupabase } from "@/lib/supabase-provider"
import { toast } from "sonner"
import { useRouter } from "next/navigation"
import { useArticleStore } from "../store"

interface SidebarProps {
  showOrgSwitcher?: boolean
}

type NewArticle = Pick<Article, 'title' | 'content' | 'owner_id'>

export function Sidebar({ showOrgSwitcher = true }: SidebarProps) {
  const { user } = useUser()
  const { organization } = useOrganization()
  const { supabase } = useSupabase()
  const router = useRouter()
  const { articles, isLoadingaddArticle, addArticle } = useArticleStore()

  async function onNewArticleClicked() {
    if (!supabase || !user) return

    const newArticle: NewArticle = {
      title: 'New Article',
      content: '',
      owner_id: user.id
      owner_id: organization.id ?? user.id
    }

    const { error, data } = await supabase
      .from('articles')
      .insert(newArticle)
      .select()
      .single<Article>()

    if (error) {
      toast.error('Failed to create new article')
		} else {
      router.push(`/me/${data.id}`)
      router.push(`${organization ? `/orgs/${organization.slug}` : '/me'}/${data.id}`)
      addArticle(data)
    }
  }

  return (
    <div className="w-64 border-r p-4 flex flex-col">
      <div className="flex items-center gap-2 pb-2">
        <UserButton showName />
      </div>
      <Button onClick={onNewArticleClicked} className="mb-4">
        New Article
      </Button>
      <div className="overflow-y-auto flex-1 flex flex-col">
        {isLoading ? (
          <div className="text-center text-gray-500">Loading articles...</div>
        ) : (
          articles.map((article) => (
            <Link
              key={article.id}
              href={`/me/${article.id}`}
              href={`${organization ? `/orgs/${organization.slug}` : '/me'}/${article.id}`}
              className="px-3 py-2 rounded-md cursor-pointer hover:bg-gray-100"
            >
              <h3 className="font-medium">{article.title || 'Untitled'}</h3>
              <div className="flex items-center gap-2 text-sm text-gray-500">
                <span>{new Date(article.updated_at).toLocaleDateString()}</span>
              </div>
            </Link>
          ))
        )}
      </div>
      {showOrgSwitcher && (
        <OrganizationSwitcher
          hideSlug={false}
          hidePersonal={false}
          afterCreateOrganizationUrl="/orgs/:slug"
          afterSelectOrganizationUrl="/orgs/:slug"
          afterSelectPersonalUrl="/me"
        />
      )}
    </div>
  )
}

```

Within the application, I now have this new dropdown where I can create an organization and invite others to it:

![Organization switcher open in Quillmate](./create-org.png)

### Update the RLS policies

Next, create a migration to add the `requesting_owner_id` function to the database and replace the existing RLS policies to use the function.

Run the following command in the terminal to create the migration file:

```bash
pnpm supabase migration new support_clerk_orgs
```

This will create a file in the `supabase/migrations` directory with the `support_clerk_orgs` prefix. Add the following SQL to that file:

```sql
-- Create the requesting_owner_id function
create or replace function requesting_owner_id()
returns text
language sql
stable
as $$
  select
    coalesce(
      (auth.jwt() -> 'o'::text) ->> 'id'::text,
      (auth.jwt() ->> 'sub'::text)
    );
$$;

-- Update the policies to use requesting_owner_id()
drop policy if exists "Users can view their own articles" on articles;
drop policy if exists "Users can insert their own articles" on articles;
drop policy if exists "Users can update their own articles" on articles;
drop policy if exists "Users can delete their own articles" on articles;

create policy "Users can view their own articles"
on articles for select
using (owner_id = requesting_owner_id());

create policy "Users can insert their own articles"
on articles for insert
with check (owner_id = requesting_owner_id());

create policy "Users can update their own articles"
on articles for update
using (owner_id = requesting_owner_id())
with check (owner_id = requesting_owner_id());

create policy "Users can delete their own articles"
on articles for delete
using (owner_id = requesting_owner_id());
```

Finally, apply the migration to the database by executing the following command in your terminal:

```bash
pnpm supabase db push
```

### Testing the changes

With the above changes in place, it's time to test by creating an “article” (which is the main entity of Quillmate) in your personal account and an organization.

Start the project by running the following command:

```bash
pnpm dev
```

Open the application in your browser using the URL displayed in your terminal. Note how the `<OrganizationSwitcher />` displays “Personal account” to indicate that you do not have an active organization selected.

Click the **New Article** button in the sidebar to create an article while in the personal account to populate the database with some data. The editor supports markdown syntax and setting an H1 will automatically set the name of the article in the sidebar.

In my environment, I have a single article named “Hello world” in this tenant:

![Hello world article in personal account](./personal-article.png)

Use the `<OrganizationSwitcher />` to create an organization and switch to it. Your list of articles should be blank - create another article here as well.

In my environment, I have a test organization named “D2 Frontiers”, where I have a completely different article. This is because Supabase is returning all records with an `owner_id` that matches the value of the `o`.`id` value in the claims:

![Article in an active organization](./org-article.png)

Next, access the organization settings by opening the `<OrganizationSwitcher />` and selecting **Manage** in the header. Navigate to **Members** and invite another user so they can access the application as well. I recommend using an alternate email address if you have one.

As an example, I’ve switched to a completely different user account that also has access to the “D2 Frontiers” organization and can access the same article!

![Article in an active organization as another user](./org-article-alt.png)

Back in the database, the values in the `owner_id` column are different depending on the owner type.

Note that the article created with my personal account starts has an `owner_id` starting with `user_` and the one created with the organization active starts with `org_`:

![Owner ID values in the database](./owner-id.png)

## Conclusion

Using Clerk with Supabase unlocks multi-tenancy and team-based functionality in your application in a matter of minutes with just a few small changes to the code. This empowers your users to create their own groups where individuals can be invited to collaborate within your application.

\* [https://www.rocket.chat/blog/collaboration-software](https://www.rocket.chat/blog/collaboration-software)

---

# How Clerk integrates with a Next.js application using Supabase
URL: https://clerk.com/blog/how-clerk-integrates-nextjs-supabase.md
Date: 2025-03-31
Category: Company
Description: Learn how Supabase works with Next.js to increase security and reduce development hours, and how Clerk integrates with this stack.

Supabase is one of the most popular backend platforms on the web, but it breaks from traditional architecture patterns when accessing data.

Full-stack applications typically have distinct frontend and backend codebases. When a user needs to access or modify data, the frontend proxy requests through the backend to the database. This prevents third parties from obtaining the connection string used for database operations.  In comparison, Supabase allows you to build applications *without* a backend, yet still keep your data safe.

In this article, you’ll learn how Supabase functions with Next.js applications, and how Clerk integrates with this configuration.

This article assumes you have [familiarity with Next.js and Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs), and have ideally built an application using them both.

## How Supabase works with Next.js applications

Supabase provides a way to access data from the client directly without going through your own backend.

As a full-stack JavaScript framework, Next.js supports accessing Supabase through server-side code, but most developers take advantage of the Supabase API to gain direct access to the underlying data. In this setup, the client will request data from the database through the API and include an authorization token so that the service can associate the request with a specific user.

To protect the data, Supabase uses a feature of Postgres (the underlying database engine) called Row Level Security (RLS). RLS is a way to secure database tables on a per-record by using policies to evaluate each request and determine if the user making a data access/modification request is authorized to do so on the specific rows.

> \[!NOTE]
> To learn a more about how the Supabase API works with RLS, check out [our article comparing Supabase Auth and Clerk](/blog/how-clerk-integrates-with-supabase-auth) where we dive deeper into both implementations.

## How Clerk integrates with Next.js and Supabase

When a user signs in with Clerk, they receive short-lived tokens that are used by backend services to validate requests. In a traditional configuration, this would be the backend code you write for your application. As stated in the previous section, applications using Supabase often bypass any custom backend code and send requests directly to Supabase, so there are a few considerations to be taken so that Clerk will work properly with Supabase.

### Verifying JWTs with the JWKS endpoint

In the traditional configuration, Clerk’s SDKs will automatically verify your request using the public key associated with Clerk application.

Supabase does not support this since you do not control the backend code that powers the Supabase APIs. However, Supabase does support integrating with Clerk as a third party authentication provider, where JWTs sent to the Supabase backend will automatically be verified with your Clerk application using the JWKS endpoint for that application.

A JSON Web Key Set (JWKS) is a JSON object that contains a set of keys used to verify the signatures of JWTs. This object is often publicly available through a standard URL so they can be used by external systems and integrations to perform this verification.

The following diagram shows what this flow looks like:

![JWT Verification Flow](./diagram.png)

1. The user signs in using Clerk
2. Clerk issues JWT to the user
3. User makes request to Supabase, including JWT
4. Supabase verifies with the Clerk application JWKS endpoint
5. Clerk verifies token
6. Supabase response to the user with the requested data

### Setting the correct role

Supabase supports a number of different roles which each provide different levels of access for managing database and storage operations. The role which the request assumes is set in the JWT itself as the `role` claim.

To adhere to Supabase standards, the `role` in the JWT should be set to `authenticated` so that the request has the correct level of security. The following snippet shows what the claims of the JWT are when properly configured:

```json
{
  "app_metadata": {},
  "aud": "authenticated",
  "azp": "http://localhost:3000",
  "email": "brian@clerk.dev",
  "exp": 1742938129,
  "iat": 1742938069,
  "iss": "https://modest-hog-24.clerk.accounts.dev",
  "jti": "01a722552fe233fc649b",
  "nbf": 1742938064,
  "role": "authenticated", // Note the 'role' claim
  "sub": "user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7"
}
```

### Using the Clerk JWT for requests to Supabase

Clerk stores its token in cookies that automatically get sent with every request in the same domain. Without some infrastructure considerations, your Supabase application is likely not going to be accessible on the same domain as your application.

Fortunately, you can obtain the current session token using the `getToken` function of the SDK:

```tsx
import { useSession } from '@clerk/nextjs'

const { session } = useSession()
session.getToken()
```

When creating a Supabase client in your front end, you can provide the token created using the JWT template into the `accessToken` option of the Supabase client:

```tsx
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_KEY!,
  {
    // Use the session.getToken() method from Clerk
    accessToken: () => session?.getToken(),
  },
)
```

This will ensure that each request sent to Supabase will contain the custom token created with the JWT secret so that Supabase can validate it, extract the claims, and use it to access the requested data.

> \[!NOTE]
> Interested in more content featuring Supabase? [Let us know!](https://feedback.clerk.com/roadmap?id=f95553cd-204d-43b8-b2b5-1f84ecf1bd59)

### Using the Clerk user ID in RLS policies

Once the token has been verified, the claims within that token are accessible to Postgres using the built-in `auth.jwt()` function (used to access the JWT claims) and accessing the `sub` claim.

For example, say a `tasks` table has the following RLS policy defined:

```sql
create policy "select_by_user_id" on tasks
for select using (auth.jwt()->>'sub' = user_id);
```

Assuming the Clerk user ID is `user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7`, running a `select` statement will effectively apply a filter to restrict what data is returned as shown below:

```sql
select * from tasks;
-- Turns into this:
select * from tasks where user_id = 'user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7';
```

This can provide an additional layer of security for applications with a dedicated backend, or save the developer from having to build one in the first place, if implemented properly.

## Examining an implementation of Next.js using Clerk and Supabase

To see this implemented in a real-world application, we’ll explore the code for Quillmate. Quillmate is an open-source, web-based writing tool built with Next.js, Supabase, and Clerk.

> \[!NOTE]
> The source code for Quillmate is available [on GitHub](https://github.com/bmorrisondev/quillmate).

### Configuring the integration

To integrate Supabase with Clerk, you’d start in the Supabase Dashboard and navigate to **Authentication** > **Sign In/Up** > **Third Party Auth** and add Clerk as the provider. The modal that appears will prompt you for a **Clerk Domain**, but also contains a link to open the [Supabase Integration Setup page in Clerk](https://dashboard.clerk.com/setup/supabase) to select your application and enable the integration:

![Supabase Integration Setup](./supabase-dash.png)

Once completed, you’ll be provided with the Clerk Domain for your application and instance. You simply need to copy and paste this into the Supabase Dashboard and click **Create connection**.

![Supabase Integration Setup](./connect-supabase.png)

Supabase now knows that when a JWT is received that was issued by Clerk, it should use the JWKS endpoint available on the provided Clerk Domain for verification. On the Clerk side, the session claims have been updated to include `"role": "authenticated"` with all new JWTs created:

![Managed claim](./managed-claim.png)

### Accessing Supabase with a Clerk JWT

The following code demonstrates how the JWT template is accessed when creating the Supabase client. This code uses the React Context API to simplify creating and accessing the client with the Clerk `getToken` function, but you may create the client however you need as long as you can pass in the `getToken` function:

```tsx
'use client'

import { createClient, SupabaseClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/nextjs'
import { createContext, useContext, useEffect, useState } from 'react'

type SupabaseContext = {
  supabase: SupabaseClient | null
  isLoaded: boolean
}

const Context = createContext<SupabaseContext>({
  supabase: null,
  isLoaded: false,
})

type Props = {
  children: React.ReactNode
}

export default function SupabaseProvider({ children }: Props) {
  const { session } = useSession()
  const [supabase, setSupabase] = useState<SupabaseClient | null>(null)
  const [isLoaded, setIsLoaded] = useState(false)

  useEffect(() => {
    if (!session) return

    const client = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_KEY!,
      {
        accessToken: () => session?.getToken(),
      },
    )

    setSupabase(client)
    setIsLoaded(true)
  }, [session])

  return (
    <Context.Provider value={{ supabase, isLoaded }}>
      {!isLoaded ? <div>Loading...</div> : children}
    </Context.Provider>
  )
}

export const useSupabase = () => {
  const context = useContext(Context)
  if (context === undefined) {
    throw new Error('useSupabase must be used within a SupabaseProvider')
  }
  return {
    supabase: context.supabase,
    isLoaded: context.isLoaded,
  }
}
```

For the sake of this explanation, the only table of interest is the `articles` table. The `articles` table itself has a relatively simple schema:

```sql
create table if not exists public.articles (
  id bigint primary key generated always as identity,
  title text not null,
  content text not null,
  user_id text not null,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);
```

Quillmate will request a list of records from the `articles` table using the following code. Note that I am not leveraging the user ID within these queries. This is because the Clerk/Supabase integration will automatically parse the user ID from the request.

```tsx
const { data, error } = await supabase
  .from('articles')
  .select('*')
  .order('updated_at', { ascending: false })
```

And is protected from the following RLS policies. Note the use of `auth.jwt()->>'sub'` which extracts the `sub` claim (which is the user ID) from the JWT used on the request and compares it with the `user_id` column in the table.

```sql
CREATE POLICY "Users can view their own articles"
  ON public.articles
  FOR SELECT
  USING (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can create their own articles"
  ON public.articles
  FOR INSERT
  WITH CHECK (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can update their own articles"
  ON public.articles
  FOR UPDATE
  USING (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can delete their own articles"
  ON public.articles
  FOR DELETE
  USING (auth.jwt()->>'sub' = user_id);
```

## Conclusion

Building an application with Supabase can challenge the architectural norms that developers have used for decades. However, once you properly understand how the data is accessed and secured by Supabase, building with this new approach can save development time and potentially increase table security by ensuring that each request is evaluated with RLS on a row-by-row basis.

This includes B2B applications, which is covered in our follow up article on [building multi-tenancy applications with Supabase and Clerk](/blog/multitenancy-clerk-supabase-b2b).

---

# How Clerk integrates with Supabase
URL: https://clerk.com/blog/how-clerk-integrates-with-supabase-auth.md
Date: 2025-03-31
Category: Guides
Description: Learn how Supabase Auth works and how Clerk can provide more capabilities in less time.

While Supabase Auth is the default choice for Supabase-powered apps, Clerk is a drop-in replacement offering the same simplicity with an enhanced feature set at your disposal and takes less time to implement. On top of reducing development time, Clerk offers much more than just authentication, from beautifully designed UI components to our suite of B2B tools for multi-tenant applications.

In this article, we’ll compare Supabase Auth with Clerk, looking at the differences in implementation and covering some of the additional capabilities offered by Clerk.

To follow along with this post, you should have a [basic understanding of Supabase](https://supabase.com/docs/guides/getting-started), ideally having built an application with Supabase as the backend.

## How Supabase Auth works

Supabase provides an easy approach to add authentication into your application. To properly compare it with Clerk, it’s important to understand how Supabase Auth is implemented.

A default Supabase project comes pre-configured with an `auth` schema in the underlying Postgres instance that stores a list of users and their credentials. This schema is used by the Supabase client SDK, specifically the helper functions which are used with your own forms to simplify the sign up and sign in processes.

The following snippet demonstrates the respective functions used with those forms:

```ts
// Sign-up
const { error } = await supabase.auth.signUp({
  email,
  password,
  options: {
    emailRedirectTo: `${location.origin}/auth/callback`,
  },
})

// Sign-in
const { error } = await supabase.auth.signInWithPassword({
  email,
  password,
})
```

Upon signing in, the service will mint a JWT and return it to the caller, which is automatically stored locally by the client SDK. Subsequent requests to Supabase will include the JWT so that the Supabase backend can authenticate the request. When Supabase receives an authenticated request, it will parse the claims from the JWT so it can be used with Row Level Security to prevent unauthorized access to data within the database.

The following diagram shows what this flow looks like:

![Auth diagram](./diagram.png)

1. The user signs in using Clerk
2. Clerk issues JWT to the user
3. User makes request to Supabase, including JWT
4. Supabase verifies with the Clerk application JWKS endpoint
5. Clerk verifies token
6. Supabase response to the user with the requested data

## How Row Level Security works with Supabase Auth

Row Level Security (RLS) is a feature of Postgres that allows you to control access to data by requiring certain criteria to match before the query will be processed.

RLS is enabled and configured on a per-table basis. When enabled, you can define RLS policies which will be evaluated by the database engine before any data in that table is read or modified.

The following snippet defines an RLS policy on the `articles` table that ensures only records with “published” in the `status`  column are returned:

```sql
create policy "Users can read published articles"
  on public.articles
  for select
  using (status = 'published');
```

Using the above policy as an example, Postgres will effectively transform the following query:

```sql
select * from articles;
```

Into:

```sql
select * from articles where status = 'published';
```

Supabase Auth uses RLS policies in a similar fashion but uses a helper function to identify who is making the request. When you’re using Supabase Auth, you have access to the `auth.uid()` function which returns the unique identifier for the user currently signed-in.

Building on the same example policy above, the following RLS policy verifies the user ID and only returns data belonging to that user:

```sql
create policy "Users can view their own articles"
  on public.articles
  for select
  using (auth.uid()::text = user_id);
```

### Why RLS?

Traditional applications have a dedicated backend system with custom logic to verify the requests being sent. This is normally where you parse the session or user ID from the request and adjust your queries (whether using SQL or an ORM) accordingly to prevent the user from accessing data they shouldn’t.

> \[!NOTE]
> If you want to further understand how authentication is implemented in a traditional configuration, check out our article on [Building a React login page template](/blog/building-a-react-login-page-template), which walks you through building session-based authentication from scratch.

While this same configuration can be set up with Supabase, most developers take advantage of the PostgREST API built into Supabase to save on the hours they’d otherwise spend building and maintaining the backend. PostgREST is a feature of Postgres that provides an API to access the database tables directly over HTTP.

The tradeoff of using PostgREST is that you don’t have access to modify the API logic itself. You instead have to rely on RLS to prevent users from accessing data they shouldn’t.

## Clerk as a drop-in replacement to Supabase Auth

Clerk’s integration with Supabase functions almost exactly like Supabase Auth with a few minor differences that are transparent when configured properly.

Supabase Auth automatically uses its own keys used to mint JWTs. Like Supabase Auth, Clerk employs application-specific JWT keys. Since they are different keys, they are incompatible with each other without additional configuration.

Fortunately, Supabase allows you to provide a JSON Web Key Set (JWKS) URL that Supabase can to verify incoming JWTs, and each Clerk application has a dedicated JWKS endpoint with these keys publicly available for this purpose.

A JSON Web Key Set (JWKS) is a JSON object that contains a set of keys used to verify the authenticity of JWTs. The JWKS is available by identity providers (like Clerk) through a public URL to be used with third party services for integrations.

### Connecting Clerk with Supabase

Clerk is a supported third-party authentication provider for Supabase which can be added in the Supabase dashboard, via **Authentication** > **Sign In/Up** > **Third Party Auth**. When adding Clerk as a provider, a modal will appear asking for the Clerk Domain, which is used to access the JWKS endpoint for your application.

From that modal, you can access Clerk's [Supabase Integration Setup page](https://dashboard.clerk.com/setup/supabase), where you can select your application and enable the integration. Once you enable the Supabase integration, all JWTs created by Clerk will include the `"role": "authenticated"` claim which Supabase uses to determine whether the user is authenticated.

The following image shows the setup page with the integration enabled:

![Supabase Integration Setup](./connect-supabase.png)

You can then use the domain provided in the **Clerk Domain** field (pictured above) when prompted. Supabase will automatically verify JWTs with Clerk instead of its own keys going forward.

![Add new Clerk connection in Supabase](./add-clerk.png)

Another difference is in the way that the user ID is referenced within RLS policies. Clerk uses string-based identifiers whereas Supabase uses UUIDs. Since the `auth.uid()` function returns the UUID for the current user, this function cannot be used when accessing Supabase data with Clerk.

You’d instead use the `auth.jwt()` function to access data within the JWT, specifically the `sub` claim which corresponds to the ID of the user:

```sql
create policy "Users can view their own articles"
  on public.articles
  for select
  using (auth.jwt()->>'sub' = user_id);
```

Finally, when creating the Supabase client within your application, you’d use the Clerk `session` object to request a JWT that is compatible with Supabase, which is the JWT template that uses your Supabase signing key.

When using `session.getToken()` for the `accessToken` parameter, requests to Supabase will use the correct JWT created by Clerk:

```tsx
import { createClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/clerk-react'

const { session } = useSession()

const client = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_KEY!,
  {
    accessToken: () => session?.getToken(),
  },
)
```

> \[!NOTE]
> If you are using Next.js, you might be interested in our follow up article on [how Clerk integrates with Next.js and Supabase](/blog/how-clerk-integrates-nextjs-supabase).

## What other benefits does Clerk include?

Using Clerk provides a host of other benefits layered on top of [adding authentication](/nextjs-authentication) to your application.

As a developer, you can quickly configure various authentication strategies, including social sign-ons, passkeys, and email links, beyond the traditional username and password, accommodating your users based on their preferences.

Clerk also provides drop-in UI components that make it easy to extend user management in your application, often with just a single line of code. For example, adding our `<UserButton />` component to your navigation provides users a great experience for managing various aspects of their profile such as updating their sign-in providers, resetting their password, or even remotely signing out other devices.

![User Button](./user-button.jpeg)

If you are building a multi-tenant application, our suite of B2B tools makes it easy for you to quickly add teams and organizations to your application. The `<OrganizationSwitcher />` component enables users to create organizations, invite others, and set permissions, ensuring access is limited to what each user needs.

![Organization Switcher](./organization-switcher.jpeg)

## Conclusion

With just a bit more configuration, Clerk can not only act as a drop-in replacement for Supabase Auth but also provides more capabilities out of the box with access to a large number of commonly required user management features.

And because of our component-first approach to features built with Clerk, you can get up and running with authentication [in as little as 2 minutes](https://www.youtube.com/watch?v=QstMsE_HbgM).

---

# Next.js CVE-2025-29927
URL: https://clerk.com/blog/cve-2025-29927.md
Date: 2025-03-23
Category: Company
Description: On March 21, 2025, Next.js disclosed a critical security vulnerability, CVE-2025-29927, that may impact your application.

On March 21, 2025, Next.js disclosed a critical security vulnerability, [CVE-2025-29927](https://nextjs.org/blog/cve-2025-29927), that may impact your application.

This vulnerability allows attackers to bypass middleware-based authentication and authorization protections, potentially allowing unauthorized access to your application.

## Impacted applications

> \[!IMPORTANT]
> If your application is not using Next.js, or if it is hosted on Vercel or Netlify, it is not impacted.

Yesterday, we mistakenly [announced on X](https://x.com/clerk/status/1903497002828120426) that all applications using Clerk were not impacted. Since then, we have discovered two scenarios where your application may be impacted.

1. You use Clerk's middleware for protecting routes that do not directly read user data. This is most likely to impact static applications that solely rely on middleware for authentication checks. **If you call any of the following methods in routes protected by middleware, your page or endpoint is safe:**
   - `auth()`
   - `getAuth()`
   - `protect()`
   - `currentUser()`

2. Or, you have not upgraded to `@clerk/nextjs@5.2` or higher, which was released in June 2024.

## Patching your application

If your application is impacted, the remediation is to upgrade your `next` package as follows:

- For Next.js 15.x, this issue is fixed in `15.2.3` forward
- For Next.js 14.x, this issue is fixed in `14.2.25` forward
- For Next.js 13.x, this issue is fixed in `13.5.9` forward

If patching to a safe version is infeasible, it is recommended that you prevent external user requests which contain the `x-middleware-subrequest` header from reaching your Next.js application. If your application uses Cloudflare, this can safely be accomplished with a [Managed WAF](https://developers.cloudflare.com/changelog/2025-03-22-next-js-vulnerability-waf/) rule.

Even if you are not impacted, we strongly recommend that you upgrade to the latest versions of `next` and `@clerk/nextjs`.

## Additional support is available

We have also sent an email to the administrators of all Clerk applications that are potentially impacted by this vulnerability. If you have questions, need help determining whether your application is at risk, or need help with mitigation, reply to the email you received or reach out directly to [support@clerk.com](mailto:support@clerk.com)

## Security at Clerk

Our announcement on X that Clerk applications were not impacted was a significant error. We apologize, and will be reflecting on and improving our procedures for zero-day vulnerabilities to ensure it does not happen again.

Going forward, we are pleased that the Next.js team has committed to giving Clerk advance notice on vulnerabilities. We will be seeking similar relationships with other framework authors.

---

# Build a blog with tRPC, Prisma, Next.js and Clerk
URL: https://clerk.com/blog/build-a-blog-with-trpc-prisma-nextjs-clerk.md
Date: 2025-03-14
Category: Guides
Description: Learn how to work with tRPC, Prisma, Next.js, and Clerk by building a secure blog application

In this tutorial, you'll build a blog app from scratch using many modern and popular technologies such as Next.js, Clerk, tRPC, Prisma, and more. After reading this guide, you'll have a simple blog application that allows users to create and read posts.

Let's explore the various technologies used in the article:

- Next.js is the React framework used throughout this guide, specifically using the [App Router](/docs/quickstarts/nextjs).
- Clerk is used for [user management](/docs/user-authentication) and [authentication](/docs/nextjs-authentication).
- Prisma will be the ORM used to connect to the database.
- Vercel is used for hosting and automated deployments.
- Neon is a serverless Postgres database, and you'll actually use Vercel to automate deployment of the database as well.
- Tanstack Query simplifies the process of fetching and caching data.
- [tRPC](/docs/references/nextjs/trpc) provides a type-safe API endpoint wrapper around Tanstack Query.
- Zod is used for schema validation.
- Tailwind provides a simple and modern way to style your app with CSS.

You'll start by creating a Next.js app and integrating Clerk into it for authentication. Then you'll deploy the application to Vercel, where you will also create a Neon database that will be used by Prisma to access and manipulate data within that database. At this point the application will be fully functional, however the rest of the tutorial further enhances the type-safety of your app by adding Tanstack Query, tRPC, and zod. Finally, you'll learn how to create protected procedures using Clerk's authentication context.

To follow along, you should have Node.js installed on your computer and a Vercel account. Familarity with React and Next.js is recommended as well.

> Check out the finished product in Clerk's demo repository:
> [https://github.com/clerk/clerk-nextjs-trpc-prisma](https://github.com/clerk/clerk-nextjs-trpc-prisma)

## Setting up a Next.js application with Clerk

Start by opening your terminal and running the following command to create a new Next.js application. When prompted for the various configuration options, use the options specified below:

```bash
npx create-next-app@latest

# Use the following configuration
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … No
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like your code inside a `src/` directory? … No
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to use Turbopack for `next dev`? … Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No
```

Once the application has been created, follow the [quickstart in the docs](https://clerk.com/docs/quickstarts/nextjs) to add Clerk to it.

Alternatively, you can clone the [Clerk Next.js quickstart repository](https://github.com/clerk/clerk-nextjs-app-quickstart). This repository contains a pre-configured Next.js app with Clerk already added in keyless mode, which allows you to test the authentication features in your app locally without having to create an account.

```bash
git clone https://github.com/clerk/clerk-nextjs-app-quickstart
```

### Create a Clerk application

Since keyless mode only works for local development, you will want to create a Clerk account and an application in the [dashboard](https://dashboard.clerk.com) to deploy your application to Vercel.

The Clerk Dashboard is where you, as the application owner, can manage your application's settings, users, and organizations. For example, if you want to enable phone number authentication, multi-factor authentication, social providers like Google, delete users, or create organizations, you can do all of this and more in the Clerk Dashboard.

### Set your Clerk API keys

You need to set your Clerk API keys in your app so that your app can use the configuration settings that you set in the Clerk Dashboard.

1. In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page.
2. In the **Quick Copy** section, copy your Clerk Publishable and Secret Keys.
3. In your `.env` file, set the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables to the values you copied from the Clerk Dashboard.

```env
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY={{pub_key}}
CLERK_SECRET_KEY={{secret_key}}
```

## Install dependencies and test your app

While developing, it's best practice to keep your project running so that you can test your changes as you work. So, let's make sure the app is working as expected.

1. Run the following commands to install the dependencies and start the development server:
   ```bash
   npm install
   npm run dev
   ```
2. Open your browser and navigate to the URL displayed in your terminal. The default is `http://localhost:3000` and will be used through the remainder of the tutorial. It should render a new Next.js app, but with a "Sign in" and "Sign up" button in the top right corner.
   ![The development instance running.](./one.png)
3. Select the "Sign in" button. You should be redirected to your Clerk [Account Portal sign-in](https://clerk.com/docs/account-portal/overview#sign-in) page, which renders Clerk's [`<SignIn />`](https://clerk.com/docs/components/sign-in) component. The `<SignIn />` component will look different depending on the configuration of your Clerk instance.
   ![A Clerk Account Portal sign-in page.](./two.png)
4. Sign in to your Clerk application.
5. You should be redirected back to your app, where you should see Clerk's [`<UserButton />`](https://clerk.com/docs/components/user/user-button) component in the top right corner.

## Install Prisma ORM

Run the following command to install Prisma:

```bash
npm install prisma --save-dev
```

Then run `npx prisma init` to initialize Prisma in your project.

```bash
npx prisma init
```

This will create a new `prisma` directory in your project, with a `schema.prisma` file inside of it. The `schema.prisma` file is where you will define your database models.

The `prisma init` command will also update your `.env` file to include a `DATABASE_URL` environment variable, which is used to store your database connection string. If you have a database already, great! If not, let's spin one up using Vercel.

## Deploy to Vercel

Before you can create a database using Vercel, you first need to deploy your app to Vercel.

1. Create a repository on GitHub for your app. If you're not sure how to do this, follow the [GitHub docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository).
2. Go to [Vercel](https://vercel.com) and add a new project. While going through the process, select the **Environment Variables** dropdown, and add your Clerk Publishable and Secret Keys.
   ![Vercel dashboard showing where to input environment variables](./three.png)
3. Select **Deploy** to deploy your app to Vercel.
4. Select the **Settings** tab.
5. In the left sidenav, select **Functions**.
6. Under **Function Region**, there should be a tag next to one of the continents. Select the continent where the tag is, and the dropdown will reveal what regions on Vercel's network that your Vercel Functions will execute in. Take note of the region. Keep the Vercel dashboard open.
   ![Vercel dashboard with an arrow pointing to a tag that says "iad1", and an arrow pointing to a highlighted element that says "Washington, D.C., USA (EAST) - us-east-1 - iad1"](./four.png)

## Spin up a database

1. While still in Vercel's dashboard, select the **Storage** tab.
2. Select **Create Database**.
3. Select **Neon** as the database provider and select **Continue**.
4. Select the **Region** dropdown and select the region you noted earlier. You want your database's region to match your Vercel Functions region for optimal performance.
5. Select **Continue**.
6. Copy the environment variables and add them to your `.env` file. They should look something like this:

```env
# Recommended for most uses
DATABASE_URL=***

# For uses requiring a connection without pgbouncer
DATABASE_URL_UNPOOLED=***

# Parameters for constructing your own connection string
PGHOST=***
PGHOST_UNPOOLED=***
PGUSER=***
PGDATABASE=***
PGPASSWORD=***

# Parameters for Vercel Postgres Templates
POSTGRES_URL=***
POSTGRES_URL_NON_POOLING=***
POSTGRES_USER=***
POSTGRES_HOST=***
POSTGRES_PASSWORD=***
POSTGRES_DATABASE=***
POSTGRES_URL_NO_SSL=***
POSTGRES_PRISMA_URL=***
```

## Create a database model

Now that your database is created and connected to your app, it's time to create a database model. The main entity of the application is a `Post` that represents each entry in the blog app, so add the following `Post` model to your `schema.prisma` file:

```prisma
model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  authorId  String
}
```

## Update your database schema

Run the following command to apply your schema to your database:

```bash
npx prisma migrate dev --name init
```

This creates an initial migration creating the `Post` table and applies that migration to your database.

## Set up Prisma Client

Now it's time to set up the Prisma Client and connect it to your database. You'll want to create a single client and bind it to the `global` object so that only one instance of the client is created in your application. This helps resolve issues with hot reloading that can occur when using Prisma with Next.js in development mode.

Create the `lib/prisma.ts` file and add the following code to it:

```ts {{ filename: 'lib/prisma.ts' }}
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

const globalForPrisma = global as unknown as { prisma: typeof prisma }

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

export default prisma
```

### Query your database

Now that all of the set up is complete, it's time to start building out your app!

Let's start with your homepage. Replace the contents of `app/page.tsx` with the following code:

```tsx {{ filename: 'app/page.tsx' }}
import Link from 'next/link'
import prisma from '@/lib/prisma'

export default async function Page() {
  const posts = await prisma.post.findMany() // Query the `Post` model for all posts

  return (
    <div className="mx-auto mt-8 flex min-h-screen max-w-2xl flex-col">
      <h1 className="mb-8 text-4xl font-bold">Posts</h1>

      <div className="mb-8 flex max-w-2xl flex-col space-y-4">
        {posts.map((post) => (
          <Link
            key={post.id}
            href={`/posts/${post.id}`}
            className="flex flex-col rounded-lg px-2 py-4 transition-all hover:bg-neutral-100 hover:underline dark:hover:bg-neutral-800"
          >
            <span className="text-lg font-semibold">{post.title}</span>
            <span className="text-sm">by {post.authorId}</span>
          </Link>
        ))}
      </div>

      <Link
        href="/posts/create"
        className="inline-block rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
      >
        Create New Post
      </Link>
    </div>
  )
}
```

This code fetches all posts from your database and displays them on the homepage, showing the title and author ID for each post. It uses the [`prisma.post.findMany()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference?utm_source=docs#findmany) method, which is a Prisma Client method that retrieves all records from the database.

That shows how to query for all records, but how do you query for a single record?

### Query a single record

Let's add a page that displays a single post. This page uses the URL parameters to get the post's ID, and then fetches it from your database and displays it on the page, showing the title, author ID, and content. It uses the [`prisma.post.findUnique()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference?utm_source=docs#findunique) method, which is a Prisma Client method that retrieves a single record from the database.

Create the `app/posts/[id]/page.tsx` file and paste the following code in it:

```tsx {{ filename: 'app/posts/[id]/page.tsx' }}
import prisma from '@/lib/prisma'

export default async function Post({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const post = await prisma.post.findUnique({
    where: { id: parseInt(id) },
  })

  if (!post) {
    return (
      <div className="mx-auto mt-8 flex min-h-screen max-w-2xl flex-col">
        <div>No post found.</div>
      </div>
    )
  }

  return (
    <div className="mx-auto mt-8 flex min-h-screen max-w-2xl flex-col">
      {post && (
        <article className="w-full max-w-2xl">
          <h1 className="mb-2 text-2xl font-bold sm:text-3xl md:text-4xl">{post.title}</h1>
          <p className="text-sm sm:text-base">by {post.authorId}</p>
          <div className="prose prose-gray prose-sm sm:prose-base lg:prose-lg mt-4 sm:mt-8">
            {post.content || 'No content available.'}
          </div>
        </article>
      )}
    </div>
  )
}
```

Test the page by navigating to a post's URL. For example, `http://localhost:3000/posts/1`. For now, it should show a "No post found" message because you haven't created any posts yet. Let's add a way to create posts.

### Create a new post

Next you'll create a page that allows users to create new posts. This page uses Clerk's [`auth()`](https://clerk.com/docs/references/nextjs/auth) helper to get the user's ID. It is a helper that is specific to Next.js App Router, and it provides authentication information on the server side.

- If there is no user ID, the user is not signed in, so a sign in button is displayed.
- If the user is signed in, the "Create New Post" form is displayed. When the form is submitted, the `createPost()` function is called. This function creates a new post in the database using the [`prisma.post.create()`](https://www.prisma.io/docs/orm/reference/prisma-client-reference?utm_source=docs#create) method, which is a Prisma Client method that creates a new record in the database.

Create the `app/posts/create/page.tsx` file and paste in the following code:

```tsx {{ filename: 'app/posts/create/page.tsx' }}
import Form from 'next/form'
import prisma from '@/lib/prisma'
import { redirect } from 'next/navigation'
import { SignInButton, useAuth } from '@clerk/nextjs'
import { revalidatePath } from 'next/cache'
import { auth } from '@clerk/nextjs/server'

export default async function NewPost() {
  const { userId } = await auth()

  // Protect this page from unauthenticated users
  if (!userId) {
    return (
      <div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center space-y-4">
        <p>You must be signed in to create a post.</p>
        <SignInButton>
          <button
            type="submit"
            className="inline-block cursor-pointer rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
          >
            Sign in
          </button>
        </SignInButton>
      </div>
    )
  }

  async function createPost(formData: FormData) {
    'use server'

    // Type check
    if (!userId) return

    const title = formData.get('title') as string
    const content = formData.get('content') as string

    await prisma.post.create({
      data: {
        title,
        content,
        authorId: userId,
      },
    })

    revalidatePath('/')
    redirect('/')
  }

  return (
    <div className="mx-auto max-w-2xl p-4">
      <h1 className="mb-6 text-2xl font-bold">Create New Post</h1>
      <Form action={createPost} className="space-y-6">
        <div>
          <label htmlFor="title" className="mb-2 block text-lg">
            Title
          </label>
          <input
            type="text"
            id="title"
            name="title"
            placeholder="Enter your post title"
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <div>
          <label htmlFor="content" className="mb-2 block text-lg">
            Content
          </label>
          <textarea
            id="content"
            name="content"
            placeholder="Write your post content here..."
            rows={6}
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <button
          type="submit"
          className="inline-block w-full rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
        >
          Create Post
        </button>
      </Form>
    </div>
  )
}
```

Test the page by navigating to the `/posts/create` page (ex: `http://localhost:3000/posts/create`) and create a new post. You should be redirected to the homepage, where you should see the new post.

# Configure tRPC, `@tanstack/react-query`, and `zod`

Now, you've got a Next.js, Clerk, and Prisma app that can create and display posts. You could stop here and have a perfectly functional app. But let's take it a step further and add tRPC to your app for type-safe API endpoints.

### Install the dependencies

Let's start by installing the following dependencies:

- `trpc` is a wrapper around your API endpoints to make them type-safe and easier to use.
- `zod` is a schema validation library, also used to enhance your app's type safety.
- `@tanstack/react-query` is a library for data fetching and caching.

Run the following command to install the packages:

```bash
npm i @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zod --force
```

> At the time of writing, `clerk-next-app` includes React 19 as a peer dependency, but `@tanstack/react-query` does not. So, you'll need to use the `--force` flag when running the command above. You may not need the `--force` flag in the future.

## Create a tRPC server

Now, you'll configure tRPC for your app. You'll start by initializing a tRPC server that creates a `router` and `publicProcedure` that you can use to create your API endpoints.

Create the `app/server/trpc.ts` file and paste in the following code

```ts {{ filename: 'app/server/trpc.ts' }}
import { initTRPC } from '@trpc/server'

const t = initTRPC.create()

export const router = t.router
export const publicProcedure = t.procedure
```

### Create a tRPC endpoint

Now, you'll create a router that's going to have your procedures on it. The following code creates a router with a `getPosts` procedure that uses the tRPC `publicProcedure` you created in the previous step to make a query using [tRPC's `query()` method](https://trpc.io/docs/server/procedures). The query then uses Prisma to query the `Post` model in your database. That part should look familiar, because you've used `prisma.post.findMany()` in your app earlier!

Create the `app/server/routers/posts.ts` file and paste in the code below:

```ts {{ filename: 'app/server/routers/posts.ts' }}
import { publicProcedure, router } from '../trpc'
import prisma from '@/lib/prisma'

export const postRouter = router({
  getPosts: publicProcedure.query(async () => {
    return await prisma.post.findMany()
  }),
})

export type PostRouter = typeof postRouter
```

This is the file where you'll add all of your queries and mutations, so you'll probably update this file frequently as you build out your app.

### Connect the tRPC router to your App Router

Now you need to connect the tRPC router to your App Router. You'll use a Route Handler that uses [tRPC's `fetchRequestHandler()` method](https://trpc.io/docs/server/adapters/fetch#nextjs-edge-runtime) to pass requests from Next.js to the tRPC router.

Create the `app/api/trpc/[trpc]/route.ts` file and paste in the code below:

```ts {{ filename: 'app/api/trpc/[trpc]/route.ts' }}
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { postRouter } from '@/app/server/routers/posts'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: postRouter,
    createContext: () => ({}),
  })

export { handler as GET, handler as POST }
```

At this point, your API endpoint should be working. You can test it by navigating to `http://localhost:3000/api/trpc/getPosts`. You should see a JSON response with the posts from your database.

## Create a tRPC client

So far, your app is entirely server-side and static. You need a way to mutate data, which is where `@tanstack/react-query` comes in. But to use tRPC with `@tanstack/react-query`, you need to create a tRPC client.

Create the `app/_trpc/client.ts` file and paste in the code below:

```ts {{ filename: 'app/_trpc/client.ts' }}
'use client'

import { createTRPCReact } from '@trpc/react-query'

import type { PostRouter } from '@/app/server/routers/posts'

export const trpc = createTRPCReact<PostRouter>({})
```

## Create a Tanstack Query + tRPC provider

To use Tanstack Query and tRPC together, you need to create a provider using the React Context API. This provider will make both the Tanstack Query client and the tRPC client available to your app using the `trpc.Provider` and `QueryClientProvider` components.

Create the `app/_trpc/Provider.tsx` file and paste in the code below:

```tsx {{ filename: 'app/_trpc/Provider.tsx' }}
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import React, { useState } from 'react'

import { trpc } from './client'

export default function Provider({ children }: { children: React.ReactNode }) {
  // Create a Tanstack Query client
  const [queryClient] = useState(() => new QueryClient({}))
  // Create a tRPC client
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: 'http://localhost:3000/api/trpc',
        }),
      ],
    }),
  )
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </trpc.Provider>
  )
}
```

Now, wrap your app in the provider. Update the main layout to import the provider as `TRPCProvider` and wrap your app in it. It's very important that `<ClerkProvider>` is wrapped around `<TRPCProvider>`, and not the other way around, because the `<TRPCProvider>` needs to have access to the Clerk authentication context.

In `app/layout.tsx`, add the following code:

```tsx {{ filename: 'app/layout.tsx', ins: [12, 36, 51] }}
import type { Metadata } from 'next'
import {
  ClerkProvider,
  SignInButton,
  SignUpButton,
  SignedIn,
  SignedOut,
  UserButton,
} from '@clerk/nextjs'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import TRPCProvider from '@/app/_trpc/Provider'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

export const metadata: Metadata = {
  title: 'Clerk Next.js Quickstart',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <TRPCProvider>
        <html lang="en">
          <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
            <header className="flex h-16 items-center justify-end gap-4 p-4">
              <SignedOut>
                <SignInButton />
                <SignUpButton />
              </SignedOut>
              <SignedIn>
                <UserButton />
              </SignedIn>
            </header>
            {children}
          </body>
        </html>
      </TRPCProvider>
    </ClerkProvider>
  )
}
```

Now, you can use the `trpc` client to fetch and mutate data in your app! Let's update the functionality of your app to use the `trpc` client.

## Use the tRPC client to fetch and mutate data

Let's start by updating the homepage where the list of posts is rendered. Since the page is still rendered server-side, you'll create a client component that uses the `trpc` client to fetch posts.

Create the `app/components/Posts.tsx` file and paste in the following code:

```tsx {{ filename: 'app/components/Posts.tsx' }}
'use client'

import Link from 'next/link'
import { trpc } from '../_trpc/client'

export default function Posts() {
  // Use the `getPosts` query from the TRPC client
  const getPosts = trpc.getPosts.useQuery()
  const { isLoading, data } = getPosts

  return (
    <div className="mb-8 flex max-w-2xl flex-col space-y-4">
      {isLoading && <div>Loading...</div>}
      {data?.map((post) => (
        <Link
          key={post.id}
          href={`/posts/${post.id}`}
          className="flex flex-col rounded-lg px-2 py-4 transition-all hover:bg-neutral-100 hover:underline dark:hover:bg-neutral-800"
        >
          <span className="text-lg font-semibold">{post.title}</span>
          <span className="text-sm">by {post.authorId}</span>
        </Link>
      ))}
    </div>
  )
}
```

Then, update the homepage to use the `<Posts />` component:

```tsx {{ filename: 'app/page.tsx', ins: [3, 19], del: [2, [10, 17]] }}
import Link from 'next/link'
import prisma from '@/lib/prisma'
import Posts from './components/Posts'

export default async function Page() {
  return (
    <div className="-mt-16 flex min-h-screen flex-col items-center justify-center">
      <h1 className="mb-8 text-4xl font-bold">Posts</h1>

      <div className="mb-8 flex max-w-2xl flex-col space-y-4">
        {posts.map((post) => (
          <Link
            key={post.id}
            href={`/posts/${post.id}`}
            className="flex flex-col rounded-lg px-2 py-4 transition-all hover:bg-neutral-100 hover:underline dark:hover:bg-neutral-800"
          >
            <span className="text-lg font-semibold">{post.title}</span>
            <span className="text-sm">by {post.authorId}</span>
          </Link>
        ))}
      </div>

      <Posts />

      <Link
        href="/posts/create"
        className="inline-block rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
      >
        Create New Post
      </Link>
    </div>
  )
}
```

Notice that the `prisma.post.findMany()` function is no longer used. Instead, your app is using `trpc.getPosts.useQuery()` in the `<Posts />` component to fetch the posts, because remember, you created a tRPC `postRouter` with a `getPosts` procedure that uses `prisma.post.findMany()`. So now, you don't need to use Prisma directly, you can use tRPC in order to have type safety and a better developer experience. Let's update the rest of your app to use tRPC.

Of course, let's test and make sure the new logic is working. Navigate to the homepage and make sure you can see the posts.

Once you've verified everything's working, let's go back to your `postRouter` and create more procedures to handle your other queries.

### Use tRPC to fetch a single post

In `app/server/routers/posts.ts`, update the code to match the following:

```ts {{ filename: 'app/server/routers/posts.ts', ins: [3, [6, 10]] }}
import { publicProcedure, router } from '../trpc'
import prisma from '@/lib/prisma'
import { z } from 'zod'

export const postRouter = router({
  getPost: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
    return await prisma.post.findUnique({
      where: { id: parseInt(input.id) },
    })
  }),
  getPosts: publicProcedure.query(async () => {
    return await prisma.post.findMany()
  }),
})

export type PostRouter = typeof postRouter
```

This adds a `getPost` procedure to fetch a single post by ID.

In `app/posts/[id]/page.tsx`, update the code to match the following:

```tsx {{ filename: 'app/posts/[id]/page.tsx', ins: [[32, 56]], del: [[1, 30]], prettier: false }}
import prisma from '@/lib/prisma'

export default async function Post({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params
  const post = await prisma.post.findUnique({
    where: { id: parseInt(id) },
  })

  if(!post) {
    return (
      <div className="flex min-h-screen flex-col max-w-2xl mx-auto mt-8">
        <div>No post found.</div>
      </div>
    )
  }

  return (
    <div className="flex min-h-screen flex-col max-w-2xl mx-auto mt-8">
      {post && (
        <article className="w-full max-w-2xl">
          <h1 className="mb-2 text-2xl font-bold sm:text-3xl md:text-4xl">{post.title}</h1>
          <p className="text-sm sm:text-base">by {post.authorId}</p>
          <div className="prose prose-gray prose-sm sm:prose-base lg:prose-lg mt-4 sm:mt-8">
            {post.content || 'No content available.'}
          </div>
        </article>
      )}
    </div>
  )
}

'use client'
import { trpc } from '@/app/_trpc/client'
import { use } from 'react'
export default function Post({ params }: { params: Promise<{ id: string }> }) {
  // Params are wrapped in a promise, so we need to unwrap them using React's `use()` hook
  const { id } = use(params)
  // Use the `getPost` query from the TRPC client
  const { data: post, isLoading } = trpc.getPost.useQuery({ id })

  return (
    <div className="flex min-h-screen flex-col max-w-2xl mx-auto mt-8">
      {isLoading && <p>Loading...</p>}
      {!isLoading && !post && <p>No post found.</p>}
      {!isLoading && post && (
        <article className="w-full max-w-2xl">
          <h1 className="mb-2 text-2xl font-bold sm:text-3xl md:text-4xl">{post.title}</h1>
          <p className="text-sm sm:text-base">by {post.authorId}</p>
          <div className="prose prose-gray prose-sm sm:prose-base lg:prose-lg mt-4 sm:mt-8">
            {post.content || 'No content available.'}
          </div>
        </article>
      )}
    </div>
  )
}
```

This replaces `prisma.post.findUnique()` with `trpc.getPost.useQuery()`. Because tRPC is using Tanstack Query to fetch the data, the query result includes the data and other states, such as loading and error. You can learn more about in the [Tanstack Query docs](https://tanstack.com/query/v4/docs/framework/react/guides/queries#:~:text=throughout%20your%20application.-,The%20query%20result,-returned%20by%20useQuery).

And before you go any further, test to make sure the new logic is working. Navigate to a post's URL, such as `http://localhost:3000/posts/1`, and make sure you can see the post.

If that's working, let's go back to your `postRouter` and add the last procedure you need to handle your create post functionality.

### Use tRPC to create a new post

In `app/server/routers/posts.ts`, add the following code:

```ts {{ filename: 'app/server/routers/posts.ts', ins: [[5, 9], [20, 29]] }}
import { publicProcedure, router } from '../trpc'
import prisma from '@/lib/prisma'
import { z } from 'zod'

const postSchema = z.object({
  title: z.string(),
  content: z.string(),
  authorId: z.string(),
})

export const postRouter = router({
  getPost: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
    return await prisma.post.findUnique({
      where: { id: parseInt(input.id) },
    })
  }),
  getPosts: publicProcedure.query(async () => {
    return await prisma.post.findMany()
  }),
  // Protected procedure that requires a user to be signed in
  createPosts: publicProcedure.input(postSchema).mutation(async ({ input }) => {
    return await prisma.post.create({
      data: {
        title: input.title,
        content: input.content,
        authorId: input.authorId,
      },
    })
  }),
})

export type PostRouter = typeof postRouter
```

This adds a `createPosts` procedure that creates a new post.

In `app/posts/create/page.tsx`, replace the existing code with the following:

```tsx {{ filename: 'app/posts/create/page.tsx', ins: [1, 10, 11, 12, 16, [17, 31], [72, 82], 88, 97, 98, 110, 111, 123], del: [[3, 5], 7, 8, 14, 15, [50, 69], 87, 124], prettier: false }}
'use client'

import Form from 'next/form'
import prisma from '@/lib/prisma'
import { redirect } from 'next/navigation'
import { SignInButton, useAuth } from '@clerk/nextjs'
import { revalidatePath } from 'next/cache'
import { auth } from '@clerk/nextjs/server'

import { redirect } from 'next/navigation'
import { trpc } from '@/app/_trpc/client'
import { useState } from 'react'

export default async function NewPost() {
  const { userId } = await auth()
export default function NewPost() {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')
  // Use Clerk's `useAuth()` hook to get the user's ID
  const { userId, isLoaded } = useAuth()
  // Use the `createPosts` mutation from the TRPC client
  const createPostMutation = trpc.createPosts.useMutation()

  // Check if Clerk is loaded
  if (!isLoaded) {
    return (
      <div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center space-y-4">
        <div>Loading...</div>
      </div>
    )
  }

  // Protect this page from unauthenticated users
  if (!userId) {
    return (
      <div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center space-y-4">
        <p>You must be signed in to create a post.</p>
        <SignInButton>
          <button
            type="submit"
            className="inline-block cursor-pointer rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
          >
            Sign in
          </button>
        </SignInButton>
      </div>
    )
  }

  async function createPost(formData: FormData) {
    'use server'

    // Type check
    if (!userId) return

    const title = formData.get('title') as string
    const content = formData.get('content') as string

    await prisma.post.create({
      data: {
        title,
        content,
        authorId: userId,
      },
    })

    revalidatePath('/')
    redirect('/')
  }

  // Handle form submission
  async function createPost(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()

    createPostMutation.mutate({
      title,
      content,
      authorId: userId as string,
    })

    redirect('/')
  }

  return (
    <div className="mx-auto max-w-2xl p-4">
      <h1 className="mb-6 text-2xl font-bold">Create New Post</h1>
      <Form action={createPost} className="space-y-6">
      <form onSubmit={createPost} className="space-y-6">
        <div>
          <label htmlFor="title" className="mb-2 block text-lg">
            Title
          </label>
          <input
            type="text"
            id="title"
            name="title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Enter your post title"
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <div>
          <label htmlFor="content" className="mb-2 block text-lg">
            Content
          </label>
          <textarea
            id="content"
            name="content"
            value={content}
            onChange={(e) => setContent(e.target.value)}
            placeholder="Write your post content here..."
            rows={6}
            className="w-full rounded-lg border px-4 py-2"
          />
        </div>
        <button
          type="submit"
          className="inline-block w-full rounded-lg border-2 border-current px-4 py-2 text-current transition-all hover:scale-[0.98]"
        >
          Create Post
        </button>
      </form>
      </Form>
    </div>
  )
}
```

This updates a few things. First, it turns this page into a client component, because Tanstack Query and the tRPC client are client-side. So now, the Server Action that you created before can no longer be used. Instead, the form data is handled using state. When the form is submitted, the `createPost()` function no longer uses `prisma.post.create()`, but instead uses `trpc.createPosts.useMutation()` from the tRPC client. Also, because the page is now a client component, Clerk's `auth()` helper no longer works, so it's replaced with Clerk's `useAuth()` hook. This introduces the benefit of having access to Clerk's loading state, so a loading UI is added.

And don't forget, test your changes. Navigate to the create post page, such as `http://localhost:3000/posts/create`, and make sure you can create a new post.

Once you've confirmed everything's working, you're almost done...

## Create protected procedures

In many applications, it's essential to restrict access to certain routes based on user authentication status. This ensures that sensitive data and functionality are only accessible to authorized users.

The benefit of using Clerk with tRPC is that you can create protected procedures using Clerk's authentication context. Clerk's [`Auth`](https://clerk.com/docs/references/backend/types/auth-object) object includes important authentication information like the current user's session ID, user ID, and organization ID. It also contains methods to check for the current user's permissions and to retrieve their session token. You can use the `Auth` object to access the user's authentication information in your tRPC queries.

### Create the tRPC context

In your `server` directory, create a `context.ts` file with the following code:

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

export const createContext = async () => {
  return { auth: await auth() }
}

export type Context = Awaited<ReturnType<typeof createContext>>
```

This creates a context that will be used to create the context for every tRPC query sent to the server. The context will use the [`auth()`](/docs/references/nextjs/auth) helper from Clerk to access the user's `Auth` object.

### Pass the context to the tRPC server

Then, in your tRPC server (`app/api/trpc/[trpc]/route.ts`), pass the context:

```ts {{ filename: 'app/api/trpc/[trpc]/route.ts', ins: [3, 33], del: [10] }}
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/app/server/routers/posts'
import { createContext } from '@/app/server/context'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext: () => ({}),
    createContext,
  })

export { handler as GET, handler as POST }
```

### Access the context data in your procedures

The tRPC context, or `ctx`, should now have access to the Clerk `Auth` object.

In your `server/trpc.ts` file, create a protected procedure:

```ts {{ filename: 'app/server/trpc.ts', ins: [2, 3, 6, [10, 19], 23], del: [1, 5] }}
import { initTRPC } from '@trpc/server'
import { initTRPC, TRPCError } from '@trpc/server'
import { Context } from './context'

const t = initTRPC.create()
const t = initTRPC.context<Context>().create()

// Check if the user is signed in
// Otherwise, throw an UNAUTHORIZED code
const isAuthed = t.middleware(({ next, ctx }) => {
  if (!ctx.auth.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED' })
  }
  return next({
    ctx: {
      auth: ctx.auth,
    },
  })
})

export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(isAuthed)
```

### Use your protected procedure

Once you have created your procedure, you can use it in any router. In this case, you don't want unauthenticated users to be able to create posts, so let's update the `createPosts` mutation to be protected by swapping the `publicProcedure` with the `protectedProcedure`:

```ts {{ filename: 'app/server/routers/posts.ts', ins: [2, 22], del: [1, 21], prettier: false }}
import { publicProcedure, router } from '../trpc'
import { protectedProcedure, publicProcedure, router } from '../trpc'
import prisma from '@/lib/prisma'
import { z } from 'zod'

const postSchema = z.object({
  title: z.string(),
  content: z.string(),
  authorId: z.string(),
})

export const postRouter = router({
  getPost: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
    return await prisma.post.findUnique({
      where: { id: parseInt(input.id) },
    })
  }),
  getPosts: publicProcedure.query(async () => {
    return await prisma.post.findMany()
  }),
  createPosts: publicProcedure.input(postSchema).mutation(async ({ input }) => {
  createPosts: protectedProcedure.input(postSchema).mutation(async ({ input }) => {
    return await prisma.post.create({
      data: {
        title: input.title,
        content: input.content,
        authorId: input.authorId,
      },
    })
  }),
})

export type PostRouter = typeof postRouter
```

## Finished!

At this point, you've got a fully functional app for creating and displaying posts. You can now add more features to your app, such as updating and deleting posts, adding comments, storing more author information from the Clerk [`User`](https://clerk.com/docs/references/javascript/user) object, and more.

---

# How to enrich PostHog events with Clerk user data
URL: https://clerk.com/blog/posthog-events-with-clerk.md
Date: 2025-02-28
Category: Guides
Description: Learn how to enrich PostHog events with Clerk user data to better understand your users and their actions on your website.

Data-driven decisions are critical for teams building SaaS products due to their ability to optimize processes, improve customer satisfaction, and drive growth. Attributing those data points to individual users can significantly enhance this process by providing more targeted insights about user groups and behaviors.

In this article, you’ll learn how events gathered by PostHog can be directly associated to individual users in applications using Clerk.

> \[!NOTE]
> This is the third article in the Kozi series, which walks you through building a project/knowledge management SaaS from the ground up using Clerk, Neon, Next.js, and more.
>
> [Learn more](https://github.com/bmorrisondev/kozi)

## What is PostHog?

PostHog is an open-source product analytics platform that allows developers to gain a deeper understanding of how their product is used with tools like event tracking, session replay, feature flags, and more. Using one of the PostHog SDKs, web applications can be configured to automatically collect data and transmit it to the platform. This data can fuel dashboards to help you make data-driven decisions on how to optimize their product.

When configured properly, the event data in PostHog can be attributed directly to your  users and identify which features they're utilizing.

![The PostHog dashboard with a list of events from a user](./posthog-users.png)

## Enriching event data with user information

The PostHog SDK provides the `identify` function as a means to attribute a session to a specific user. This function also supports including arbitrary data about the current user to further enrich the data sent back to its platform. Furthermore, PostHog will proactively enrich past events once a session has been associated with a user so that you have the most accurate view of how your product is being used.

Clerk SDKs provide helper functions to easily gather information about the user currently using your product. The following snippet demonstrates how the current Clerk user data can be used with `identify` to enrich the event data sent to PostHog within a Next.js application:

```tsx
// The `useUser` hook returns information about the current user.
const { user } = useUser()

// That information can be used with the `posthog.identify` function to associate the data with the user.
posthog.identify(userId, {
  email: user.primaryEmailAddress?.emailAddress,
  username: user.username,
})
```

Read on to see how this code is implemented.

> \[!NOTE]
> If you want to learn more about integrating PostHog with Clerk, let us know in our [feedback portal](https://feedback.clerk.com/roadmap?id=c0964197-4879-4a3e-858f-521f783500c5).

## How to configure PostHog to use Clerk user data in Next.js

Let’s explore how to implement this in a real-world scenario by configuring this integration into Kozi. Kozi is an open-source project/knowledge management web application built with Next.js, Neon, and Clerk.

If you want to follow along on your computer, clone the [`article-2ph-start`](https://github.com/bmorrisondev/kozi/tree/article-2ph-start) branch of the Kozi repository and run the follow the instructions in the `README` to configure the project before proceeding.

### Configure the **`PostHogPageView.tsx`** component

The following client-side component has two `useEffects` that perform the following operations:

- The first will use the `posthog.capture` function with the `$pageview` event passing in the current URL.
- The second will run the `posthog.identify` function if the user is not already identified, passing in information from the `useAuth` and `useUser` Clerk hooks.
  - This function will also clear the user information from PostHog in the current session if user is no longer logged in using the `isSignedIn` boolean from the `useAuth` Clerk hook.

```tsx {{ filename: 'src/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,
      })
    }

    // 👉 Reset the user if they sign out
    if (!isSignedIn && posthog._isIdentified()) {
      posthog.reset()
    }
  }, [posthog, user])

  return null
}
```

This component is then added to the root layout file within the `<PostHogProvider>` tags:

```tsx {{ filename: 'src/app/layout.tsx', ins: [6, 34] }}
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import { ClerkProvider } from '@clerk/nextjs'
import { PostHogProvider } from './providers'
import PostHogPageView from './PostHogPageView'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

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

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
          <PostHogProvider>
            {children}
            <PostHogPageView />
          </PostHogProvider>
        </body>
      </html>
    </ClerkProvider>
  )
}
```

### Configure user interaction event tracking

While the above component will automatically capture pageviews using a default event name, PostHog can also capture custom events associated to your users:

```sql
posthog.capture('task_created');
```

> \[!NOTE]
> The `posthog.capture` function only works in the browser, so make sure to use it only in client components, otherwise the function will silently fail.

The `CreateTaskInput.tsx` is what renders the input at the bottom of a task list:

![The CreateTaskInput component](./create-task-input.png)

To instrument this component, you only need to add the `usePostHog` hook and insert a line in the function that hands the form submission:

```tsx {{ filename: 'src/app/app/components/CreateTaskInput.tsx', ins: [17, 24] }}
'use client'

import { useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { createTask } from '@/app/app/actions'
import { PlusIcon } from 'lucide-react'
import { usePostHog } from 'posthog-js/react'

interface CreateTaskInputProps {
  projectId?: string
}

export default function CreateTaskInput({ projectId }: CreateTaskInputProps) {
  const [title, setTitle] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const posthog = usePostHog()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    if (!title.trim()) return

    posthog.capture('create_task')

    try {
      setIsSubmitting(true)
      const formData = new FormData()
      formData.append('title', title)
      if (projectId) {
        formData.append('project_id', projectId)
      }
      await createTask(formData)
      setTitle('')
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <div className="w-full rounded-lg bg-white p-2 shadow dark:bg-gray-800">
      <form onSubmit={handleSubmit} className="flex w-full items-center gap-2">
        <Input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="Add a task..."
          className="w-full border-0 bg-transparent text-gray-900 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-gray-100"
        />
        <Button
          type="submit"
          size="icon"
          disabled={isSubmitting || !title.trim()}
          className="rounded"
        >
          <PlusIcon className="h-4 w-4" />
        </Button>
      </form>
    </div>
  )
}
```

From that point forward, any time a user creates a task, PostHog will have an event logged that can be used for product analytics:

![The PostHog dashboard with a list of events showing the create\_task event](./create-task-event.png)

## Conclusion

Using PostHog with Clerk can unlock powerful user engagement insights that drive your product's growth. Tracking standard events like page views and custom events tailored to your application, you can identify usage trends that might otherwise go unnoticed, allowing you to confidently iterate on your product.

---

# How to build a secure project management platform with Next.js, Clerk, and Neon
URL: https://clerk.com/blog/build-secure-project-management-nextjs.md
Date: 2025-02-20
Category: Guides
Description: Learn a security-first approach to building web applications by building a secure project management platform with Next.js.

Around 30,000 websites and applications are hacked every day\*, and the developer is often to blame.

The vast majority of breaches occur due to misconfiguration rather than an actual vulnerability. This could be due to exposed database credentials, unprotected API routes, or data operations without the proper authorization checks just to name a few. It’s important to ensure that your application is configured in a way that prevents attackers from gaining unauthorized access to user data.

In this article, you’ll learn how to build a project management web application while considering security best practices throughout.

Although this article can be followed by itself, it is the second in a series covering the process of building **Kozi** - a collaborative project and knowledge management tool. Throughout the series, the following features will be implemented:

- Create organizations to invite others to manage projects as a team.
- A rich, collaborative text editor for project and task notes.
- A system to comment on projects, tasks, and notes.
- Automatic RAG functionality for all notes and uploaded files.
- Invite users from outside your organization to collaborate on individual tasks.
- Be notified when events occur on tasks you subscribe to, or you are mentioned in comments or notes.

## What makes this a “secure” project management system?

Data security is considered throughout this guide by using the following techniques:

### Clerk and the Next.js middleware

Clerk is a [user management platform for Next.js](/nextjs-authentication) designed to get authentication into your application as quick as possible by providing a complete suite of user management tools as well as drop-in UI components. Behind the scenes, Clerk creates fast expiring tokens upon user sign-in that are sent to your server with each request, where Clerk also verifies the identify of the user.

Clerk integrates with Next.js middleware to ensure every request to the application is evaluated before it reaches its destination. In the section where the middleware is configured, we instruct the middleware to protect any route starting with `/app` so that only authenticated users may access them. This means that before any functions are executed (on the client or server), the user will need to be authenticated.

### Server actions

In this project, server actions are the primary method of interacting with the data in the database. Direct access to the database should always happen on the server and NEVER on the client where tech-savvy users can gain access to the database credentials. Since all functions that access the database are built with server actions, they do not execute client-side.

It's important to note that calling these server actions should only ever be performed from protected routes. When a Next.js client component executes a server action, an HTTP POST request of form data is submitted to the current path with a unique identifier of the action for Next.js to route the data internally.

This means that calling a server function from an anonymous route might result in anonymous users getting access to the data. This potential vulnerability is addressed in the next section.

### Database requests

Protecting access to the functions is only one consideration. Each request will have an accompanying user identifier which can be used to determine the user making that request. This identifier is stored alongside the records the user creates, allowing each request for data to ONLY return the data associated with that user.

When making data modifications, the requesting user ID is cross-referenced with the records being modified or deleted so that one user cannot affect another user’s data.

The combination of protecting access to the routes, being mindful of calling server actions, and cross-referencing database queries with the user making the request ensures that the data within the application is secure and only accessible to those who have access to it.

## How to follow along

Kozi is an open-source project, with each article in the series having corresponding start and end branches. This makes it easy to jump in at any point to get hands-on experience with the concepts outlined in each piece, as well as a point of reference if you simply want to see the completed code. Here are links to the specific branches:

- [`article-2-start`](https://github.com/bmorrisondev/kozi/tree/article-2-start)
- [`article-2-end`](https://github.com/bmorrisondev/kozi/tree/article-2-end)

You should have a basic understanding of Next.js and React as well.

### Launching the project

Once the branch above is cloned, open the project in your editor or terminal and run the following command to start up the application:

```bash
npm install
npm run dev
```

Open your browser and navigate to the URL displayed in the terminal to access Kozi. At the bottom right of the screen, you should see Clerk is running in keyless mode. Click the button to claim your keys and associate this instance to your Clerk account. If you don’t have an account, you’ll be prompted to create one.

![Claim your Clerk keys](./claim-keys.png)

You are now ready to start building out the core functionality of Kozi!

## Setting up the database

To store structured data, you’ll be using a serverless instance of Postgress provided by Neon. Start by heading to [neon.tech](http://neon.tech) and creating an account if you don’t have one. Create a new database and copy the connection string as shown below.

![Copy the connection string](./neon-cs.png)

Create a new file in your local project named `.env.local` and paste the following snippet, replacing the placeholder for your specific Neon database connection string.

```
DATABASE_URL=<your_neon_connection_string>
```

### Configuring Prisma

Prisma is used as the ORM to access and manipulate data in the database, as well as apply schema changes to the database as the data needs are updated. Open the project in your IDE and start by creating the schema file at `prisma/schema.prisma`. Paste in the following code:

```prisma {{ filename: 'prisma/schema.prisma' }}
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Project {
  id          String   @id @default(cuid())
  name        String
  description String?
  owner_id    String
  created_at  DateTime @default(now())
  updated_at  DateTime @updatedAt
  is_archived Boolean @default(false)
}

model Task {
  id          String   @id @default(cuid())
  title       String
  description String?
  owner_id    String
  is_completed Boolean @default(false)
  created_at  DateTime @default(now())
  updated_at  DateTime @updatedAt
  project_id  String?
}
```

> \[!NOTE]
> We’re using the `owner_id` column instead of `user_id` since this application will be updated to support teams and organizations in a future entry.

Next, create the `src/lib/db.ts` file and paste in the following code which will be used throughout the application to create a connection to the database:

```ts {{ filename: 'src/lib/db.ts' }}
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
```

To sync the schema changes to Neon, run the following command in the terminal:

```bash
npx prisma db push
```

If you open the database in the Neon console and navigate to the Tables menu item, you should see the `projects` and `tasks` tables shown.

![Neon tables](./neon-tables.png)

Finally, since it is not best practice to use the Prisma client in any client-side components, you’ll want a file to store interfaces so that TypeScript can recognize the structure of your objects when passing them between components.

Create the `src/app/app/models.ts` file and paste in the following:

```ts {{ filename: 'src/app/app/models.ts' }}
export interface Task {
  id: string
  title: string
  description?: string | null
  is_completed: boolean
  created_at: Date
  updated_at: Date
  project_id?: string | null
  owner_id: string
}

export interface Project {
  name: string
  id: string
  description: string | null
  owner_id: string
  created_at: Date
  updated_at: Date
  is_archived: boolean
}
```

## Configure `/app` as a protected route with Clerk

Clerk’s middleware uses a helper function called `createRouteMatcher` that lets you define a list of routes to protect. This includes any pages, server actions, or API handlers stored in the matching folders of the project.

All of the core functionality of the application will be stored in the `/app` route, so update `src/middleware.ts` to use the `createRouteMatcher` to protect everything in that folder:

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

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

export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) await 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)(.*)',
  ],
}
```

The `/app` route will use a different layout from the landing page, which will contain a collapsible sidebar that contains the `<UserButton />` (a Clerk UI component that lets users manage their profile and sign out), an inbox for tasks, and a list of projects that tasks can be created in.

Start by creating the `src/app/app/components/Sidebar.tsx` file to render the elements of the sidebar:

```tsx {{ filename: 'src/app/app/components/Sidebar.tsx' }}
'use client'

import { cn } from '@/lib/utils'
import { ChevronRightIcon, ChevronLeftIcon, InboxIcon } from 'lucide-react'
import React from 'react'
import Link from 'next/link'
import { UserButton } from '@clerk/nextjs'

function Sidebar() {
  const [isCollapsed, setIsCollapsed] = React.useState(false)

  return (
    <div
      className={cn(
        'h-screen border-r border-gray-200 bg-gradient-to-b from-blue-50 via-purple-50/80 to-blue-50 p-4 dark:border-gray-800 dark:from-blue-950/20 dark:via-purple-950/20 dark:to-blue-950/20',
        'transition-all duration-300 ease-in-out',
        isCollapsed ? 'w-16' : 'w-64',
      )}
    >
      <nav className="space-y-2">
        <div className="flex items-center justify-between gap-2">
          <div
            className={cn(
              'transition-all duration-300',
              isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
            )}
          >
            <UserButton showName />
          </div>
          <button
            onClick={() => setIsCollapsed(!isCollapsed)}
            className="flex-shrink-0 rounded-lg p-1 transition-colors hover:bg-white/75 dark:hover:bg-gray-800/50"
          >
            {isCollapsed ? (
              <ChevronRightIcon className="h-4 w-4" />
            ) : (
              <ChevronLeftIcon className="h-4 w-4" />
            )}
          </button>
        </div>

        <div
          className={cn(
            'transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <Link
            href="/app"
            className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-white/75 dark:text-gray-200 dark:hover:bg-gray-800/50"
          >
            <InboxIcon className="h-4 w-4" />
            <span>Inbox</span>
          </Link>
        </div>
      </nav>
    </div>
  )
}

export default Sidebar
```

Now create `src/app/app/layout.tsx` to render the sidebar with the pages in the `/app` route:

```tsx {{ filename: 'src/app/app/layout.tsx' }}
import * as React from 'react'
import Sidebar from './components/Sidebar'

export default function AppLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  )
}
```

Next, create `src/app/app/page.tsx` which is just a simple page that renders some text to make sure the `/app` route works as expected:

```tsx {{ filename: 'src/app/app/page.tsx' }}
import React from 'react'
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function AppHome() {
  const { userId } = await auth()

  if (!userId) {
    return redirect('/sign-in')
  }

  return <div className="flex h-screen">Inbox</div>
}
```

Open the application in your browser and test out the changes by navigating to the `/app` which should automatically redirect you to the `/sign-in` route where you can create an account and make sure `/app` only works when authenticated.

## Working with tasks

At the core of every project is a list of tasks, so now we’ll configure the ability to create and work with tasks in the default Inbox list. Several components will be used to provide the following application structure. The following image shows how these components will be used:

![Kozi UI diagram](./kozi-diagram.png)

These are all client components so they will need corresponding server actions so they can interact with the database securely. Create the `src/app/app/actions.ts` file and paste in the following code:

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

import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createTask(formData: FormData) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const title = formData.get('title') as string
  if (!title?.trim()) {
    throw new Error('Title is required')
  }

  await prisma.task.create({
    data: {
      title: title.trim(),
      owner_id: userId,
      project_id: null,
    },
  })

  revalidatePath('/app')
}

export async function toggleTask(taskId: string) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const task = await prisma.task.findUnique({
    where: { id: taskId },
  })

  if (!task || task.owner_id !== userId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id: taskId },
    data: { is_completed: !task.is_completed },
  })

  revalidatePath('/app')
}
```

We’re going to start with the `<CreateTaskInput />` component which renders the field where users can create tasks. Create the `src/app/app/components/CreateTaskInput.tsx` file and paste in the following:

```tsx {{ filename: 'src/app/app/components/CreateTaskInput.tsx' }}
'use client'

import { useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { createTask } from '@/app/app/actions'
import { PlusIcon } from 'lucide-react'

export default function CreateTaskInput() {
  const [title, setTitle] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    // Don't create a task if the title is empty
    if (!title.trim()) return

    try {
      setIsSubmitting(true)
      const formData = new FormData()
      formData.append('title', title)
      await createTask(formData)
      setTitle('')
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <div className="group relative w-full rounded-full bg-white p-2 transition-shadow duration-200 focus-within:shadow-[0_4px_20px_-2px_rgba(96,165,250,0.3),0_4px_20px_-2px_rgba(192,132,252,0.3)] dark:bg-gray-800">
      <div className="absolute inset-0 rounded-full bg-gradient-to-r from-blue-400/25 to-purple-400/25 transition-opacity duration-200 group-focus-within:from-blue-400 group-focus-within:to-purple-400"></div>
      <div className="absolute inset-[1px] rounded-full bg-white transition-all group-focus-within:inset-[2px] dark:bg-gray-800"></div>
      <div className="relative">
        <form onSubmit={handleSubmit} className="flex w-full items-center gap-2">
          <Input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Add a task..."
            className="flex-1 border-0 bg-transparent text-gray-900 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-gray-100"
          />
          <Button
            type="submit"
            size="icon"
            disabled={isSubmitting || !title.trim()}
            className="flex !h-[30px] !min-h-0 !w-[30px] items-center justify-center !rounded-full !p-0 !leading-none"
          >
            <PlusIcon className="h-4 w-4" />
          </Button>
        </form>
      </div>
    </div>
  )
}
```

Next, we’ll move on to `<TaskCard />`, which will display the name of the task and allow users to toggle it using a checkbox, as is standard in task-centric applications. Create the `src/app/app/components/TaskCard.tsx` file and paste in the following:

```tsx {{ filename: 'src/app/app/components/TaskCard.tsx' }}
'use client'

import React from 'react'
import { toggleTask } from '../actions'
import { cn } from '@/lib/utils'
import { Task } from '@prisma/client'

interface Props {
  task: Task
}

export default function TaskCard({ task }: Props) {
  return (
    <div
      className={cn(
        'cursor-pointer rounded-lg border border-transparent p-2 transition-colors duration-200 hover:border-gray-100 dark:border-gray-800 dark:hover:bg-gray-800/50',
        task.is_completed && 'opacity-50',
      )}
    >
      <div className="flex items-start justify-between">
        <div className="flex items-start gap-3">
          {/* Checkbox */}
          <button
            onClick={(e) => {
              e.stopPropagation()
              toggleTask(task.id)
            }}
            className="mt-1 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500"
          >
            {task.is_completed && (
              <svg
                className="h-3 w-3 text-gray-500 dark:text-gray-400"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth={2}
                  d="M5 13l4 4L19 7"
                />
              </svg>
            )}
          </button>
          {/* Task details */}
          <div>
            <h3
              className={cn(
                'font-medium',
                task.is_completed && 'text-gray-400 line-through dark:text-gray-500',
              )}
            >
              {task.title}
            </h3>

            {task.description && (
              <p
                className={cn(
                  'mt-1 text-sm text-gray-500 dark:text-gray-400',
                  task.is_completed && 'line-through opacity-75',
                )}
              >
                {task.description}
              </p>
            )}
          </div>
        </div>
      </div>
    </div>
  )
}
```

Finally, create the `<TaskList />` component to render the list of tasks and the input to create new ones. Create the `src/app/app/components/TaskList.tsx` file and paste in the following:

```tsx {{ filename: 'src/app/app/components/TaskList.tsx' }}
'use client'

import React from 'react'
import TaskCard from './TaskCard'
import CreateTaskInput from './CreateTaskInput'
import { Task } from '@prisma/client'

interface Props {
  title: string
  tasks: Task[]
}

export default function TaskList({ title, tasks }: Props) {
  return (
    <div className="flex h-screen w-full max-w-2xl flex-col gap-4 p-8">
      <h1 className="text-lg font-semibold md:text-xl">{title}</h1>

      <div className="w-full flex-1 rounded-xl">
        <div className="space-y-2">
          {tasks.length === 0 ? (
            <p className="text-gray-500 dark:text-gray-400">No tasks</p>
          ) : (
            tasks.map((task) => <TaskCard key={task.id} task={task} />)
          )}
        </div>
      </div>
      <div className="w-full">
        <CreateTaskInput />
      </div>
    </div>
  )
}
```

With all of our components created, update the `src/app/app/page.tsx` to match the following code which uses the components created above, as well as queries the database for all tasks on load:

```tsx {{ filename: 'src/app/app/page.tsx', ins: [4, 5, [14, 29]], del: [30], prettier: false }}
import React from 'react'
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
import { prisma } from '@/lib/db'
import TaskList from './components/TaskList'

export default async function AppHome() {
  const { userId } = await auth()

  if (!userId) {
    return redirect('/sign-in')
  }

  // Get the user's inbox tasks
  const tasks = await prisma.task.findMany({
    where: {
      owner_id: userId,
      project_id: null,
    },
    orderBy: {
      created_at: 'desc',
    },
  })

  return (
    <div className="flex h-screen">
      <TaskList title="Inbox" tasks={tasks} />
    </div>
  )
  return <div className="flex h-screen">Inbox</div>
}
```

If you access the application again, you can now create tasks in your inbox and complete them.

### Editing and deleting tasks

Now that you can create tasks, the next step is to set up a modal so clicking the task (outside of the checkbox) will display the modal and allow you to change the name of the task and set a description if needed.

As a design decision, this modal does not include a save button but rather debounces any edits for 1 second to create an experience where users can quickly save values and avoid another click. The modal will also create a menu in the header which allows you to delete the task.

Start by appending the following code to `src/app/app/actions.ts`:

```ts {{ filename: 'src/app/app/actions.ts' }}
export async function updateTask(formData: FormData) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const id = formData.get('id') as string
  const title = formData.get('title') as string
  const description = formData.get('description') as string

  if (!id || !title?.trim()) {
    throw new Error('Invalid input')
  }

  const task = await prisma.task.findUnique({
    where: { id },
  })

  if (!task || task.owner_id !== userId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id },
    data: {
      title: title.trim(),
      description: description?.trim() || null,
    },
  })

  revalidatePath('/app')
}

export async function deleteTask(taskId: string) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  // Delete the task
  await prisma.task.delete({
    where: {
      id: taskId,
      owner_id: userId, // Ensure the task belongs to the user
    },
  })

  revalidatePath('/app')
}
```

Next, create the `src/app/app/components/EditTaskModal.tsx` and paste in the following:

```tsx {{ filename: 'src/app/app/components/EditTaskModal.tsx' }}
'use client'

import { useEffect, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Textarea } from '@/components/ui/textarea'
import { useRouter } from 'next/navigation'
import { updateTask, toggleTask, deleteTask } from '../actions'
import { Folder, MoreVertical, Trash2, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Task } from '../models'

interface Props {
  task: Task
  open: boolean
  onOpenChange: (open: boolean) => void
  projectName?: string
}

export default function EditTaskModal({
  task: initialTask,
  open,
  onOpenChange,
  projectName,
}: Props) {
  const [task, setTask] = useState(initialTask)
  const [title, setTitle] = useState(task.title)
  const [description, setDescription] = useState(task.description || '')
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
  const router = useRouter()

  // Reset form when modal opens
  useEffect(() => {
    if (open) {
      setTask(initialTask)
      setTitle(initialTask.title)
      setDescription(initialTask.description || '')
    }
  }, [open, initialTask])

  const saveChanges = useDebouncedCallback(async () => {
    if (!title.trim()) return

    try {
      setIsSubmitting(true)
      const formData = new FormData()
      formData.append('id', task.id)
      formData.append('title', title.trim())
      formData.append('description', description.trim())
      await updateTask(formData)
      router.refresh()
    } finally {
      setIsSubmitting(false)
    }
  }, 1000)

  async function onToggleCompleted() {
    const newIsCompleted = !task.is_completed
    setTask((prev) => ({ ...prev, is_completed: newIsCompleted }))
    try {
      await toggleTask(task.id)
    } catch (error) {
      // Revert on error
      setTask((prev) => ({ ...prev, is_completed: !newIsCompleted }))
    }
  }

  function titleRef(el: HTMLTextAreaElement | null) {
    if (el) {
      el.style.height = '2.5rem' // Set initial height
      const scrollHeight = el.scrollHeight
      const minHeight = 40 // 2.5rem in pixels
      el.style.height = `${Math.max(scrollHeight, minHeight)}px`
    }
  }

  function onTitleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
    setTitle(e.target.value)
    saveChanges()

    // Auto-adjust height after value changes
    const el = e.target
    el.style.height = '2.5rem' // Reset to minimum height
    const scrollHeight = el.scrollHeight
    const minHeight = 40 // 2.5rem in pixels
    el.style.height = `${Math.max(scrollHeight, minHeight)}px`
  }

  function onDescriptionChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
    setDescription(e.target.value)
    saveChanges()
  }

  async function handleDelete() {
    try {
      await deleteTask(task.id)
      onOpenChange(false)
      router.refresh()
    } catch (error) {
      console.error('Failed to delete task:', error)
    }
  }

  return (
    <>
      {/*  The edit task modal */}
      <Dialog open={open} onOpenChange={onOpenChange}>
        <DialogContent className="flex h-[80vh] flex-col gap-0 p-0 [&>button]:hidden">
          <DialogHeader className="border-b border-gray-200 p-3">
            <div className="flex items-center justify-between">
              <DialogTitle className="flex items-center gap-2 text-sm">
                <Folder size={14} /> {projectName ?? 'Inbox'}
              </DialogTitle>
              <div className="flex items-center gap-1">
                <DropdownMenu>
                  <DropdownMenuTrigger asChild>
                    <Button variant="ghost" size="icon" className="h-8 w-8">
                      <MoreVertical className="h-4 w-4" />
                    </Button>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent align="end">
                    <DropdownMenuItem
                      onClick={() => setShowDeleteConfirm(true)}
                      className="text-red-600 dark:text-red-400"
                    >
                      <Trash2 className="mr-2 h-4 w-4" />
                      Delete Task
                    </DropdownMenuItem>
                  </DropdownMenuContent>
                </DropdownMenu>
                <Button
                  variant="ghost"
                  size="icon"
                  className="h-8 w-8"
                  onClick={() => onOpenChange(false)}
                >
                  <X className="h-4 w-4" />
                </Button>
              </div>
            </div>
          </DialogHeader>
          <div className="flex flex-1 flex-col">
            <div className="flex items-start border-b border-gray-200 p-3">
              <div className="pt-[0.7rem]">
                <input
                  type="checkbox"
                  checked={task.is_completed}
                  onChange={onToggleCompleted}
                  className="text-primary h-4 w-4 rounded border-gray-300 hover:cursor-pointer"
                />
              </div>

              <Textarea
                ref={titleRef}
                value={title}
                onChange={onTitleChange}
                placeholder="Task title"
                disabled={isSubmitting}
                className="min-h-0 flex-1 resize-none overflow-hidden border-none bg-transparent leading-normal font-semibold shadow-none ring-0 transition-colors outline-none hover:bg-gray-50 focus:border focus:border-gray-200 focus:shadow-none focus:ring-0 md:text-base dark:hover:bg-gray-800/50 dark:focus:border-gray-800"
                onKeyDown={(e) => {
                  if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault()
                  }
                }}
              />
            </div>

            <div className="flex-1">
              <Textarea
                value={description}
                onChange={onDescriptionChange}
                placeholder="Add a description..."
                disabled={isSubmitting}
                className="h-full resize-y rounded-none border-0 p-3 shadow-none focus:ring-0 focus:outline-none focus-visible:ring-0 focus-visible:outline-none"
                onKeyDown={(e) => {
                  if (e.key === 'Enter') {
                    // Allow line breaks
                    e.stopPropagation()
                  }
                }}
              />
            </div>
          </div>
          <div className="flex justify-between border-t border-gray-200 p-2 text-[10px] text-gray-400 dark:text-gray-500">
            <div>
              Created {new Date(task.created_at).toLocaleDateString()} at{' '}
              {new Date(task.created_at).toLocaleTimeString()}
            </div>
            <div>
              Updated {new Date(task.updated_at).toLocaleDateString()} at{' '}
              {new Date(task.updated_at).toLocaleTimeString()}
            </div>
          </div>
        </DialogContent>
      </Dialog>

      {/*  The alert dialog for deleting a task */}
      <AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Are you sure?</AlertDialogTitle>
            <AlertDialogDescription>
              This action cannot be undone. This will permanently delete the task.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={handleDelete} className="bg-red-500 hover:bg-red-600">
              Delete
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </>
  )
}
```

Finally, update `src/app/app/TaskCard.tsx` to include the `EditTaskModal` component and handle user click events:

```tsx {{ filename: 'src/app/app/TaskCard.tsx', ins: [7, [14, 22], 25, 27, [85, 90]], del: [] }}
'use client'

import React from 'react'
import { toggleTask } from '../actions'
import { cn } from '@/lib/utils'
import { Task } from '@prisma/client'
import EditTaskModal from './EditTaskModal'

interface Props {
  task: Task
}

export default function TaskCard({ task }: Props) {
  const [isModalOpen, setIsModalOpen] = React.useState(false)

  const handleClick = (e: React.MouseEvent) => {
    const target = e.target as HTMLElement
    // Don't open modal if clicking the checkbox
    if (!target.closest('button')) {
      setIsModalOpen(true)
    }
  }

  return (
    <>
      <div
        onClick={handleClick}
        className={cn(
          'cursor-pointer rounded-lg border border-transparent p-2 transition-colors duration-200 hover:border-gray-100 dark:border-gray-800 dark:hover:bg-gray-800/50',
          task.is_completed && 'opacity-50',
        )}
      >
        <div className="flex items-start justify-between">
          <div className="flex items-start gap-3">
            {/* Checkbox */}
            <button
              onClick={(e) => {
                e.stopPropagation()
                toggleTask(task.id)
              }}
              className="mt-1 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500"
            >
              {task.is_completed && (
                <svg
                  className="h-3 w-3 text-gray-500 dark:text-gray-400"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke="currentColor"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth={2}
                    d="M5 13l4 4L19 7"
                  />
                </svg>
              )}
            </button>
            {/* Task details */}
            <div>
              <h3
                className={cn(
                  'font-medium',
                  task.is_completed && 'text-gray-400 line-through dark:text-gray-500',
                )}
              >
                {task.title}
              </h3>

              {task.description && (
                <p
                  className={cn(
                    'mt-1 text-sm text-gray-500 dark:text-gray-400',
                    task.is_completed && 'line-through opacity-75',
                  )}
                >
                  {task.description}
                </p>
              )}
            </div>
          </div>
        </div>
      </div>

      <EditTaskModal task={task} open={isModalOpen} onOpenChange={setIsModalOpen} />
    </>
  )
}
```

Now you can click anywhere outside of the checkbox of a task to open the modal to edit the task name and description or delete the task from the database.

## Working with projects

Users of Kozi can create projects to organize their tasks into categorized lists. Projects will be listed in the sidebar in their own section from the Inbox. When selected, the user will navigate to the `/app/projects/[_id]` route to see the tasks for that project. To start implementing this, update `src/app/app/actions.ts` to match the following:

```ts {{ filename: 'src/app/app/actions.ts', ins: [18, 24, [103, 139]], del: [] }}
'use server'

import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createTask(formData: FormData) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const title = formData.get('title') as string
  if (!title?.trim()) {
    throw new Error('Title is required')
  }

  const project_id = formData.get('project_id') as string | null

  await prisma.task.create({
    data: {
      title: title.trim(),
      owner_id: userId,
      project_id: project_id || null,
    },
  })

  revalidatePath('/app')
}

export async function toggleTask(taskId: string) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const task = await prisma.task.findUnique({
    where: { id: taskId },
  })

  if (!task || task.owner_id !== userId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id: taskId },
    data: { is_completed: !task.is_completed },
  })

  revalidatePath('/app')
}

export async function updateTask(formData: FormData) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const id = formData.get('id') as string
  const title = formData.get('title') as string
  const description = formData.get('description') as string

  if (!id || !title?.trim()) {
    throw new Error('Invalid input')
  }

  const task = await prisma.task.findUnique({
    where: { id },
  })

  if (!task || task.owner_id !== userId) {
    throw new Error('Task not found or unauthorized')
  }

  await prisma.task.update({
    where: { id },
    data: {
      title: title.trim(),
      description: description?.trim() || null,
    },
  })

  revalidatePath('/app')
}

export async function deleteTask(taskId: string) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  // Delete the task
  await prisma.task.delete({
    where: {
      id: taskId,
      owner_id: userId, // Ensure the task belongs to the user
    },
  })

  revalidatePath('/app')
}

export async function getProjects() {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  return prisma.project.findMany({
    where: {
      owner_id: userId,
    },
    orderBy: {
      created_at: 'asc',
    },
  })
}

export async function createProject(formData: FormData) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const name = formData.get('name') as string
  if (!name?.trim()) {
    throw new Error('Project name is required')
  }

  const project = await prisma.project.create({
    data: {
      name: name.trim(),
      owner_id: userId,
    },
  })

  revalidatePath('/app')
  return project
}
```

Next, you’ll need to create the page to render the tasks for a given project. Create `src/app/app/projects/[_id]/page.tsx` and paste in the following:

```tsx {{ filename: 'src/app/app/projects/[_id]/page.tsx' }}
import React from 'react'
import { auth } from '@clerk/nextjs/server'
import { prisma } from '@/lib/db'
import { notFound, redirect } from 'next/navigation'
import TaskList from '../../components/TaskList'

interface ProjectPageProps {
  params: Promise<{
    _id: string
  }>
}

export default async function Project({ params }: ProjectPageProps) {
  const { userId } = await auth()

  // If the user is not logged in, redirect to the sign-in page
  if (!userId) {
    return redirect('/sign-in')
  }

  const { _id } = await params
  const project = await prisma.project.findUnique({
    where: {
      id: _id,
    },
  })

  // Check if the project exists and belongs to the user
  if (!project || project.owner_id !== userId) {
    notFound()
  }

  // Get the project tasks
  const tasks = await prisma.task.findMany({
    where: {
      project_id: _id,
      owner_id: userId,
    },
    orderBy: {
      created_at: 'desc',
    },
  })

  return (
    <div className="flex h-screen">
      <TaskList title={project.name} tasks={tasks} projectId={project.id} />
    </div>
  )
}
```

Notice in the `TaskList` component that we’ve added `projectId` to the list of props. This is so that the currently active project ID can be passed to `CreateTaskInput` so that when a task is created, it knows what project to associate it with. Let’s update those two components now.

Modify `app/src/src/components/CreateTaskInput.tsx` to match the following:

```tsx {{ filename: 'app/src/src/components/CreateTaskInput.tsx', ins: [[9, 11], 14, [28, 30]], del: [13], prettier: false }}
'use client'

import { useState } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { createTask } from '@/app/app/actions'
import { PlusIcon } from 'lucide-react'

interface Props {
  projectId?: string
}

export default function CreateTaskInput() {
export default function CreateTaskInput({ projectId }: Props) {
  const [title, setTitle] = useState('')
  const [isSubmitting, setIsSubmitting] = useState(false)

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()

    // Don't create a task if the title is empty
    if (!title.trim()) return

    try {
      setIsSubmitting(true)
      const formData = new FormData()
      formData.append('title', title)
      if (projectId) {
        formData.append('project_id', projectId)
      }
      await createTask(formData)
      setTitle('')
    } finally {
      setIsSubmitting(false)
    }
  }

  return (
    <div className="group relative w-full rounded-full bg-white p-2 transition-shadow duration-200 focus-within:shadow-[0_4px_20px_-2px_rgba(96,165,250,0.3),0_4px_20px_-2px_rgba(192,132,252,0.3)] dark:bg-gray-800">
      <div className="absolute inset-0 rounded-full bg-gradient-to-r from-blue-400/25 to-purple-400/25 transition-opacity duration-200 group-focus-within:from-blue-400 group-focus-within:to-purple-400"></div>
      <div className="absolute inset-[1px] rounded-full bg-white transition-all group-focus-within:inset-[2px] dark:bg-gray-800"></div>
      <div className="relative">
        <form onSubmit={handleSubmit} className="flex w-full items-center gap-2">
          <Input
            type="text"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Add a task..."
            className="flex-1 border-0 bg-transparent text-gray-900 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:text-gray-100"
          />
          <Button
            type="submit"
            size="icon"
            disabled={isSubmitting || !title.trim()}
            className="flex !h-[30px] !min-h-0 !w-[30px] items-center justify-center !rounded-full !p-0 !leading-none"
          >
            <PlusIcon className="h-4 w-4" />
          </Button>
        </form>
      </div>
    </div>
  )
}
```

Next, update the `TaskCard` component to pass the name of the selected project through to the `EditTaskModal` to provide a quick reference to what project the task is part of.

Edit `src/app/app/components/TaskCard.tsx` to match the following:

```tsx {{ filename: 'src/app/app/components/TaskCard.tsx', ins: [11, 15, 75], del: [14], prettier: false }}
'use client';

import React from 'react';
import { toggleTask } from '../actions';
import EditTaskModal from './EditTaskModal';
import { cn } from '@/lib/utils';
import { Task } from '@prisma/client';

interface Props {
  task: Task;
  projectName: string;
}

export default function TaskCard({ task }: Props) {
export default function TaskCard({ task, projectName }: Props) {
  const [isModalOpen, setIsModalOpen] = React.useState(false);

  const handleClick = (e: React.MouseEvent) => {
    const target = e.target as HTMLElement;
    // Don't open modal if clicking the checkbox
    if (!target.closest('button')) {
      setIsModalOpen(true);
    }
  };

  return (
    <>
      <div
        onClick={handleClick}
        className={cn(
          "p-2 rounded-lg border border-transparent hover:border-gray-100 dark:border-gray-800 dark:hover:bg-gray-800/50 cursor-pointer transition-colors duration-200",
          task.is_completed && "opacity-50"
        )}
      >
        <div className="flex items-start justify-between">
          <div className="flex items-start gap-3">
            {/* Checkbox */}
            <button
              onClick={(e) => {
                e.stopPropagation();
                toggleTask(task.id);
              }}
              className="mt-1 h-4 w-4 flex-shrink-0 rounded border border-gray-300 dark:border-gray-600 flex items-center justify-center hover:border-gray-400 dark:hover:border-gray-500"
            >
              {task.is_completed && (
                <svg className="h-3 w-3 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                </svg>
              )}
            </button>
            {/* Task details */}
            <div>
              <h3 className={cn(
                "font-medium",
                task.is_completed && "line-through text-gray-400 dark:text-gray-500"
              )}>{task.title}</h3>

              {task.description && (
                <p className={cn(
                  "text-sm text-gray-500 dark:text-gray-400 mt-1",
                  task.is_completed && "line-through opacity-75"
                )}>
                  {task.description}
                </p>
              )}
            </div>
          </div>
        </div>
      </div>

      <EditTaskModal
        task={task}
        open={isModalOpen}
        onOpenChange={setIsModalOpen}
        projectName={projectName}
      />
    </>
  );
}
```

Now update `src/app/app/components/TaskList.tsx` to include the `projectId` prop and pass it to `CreateTaskInput`:

```tsx {{ filename: 'src/app/app/components/TaskList.tsx', ins: [10, 14, 26, 33], del: [13, 25, 32], prettier: false }}
'use client';

import TaskCard from './TaskCard';
import CreateTaskInput from './CreateTaskInput';
import { Task } from '@prisma/client';

interface Props {
  title: string;
  tasks: Task[];
  projectId?: string;
}

export default function TaskList({ title, tasks }: Props) {
export default function TaskList({ title, tasks, projectId }: Props) {
  return (
    <div className="h-screen flex flex-col w-full max-w-2xl p-8 gap-4">
      <h1 className="text-lg md:text-xl font-semibold">{title}</h1>

      <div className="w-full flex-1 rounded-xl">
        <div className="space-y-2">
          {tasks.length === 0 ? (
            <p className="text-gray-500 dark:text-gray-400">No tasks</p>
          ) : (
            tasks.map((task) => (
              <TaskCard key={task.id} task={task} />
              <TaskCard key={task.id} task={task} projectName={title} />
            ))
          )}
        </div>
      </div>
      <div className='w-full'>
        <CreateTaskInput />
        <CreateTaskInput projectId={projectId} />
      </div>
    </div>
  );
}
```

In order to access project data in real time from multiple client-side components, we’re going to use a Zustand store to keep things synchronized throughout the application. Using a store will allow projects to be edited and deleted without having to refresh the page. This will become more evident in the subsequent sections.

Create `src/lib/store.ts` and paste in the following:

```ts {{ filename: 'src/lib/store.ts' }}
import { Project } from '@/app/app/models'
import { create } from 'zustand'

interface ProjectStore {
  projects: Project[]
  setProjects: (projects: Project[]) => void
  updateProject: (id: string, updates: Partial<Project>) => void
}

export const useProjectStore = create<ProjectStore>((set) => ({
  projects: [],
  setProjects: (projects) => set({ projects }),
  updateProject: (id, updates) =>
    set((state) => ({
      projects: state.projects.map((project) =>
        project.id === id ? { ...project, ...updates } : project,
      ),
    })),
}))
```

The projects will be listed in the sidebar, alongside a button to create new projects as needed. Each element in the list will be its own component. Create `src/app/app/components/ProjectLink.tsx` and paste in the following:

```tsx {{ filename: 'src/app/app/components/ProjectLink.tsx' }}
'use client'

import React from 'react'
import Link from 'next/link'
import { FolderIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Project } from '../models'

interface Props {
  project: Project
  isCollapsed?: boolean
}

export default function ProjectLink({ project, isCollapsed }: Props) {
  return (
    <div className="group relative">
      <div className="flex items-center rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors group-hover:bg-white/75 dark:text-gray-200 dark:group-hover:bg-gray-800/50">
        <Link href={`/app/projects/${project.id}`} className="flex flex-1 items-center gap-2">
          <FolderIcon className="h-4 w-4 flex-shrink-0" />
          <span className={cn('transition-opacity duration-200', isCollapsed && 'opacity-0')}>
            {project.name}
          </span>
        </Link>
      </div>
    </div>
  )
}
```

Let’s create a component that will live in the sidebar that opens a modal to create a new project. Create the `src/app/app/components/CreateProjectButton.tsx` file and paste in the following:

```tsx {{ filename: 'src/app/app/components/CreateProjectButton.tsx' }}
'use client'

import { useState, useRef } from 'react'
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { createProject } from '@/app/app/actions'
import { useFormStatus } from 'react-dom'
import { PlusIcon } from 'lucide-react'
import { useProjectStore } from '@/lib/store'

function SubmitButton() {
  const { pending } = useFormStatus()
  return (
    <Button type="submit" disabled={pending}>
      Create Project
    </Button>
  )
}

export default function CreateProjectButton() {
  const [isOpen, setIsOpen] = useState(false)
  const formRef = useRef<HTMLFormElement>(null)
  const { projects, setProjects } = useProjectStore()

  async function onSubmit(formData: FormData) {
    try {
      const project = await createProject(formData)
      setProjects([...projects, project])
      setIsOpen(false)
    } catch (error) {
      console.error('Failed to create project:', error)
    }
  }

  return (
    <Dialog open={isOpen} onOpenChange={setIsOpen}>
      <DialogTrigger asChild>
        <Button variant="ghost" size="icon" className="h-5 w-5 text-sm">
          <PlusIcon />
        </Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Create a new project</DialogTitle>
        </DialogHeader>
        <form ref={formRef} action={onSubmit} className="space-y-4">
          <Input type="text" name="name" placeholder="Project name" required />
          <div className="flex justify-end">
            <SubmitButton />
          </div>
        </form>
      </DialogContent>
    </Dialog>
  )
}
```

Finally, you’ll update the sidebar to query the list of projects and populate the store when the component renders. Update `src/app/app/components/Sidebar.tsx` to match the following:

```tsx {{ filename: 'src/app/app/components/Sidebar.tsx', ins: [[7, 11], [15, 22], [69, 82]], del: [] }}
'use client'

import { cn } from '@/lib/utils'
import { ChevronRightIcon, ChevronLeftIcon, InboxIcon } from 'lucide-react'
import Link from 'next/link'
import { UserButton } from '@clerk/nextjs'
import { useEffect, useState } from 'react'
import CreateProjectButton from './CreateProjectButton'
import ProjectLink from './ProjectLink'
import { useProjectStore } from '@/lib/store'
import { getProjects } from '../actions'

function Sidebar() {
  const [isCollapsed, setIsCollapsed] = useState(false)
  const { projects, setProjects } = useProjectStore()

  useEffect(() => {
    // Only fetch if we don't have projects yet
    if (projects.length === 0) {
      getProjects().then(setProjects)
    }
  }, [projects.length, setProjects])

  return (
    <div
      className={cn(
        'h-screen border-r border-gray-200 bg-gradient-to-b from-blue-50 via-purple-50/80 to-blue-50 p-4 dark:border-gray-800 dark:from-blue-950/20 dark:via-purple-950/20 dark:to-blue-950/20',
        'transition-all duration-300 ease-in-out',
        isCollapsed ? 'w-16' : 'w-64',
      )}
    >
      <nav className="space-y-2">
        <div className="flex items-center justify-between gap-2">
          <div
            className={cn(
              'transition-all duration-300',
              isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
            )}
          >
            <UserButton showName />
          </div>
          <button
            onClick={() => setIsCollapsed(!isCollapsed)}
            className="flex-shrink-0 rounded-lg p-1 transition-colors hover:bg-white/75 dark:hover:bg-gray-800/50"
          >
            {isCollapsed ? (
              <ChevronRightIcon className="h-4 w-4" />
            ) : (
              <ChevronLeftIcon className="h-4 w-4" />
            )}
          </button>
        </div>

        <div
          className={cn(
            'transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <Link
            href="/app"
            className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-white/75 dark:text-gray-200 dark:hover:bg-gray-800/50"
          >
            <InboxIcon className="h-4 w-4" />
            <span>Inbox</span>
          </Link>
        </div>

        <div
          className={cn(
            'pt-4 transition-all duration-300',
            isCollapsed ? 'w-0 overflow-hidden' : 'w-auto',
          )}
        >
          <div className="flex items-center justify-between px-3 pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400">
            <span>Projects</span>
            <CreateProjectButton />
          </div>
          {projects.map((project) => (
            <ProjectLink key={project.id} project={project} isCollapsed={isCollapsed} />
          ))}
        </div>
      </nav>
    </div>
  )
}

export default Sidebar
```

You can now add projects from the sidebar and add tasks to those projects.

### Editing and deleting projects

Following the same design approach as earlier, we’ll now update the project page so that users can simply click the name of a project to edit it. We’ll also debounce the save so there is no need to manually click a save button. Because a Zustand store is being used, updating the name of the project in the store will automatically cause the new name to be displayed in the sidebar without having to refresh the page.

Start by appending the following server actions to `src/app/app/actions.ts`:

```ts {{ filename: 'src/app/app/actions.ts' }}
export async function updateProject(formData: FormData) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const id = formData.get('id') as string
  const name = formData.get('name') as string

  if (!id || !name?.trim()) {
    throw new Error('Invalid input')
  }

  const project = await prisma.project.findUnique({
    where: {
      id,
      owner_id: userId,
    },
  })

  if (!project) {
    throw new Error('Project not found')
  }

  await prisma.project.update({
    where: { id },
    data: {
      name: name.trim(),
    },
  })

  revalidatePath('/app')
}

export async function deleteProject(projectId: string) {
  const { userId } = await auth()
  if (!userId) {
    throw new Error('Unauthorized')
  }

  const project = await prisma.project.findUnique({
    where: {
      id: projectId,
      owner_id: userId,
    },
  })

  if (!project) {
    throw new Error('Project not found')
  }

  // Delete all tasks associated with the project first
  await prisma.task.deleteMany({
    where: {
      project_id: projectId,
    },
  })

  // Then delete the project
  await prisma.project.delete({
    where: {
      id: projectId,
    },
  })
}
```

Since the project name is rendered in the `<TaskList />` component, update `src/app/app/components/TaskList.tsx` to match the following:

```tsx {{ filename: 'src/app/app/components/TaskList.tsx', ins: [[6, 12], [21, 48], [52, 87]], del: [88] }}
'use client'

import TaskCard from './TaskCard'
import CreateTaskInput from './CreateTaskInput'
import { Task } from '@prisma/client'
import { useDebouncedCallback } from 'use-debounce'
import { Input } from '@/components/ui/input'
import { updateProject } from '../actions'
import { useRouter } from 'next/navigation'
import { cn } from '@/lib/utils'
import { useProjectStore } from '@/lib/store'
import { useEffect, useState } from 'react'

interface Props {
  title: string
  tasks: Task[]
  projectId?: string
}

export default function TaskList({ title, tasks, projectId }: Props) {
  const [editedTitle, setEditedTitle] = useState(title)
  const [isSubmitting, setIsSubmitting] = useState(false)
  const router = useRouter()
  const { updateProject: updateProjectInStore } = useProjectStore()

  useEffect(() => {
    setEditedTitle(title)
  }, [title])

  const debouncedUpdate = useDebouncedCallback(async (newTitle: string) => {
    if (!projectId || !newTitle.trim() || newTitle === title) return

    try {
      setIsSubmitting(true)
      const formData = new FormData()
      formData.append('id', projectId)
      formData.append('name', newTitle.trim())
      await updateProject(formData)
      // Update the store
      updateProjectInStore(projectId, { name: newTitle.trim() })
      router.refresh()
    } catch (error) {
      // If there's an error, reset to the original title
      setEditedTitle(title)
    } finally {
      setIsSubmitting(false)
    }
  }, 1000)

  return (
    <div className="flex h-screen w-full max-w-2xl flex-col gap-4 p-8">
      {projectId ? (
        <div className="group relative">
          <Input
            value={editedTitle}
            onChange={(e) => {
              setEditedTitle(e.target.value)
              debouncedUpdate(e.target.value)
            }}
            className={cn(
              'h-auto w-full p-1 text-lg font-semibold md:text-xl',
              'border-0 bg-transparent ring-0 focus-visible:ring-0 focus-visible:ring-offset-0',
              'placeholder:text-gray-500 dark:placeholder:text-gray-400',
              'hover:bg-gray-50 focus:bg-gray-50 dark:hover:bg-gray-800/50 dark:focus:bg-gray-800/50',
              '-ml-1 rounded px-1 shadow-none transition-colors',
            )}
            disabled={isSubmitting}
          />
          <div className="pointer-events-none absolute top-1/2 right-1 -translate-y-1/2 text-gray-400 opacity-0 transition-opacity group-hover:opacity-100">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="14"
              height="14"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              strokeWidth="2"
              strokeLinecap="round"
              strokeLinejoin="round"
            >
              <path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
            </svg>
          </div>
        </div>
      ) : (
        <h1 className="text-lg font-semibold md:text-xl">{title}</h1>
      )}
      <h1 className="text-lg font-semibold md:text-xl">{title}</h1>

      <div className="w-full flex-1 rounded-xl">
        <div className="space-y-2">
          {tasks.length === 0 ? (
            <p className="text-gray-500 dark:text-gray-400">No tasks</p>
          ) : (
            tasks.map((task) => <TaskCard key={task.id} task={task} projectName={title} />)
          )}
        </div>
      </div>
      <div className="w-full">
        <CreateTaskInput projectId={projectId} />
      </div>
    </div>
  )
}
```

To delete projects, we’ll use the same approach as we did with tasks by rendering a dropdown menu with an option to delete the project. Instead of in a modal though, we’ll add it to the `<ProjectLink />` component so that when the user hovers over a project in the sidebar, the menu icon will be displayed as a clickable button.

Update `src/app/app/components/ProjectLink.tsx` to match the following code:

```tsx {{ filename: 'src/app/app/components/ProjectLink.tsx', ins: [[8, 26], [34, 47], [59, 98]], del: [] }}
'use client'

import React from 'react'
import Link from 'next/link'
import { FolderIcon, MoreVertical, Trash2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Project } from '../models'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { deleteProject } from '../actions'
import { useProjectStore } from '@/lib/store'
import { useRouter } from 'next/navigation'

interface Props {
  project: Project
  isCollapsed?: boolean
}

export default function ProjectLink({ project, isCollapsed }: Props) {
  const [showDeleteDialog, setShowDeleteDialog] = React.useState(false)
  const [showMenu, setShowMenu] = React.useState(false)
  const { projects, setProjects } = useProjectStore()
  const router = useRouter()

  const handleDelete = async () => {
    try {
      await deleteProject(project.id)
      setProjects(projects.filter((p) => p.id !== project.id))
      router.push('/app')
    } catch (error) {
      console.error('Failed to delete project:', error)
    }
  }

  return (
    <div className="group relative">
      <div className="flex items-center rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors group-hover:bg-white/75 dark:text-gray-200 dark:group-hover:bg-gray-800/50">
        <Link href={`/app/projects/${project.id}`} className="flex flex-1 items-center gap-2">
          <FolderIcon className="h-4 w-4 flex-shrink-0" />
          <span className={cn('transition-opacity duration-200', isCollapsed && 'opacity-0')}>
            {project.name}
          </span>
        </Link>

        {!isCollapsed && (
          <DropdownMenu open={showMenu} onOpenChange={setShowMenu}>
            <DropdownMenuTrigger
              className="ml-2 rounded p-1 opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100"
              onClick={(e) => e.preventDefault()}
            >
              <MoreVertical className="h-4 w-4 text-gray-500" />
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem
                className="text-red-600 dark:text-red-400"
                onClick={() => setShowDeleteDialog(true)}
              >
                <Trash2 className="mr-2 h-4 w-4" />
                Delete Project
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        )}

        <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
          <AlertDialogContent>
            <AlertDialogHeader>
              <AlertDialogTitle>Delete Project</AlertDialogTitle>
              <AlertDialogDescription>
                Are you sure you want to delete "{project.name}"? This action cannot be undone and
                will delete all tasks associated with this project.
              </AlertDialogDescription>
            </AlertDialogHeader>
            <AlertDialogFooter>
              <AlertDialogCancel>Cancel</AlertDialogCancel>
              <AlertDialogAction
                onClick={handleDelete}
                className="bg-red-600 hover:bg-red-700 dark:bg-red-900 dark:hover:bg-red-800"
              >
                Delete
              </AlertDialogAction>
            </AlertDialogFooter>
          </AlertDialogContent>
        </AlertDialog>
      </div>
    </div>
  )
}
```

You can now update the names of projects and delete them as needed. Deleting a project will also delete any associated tasks with that project.

## Conclusion

When building any application, security should always be something considered early on in the process. By considering the principles laid out in this article, you can build a secure system with ease using Clerk and properly structuring the code that accesses your database.

In the next article of the series, we’ll explore how you can securely access the data within your Neon database from the front end using Row Level Security using Clerk.

\* Source: [**How Many Cyber Attacks Per Day: The Latest Stats and Impacts in 2025**](https://www.getastra.com/blog/security-audit/how-many-cyber-attacks-per-day/)

---

# Validate your SaaS idea while building an audience
URL: https://clerk.com/blog/validate-saas.md
Date: 2025-02-14
Category: Guides
Description: Learn how to validate a new SaaS idea with a Clerk waitlist and Loops newsletter.

**The fastest way to validate market demand for a SaaS? A waitlist.**

When used correctly, a waitlist isn't just a sign-up form - it's a powerful market research tool. By allowing potential users to signal their interest by registering their email, you create a valuable mechanism to validate your concept before investing significant time and resources.

Integrating your list with a communication tool such as a newsletter platform opens a direct channel with future customers to share product updates, solicit feedback, and build up anticipation for your launch.

In this article, you’ll learn how to use the Clerk drop-in waitlist component and integrate it with Loops.

## What is Clerk?

Clerk is a user management platform designed to get authentication integrated into your application as quickly and easily as possible. This includes drop-in UI components for common user management requirements such as sign-in/sign-up pages, social sign on providers, and user profiles.

One such is the Waitlist component that renders a form where potential customers can enter in their email address:

![The Clerk Waitlist component](./clerk-waitlist.png)

Upon entering their email address, it will be available in the Clerk dashboard under the **Waitlist** tab where you can quickly invite interested users to join your platform with a single click.

![The waitlist in the Clerk dashboard](./clerk-dash.png)

Clerk offers webhooks as a way to integrate with external systems. When email addresses are captured, Clerk offers a webhook event that can be used to inform another system that someone has entered their email into the waitlist.

The other system simply needs an HTTP endpoint designed to parse the webhook event data.

Loops offers an integration with Clerk by providing a webhook receiver endpoint that will automatically parse the event data and add the email address to your audience. It can also receive events when a user updates their info so that your list always contains the latest information about your users.

This integration combines the simplicity of Clerk's drop-in waitlist component with the flexibility of Loops's audience management, allowing you to validate market demand for your SaaS.

## How to follow along

While the process described above is the same regardless of the chosen framework, the remainder of this article will use [Next.js](/nextjs-authentication). You may use your own Next.js application with Clerk, bootstrap one using our [quickstart guide](/docs/quickstarts/nextjs), or clone the [`article-1` branch from the repository for Kozi](https://github.com/bmorrisondev/kozi/tree/article-1), an open-source project & knowledge management tool.

You’ll also need a domain name and a production Clerk instance configured with the appropriate DNS records. If you do not already and would still like to follow along, refer to [our guide on deploying your application to production](/docs/deployments/overview) before reading on.

### Why do I need a production Clerk instance?

When you first set up a Clerk application, we automatically create a development instance for you to quickly start integrating Clerk with your application. These development instances have a more relaxed security posture to make local development easier, such as not requiring HTTPS connections and using shared OAuth credentials for single sign-on providers across the Clerk platform.

Production instances are more secure and do not have the same security exceptions, but require several DNS records so that Clerk can handle authentication on your domain. This approach avoids cross-domain authentication which could increase the risk of a cross-site scripting attack.

## Adding the waitlist component

When added to a page, the `<Waitlist />` component renders [a form](/blog/building-a-nextjs-login-page-template) that matches the style of our other drop-in components. The following code snippet shows what would be required to add it to the `/waitlist` path of a Next.js application, along with a few Tailwind classes to center the form on the page:

```tsx {{ filename: 'src/app/waitlist/[[...waitlist]]/page.tsx' }}
import { Waitlist } from '@clerk/nextjs'

function WaitlistPage() {
  return (
    <div className="flex h-screen items-center justify-center">
      <Waitlist />
    </div>
  )
}

export default WaitlistPage
```

## Integrating with Loops

Over in Loops, navigate to your team settings, expand the **Integrations** node, and select **Clerk**. You can then enable the integration which will then show an **Endpoint URL** which Clerk can communicate with.

Copy this URL as you’ll need it in the next step.

![The Clerk integration in the Loops dashboard](./loops-clerk-integration.png)

Head back to the Clerk dashboard for your application and navigate to **Configure**, then select **Webhooks** from the left sidebar. Click **Add Endpoint**.

![The Clerk dashboard showing the Webhooks tab](./clerk-dash-webhooks.png)

Paste in the URL from Loops into the **Endpoint URL** field. In the **Subscribe to events** section, type in “waitlist” and check both options. You can also check `user.created` and `user.updated` as they are supported in Loops, but aren’t necessary for this guide. Once done, scroll to the bottom and click **Create**.

![The Clerk dashboard showing the Webhook URL and events](./clerk-dash-webhooks-config.png)

After creating the endpoint, locate the **Signing Secret** in the lower right of the field. Click the eye icon to show the secret and copy the value.

![The Clerk dashboard showing the Signing Secret](./clerk-dash-webhooks-config-secret.png)

Back in Loops, paste the signing secret into the **Signing Secret** field, toggle on the events you selected in Clerk, and click **Save** at the bottom of the form.

![The Loops dashboard showing where to place the signing secret](./loops-clerk-integration-secret.png)

And thats it! Going forward, anyone who enters their email address into your waitlist form will automatically be added to your mailing list in Loops.

## Conclusion

Gauging interest in your application idea is important for determining whether an idea is worth investing your time into. By integrating the Clerk `<Waitlist />` component with Loops, you can create a powerful feedback loop with your potential customers as the development of your application progresses.

While this article used Next.js to demonstrate how the waitlist component renders on a page, this component is available to many of our SDKs, including [React](/docs/quickstarts/react), [Vue.js](/docs/quickstarts/vue), [Astro](/docs/quickstarts/astro), and others.

---

# Postmortem: February 6, 2025 service outage
URL: https://clerk.com/blog/postmortem-feb-6-2025-service-outage.md
Date: 2025-02-11
Category: Company
Description: Learn more about our service outage, including the timeline of events and our remediations.

On Thursday, February 6th, 2025, a database query was directly executed to deprecate a feature for 3,700 customers, and an error in the query resulted in immediate downtime for those customers. In addition, the downtime triggered automatic retries elsewhere in our service which nearly overloaded our infrastructure, and created significant delays for our other customers for 4 minutes, until the retry backoff took effect.

The incident lasted a total of 26 minutes, from the initial error to when the query was successfully reversed, and our systems returned to normal.

As a provider of mission-critical infrastructure, we recognize that this outage is unacceptable. After a detailed review of the incident, we have determined several actions that can be taken to mitigate its recurrence. Some have already been implemented, while others will require more significant engineering efforts.

In this postmortem, we discuss the timeline of events, and our complete set of remediations.

## Timeline of events

- **9:43 UTC** — Erroneous update query runs, setting `false` values to `"true"` within a jsonb field.
- **9:45 UTC** — Engineers receive error alerts and begin investigating.
- **9:47 UTC** — First customer outage reports arrive.
- **9:48 UTC** — Internal incident is declared.
- **9:50 UTC** — Status page updated (initially with an incorrect start time).
- **10:00-10:04 UTC** — Engineers begin manually restoring service for customers while a bulk resolution is prepared.
- **10:05 UTC** — Bulk update query is executed to correct the issue.
- **10:06 UTC** — Bulk update query completes, service health is restored.
- **10:10 UTC** — Status page updated to reflect restored status with accurate start/end times.

## Remediations

### Tuning automatic retry mechanisms

One of our retry mechanisms was misconfigured to retry too aggressively on 500-class errors, which increased the blast radius of this event. An adjustment to the mechanism has already been applied, and an audit of other retry mechanisms is being conducted.

### Further limiting direct database access

Direct database access at Clerk is already significantly limited, with only a small subset of our most senior team having this permission. However, our processes indicated they should use their own judgement for when it is safe and appropriate to leverage the capability.

Going forward, these team members will retain access, but our policies will dictate that it is only leveraged in true emergency situations, when downtime is actively impacting our customers. Other changes must be executed from within our change management tooling.

### Mandating staged rollouts for all changes to critical infrastructure

In 2024, Clerk’s platform team developed several new mechanisms for staged rollouts. As Clerk has grown, we have seen a healthy culture where our engineers *demand* that staged rollout infrastructure is in place. In many cases, we’ve delayed launches to build more mechanisms where they are missing.

In our review since the incident, we confirmed that the vast majority of changes to our critical systems leverage staged rollouts. However, when our team noted exceptions, it was always because the change was considered simple, including the one that led to this incident.

In addition, our review revealed that different projects have approached building cohorts for staged rollouts differently.

Going forward, we will be mandating that all changes to critical infrastructure require staged rollouts. We will also codify a process for building and ordering cohorts, which will incorporate the number of active users an application is supporting, and the subscription plan that applications are enrolled on.

### Improving SDK resilience for session management service outages

Clerk’s session management service is designed with a once-per-minute JWT refresh. We leverage this design in three critical areas of our service:

- **Session revocation:** When a session is revoked administratively – either by the user or by an application administrator – the revocation is achieved by blocking new JWTs from being generated. Using a short-lived JWT means we can guarantee revocation within one minute.
- **Abuse detection and prevention:** CAPTCHAs during sign up have become less effective recently as AI has gained the ability to solve them. At the same time, freemium and trial pricing have become commonplace. We’re engaged in a constant cat-and-mouse game with these attackers, and have found that our once-per-minute session refresh mechanism is a much more effective place to detect and prevent abuse than sign up.
- **XSS mitigation:** JavaScript-accessible JWTs are an expectation of many application architectures, despite seeming antithetical to web security best practices on its face. The concern is that script-accessible JWTs can be exfiltrated during XSS attacks, which would allow continued use of the JWT even after the XSS is patched. Clerk can safely allow script access because our JWTs expire every minute, which ensures that successful exfiltration would not meaningfully extend an XSS attack.

In normal operation, our once-per-minute refresh is an implementation detail that most of our customers are not aware of. However, in the event of an outage like Thursday’s, it means our customers have a strong uptime dependency on Clerk.

Going forward, we would like to eliminate as much of this strong uptime dependency as possible. We believe we can update our SDKs so that if our session management service goes down, existing sessions are maintained throughout the outage, while new session creation, session revocation, abuse prevention, and XSS mitigation are not operational. This would result in future outages having less impact on our customers.

In the interest of full disclosure, we want to highlight that this is not a simple adjustment and will take time to develop. As a simple example of a challenge, we will need to ensure the `/.well-known/jwks.json` endpoint is hardened to avoid the downtime, and/or we need provide a mechanism to self-host the JWT public key. Regardless of the effort it takes, we are placing high priority on this project.

### Completely decoupling session management from user management

At a high level, Clerk operates two services: user management, which covers sign up, sign in, and user profiles, and session management, which only handles sessions. These two systems started tightly coupled, but have naturally decoupled with time as they represent significantly different workloads:

- User management requires relatively low read and write, but it has many moving pieces. There are many different settings, and our customers use thousands of different permutations of those settings. In addition, we’re frequently introducing new settings and modifying existing settings as authentication evolves.
- Session management is the opposite. It’s extremely high read, low write, and has relatively few moving pieces.

In this incident, an error in our user management service brought down our session management service.

Going forward, we plan to decouple session management from user management as much as possible. They will still be tightly *integrated*, since sign up and sign in lead to the creation of a session, but downtime in user management should not lead downtime in session management.

### Eliminating the use of JSON column types for structured and typed data

Some application settings are stored in JSON column types. These columns have been used primarily for convenience, with types being enforced at our compute layer. In this incident, strict typing was not enforced for the query because it was executed directly against the database, which led to the outage.

Going forward, in addition to further limiting direct database access, we are ceasing additional use of JSON column types for structured and typed data. Instead, we will use strongly typed database columns, which would have prevented the erroneous query from being executed. Over time, we will also migrate and deprecate our existing usage of JSON column types.

## Looking Ahead

We regret the impact this incident had on our customers. At Clerk, reliability is a top priority, and this postmortem reflects our commitment to transparency and continuous improvement.

Some fixes are already in place, while others—like enhanced SDK resilience and service decoupling—are being prioritized to prevent future incidents and strengthen our platform.

For any questions, please [contact support](/contact)

---

# Implement Role-Based Access Control in Next.js 15
URL: https://clerk.com/blog/nextjs-role-based-access-control.md
Date: 2025-02-07
Category: Company
Description: Learn Role-Based Access Control (RBAC) by building a complete Q&A platform.

Assigning permissions to individual users is a complex task, especially when you have a large number of users.

[Role-Based Access Control (RBAC)](/glossary#role-based-access-control-rbac) is a popular approach to managing access permissions in software applications, allowing you to assign different roles and permissions to different users.

This article will guide you through building a Q\&A platform using Next.js and Neon, and show you how to implement authentication and RBAC with [Clerk](/nextjs-authentication).

## What is Role-Based Access Control?

Before we get started, let's understand RBAC and how it will be implemented in the Q\&A platform.

RBAC is a security method that allows users to interact with features of an application or system based on their roles and the permissions granted to those roles.

This approach simplifies access management by grouping permissions into roles instead of assigning them to individual users, making it easier to maintain and scale as your application grows.

By implementing RBAC, organizations can enforce the [principle of least privilege](https://www.cyberark.com/what-is/least-privilege/), ensuring users only have access to the resources necessary for their specific responsibilities.

Below is a breakdown of the permissions for each role in the Q\&A platform:

| Role        | Description                                                               | Permissions                                                                                                                            |
| ----------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Viewer      | Users not signed in or logged into the Q\&A platform.                     | • View questions and answers                                                                                                           |
| Contributor | Registered users who can ask or answer questions in the Q\&A platform.    | Everything from the Viewer role + • Post questions• Answer questions• Edit own questions and answers• Delete own questions and answers |
| Moderator   | Users with additional permissions to manage content in the Q\&A platform. | Everything from the Contributor role + • Admin dashboard access• Approve and disapprove questions• Approve and disapprove answers      |
| Admin       | Users with full control over the Q\&A platform.                           | Everything from the Moderator role + • Edit others' questions and answers• Delete others' questions and answers• Manage user roles     |

With that in mind, let us jump straight into building the Q\&A platform and implement RBAC using Clerk.

> \[!IMPORTANT]
> Clerk provides two approaches to RBAC.
>
> [Organizations](/docs/organizations/overview) is ideal for B2B applications like GitHub or Notion, where your customers are teams or companies who need to invite team members and manage their roles. It includes built-in role management and components for role-based rendering.
>
> [User metadata](/docs/references/nextjs/basic-rbac), demonstrated in this guide, is better suited for B2C applications with simple permission structures where individual users have different access levels but don't belong to teams.

## What you'll build

Before writing any code, let's take a look at how our platform works.

The landing page enables users to sign up or sign in using the **Start Exploring** button. It features a clean design with a navigation menu and welcome message.

Once signed in, users can ask questions and provide answers. Each question displays the author's name, timestamp, and interaction options. Users can edit or delete their own content, and view answers from other contributors.

![Homepage of a Q\&A Platform with a centered welcome message, navigation menu at the top showing Home, Q\&A, and Admin options, and a user profile icon. The main content features a large 'Welcome to Our Q\&A Platform' heading, followed by a subtitle encouraging community participation and a 'Start Exploring' button. The page has a clean, minimal design with a white background and footer copyright text.](./home-page.png)

![Q\&A Platform page showing a question input field at the top with an 'Ask' button. Below are two example questions: 'What is React used for?' and 'How many days are in a week?' Each question displays the author's name (Brian Morrison), timestamp, and has edit and delete icons. Questions include their answers with similar metadata and interaction options. The page maintains the same header with navigation menu and user profile icon as the homepage.](./qa-page.png)

Administrators can review and moderate all submitted content through the Admin Dashboard. The page displays questions and answers with approval status indicators, allowing admins to approve or reject content using simple checkmark and X icons.

![Admin Dashboard showing moderation controls for Q\&A content. The page displays questions and answers with approval status indicators - green 'Approved' tags for accepted content and red 'Disapproved' tags for rejected content. Each entry has approve/reject buttons (green checkmark and red X icons). The dashboard includes a 'Set Roles' button at the top right. Questions shown include 'What is React used for?' and 'How many days are in a week?' with their respective answers and moderation statuses. The page maintains the consistent header with navigation and user profile.](./admin-dashboard.png)

Finally, administrators can manage user permissions through the role management page. Using a search interface, admins can find users and assign appropriate roles (Admin, Moderator, Contributor, or Viewer) or remove existing roles.

![User role management page with a search bar and Submit button at the top. Below shows a user profile for Brian Morrison with their current role listed as admin. A row of role management buttons includes 'Make Admin', 'Make Moderator', 'Make Contributor', 'Make Viewer', and a red 'Remove Role' button. The page maintains the standard Q\&A Platform header with navigation menu and user profile icon.](./user-roles-page.png)

## Building the frontend

In this section, we will build the frontend of the Q\&A platform. In order to follow along, you should have a basic understanding of React or Next.js, as well as Node.js installed.

> If you're new to Next.js authentication, check out our [Ultimate Guide to Next.js Authentication](/blog/nextjs-authentication) for foundational concepts.

### Install dependencies

Start by running the following command in your terminal to bootstrap a Next.js application, accepting the default options as they are presented:

```bash
npx create-next-app@latest qa-app
cd qa-app
```

Next, run the following commands to initialize shadcn/ui, once again accepting the default configuration options as they are presented:

```bash
npx shadcn@latest init
```

Add the necessary components to build the UI components of the Q\&A platform:

```bash
npx shadcn@latest add button input card separator badge
```

Finally, install Lucide React, which will be used to render the icons in the UI components:

```bash
npm install lucide-react
```

With the dependencies in place, let's create the components that used with the layout, starting with the type definitions we'll use throughout the process.

### Creating the header and updating the homepage

The header component will allow our users to navigate to different parts of the application, as well as hold the Clerk `UserButton` component (to be done later in this guide).

Create the `src/components/Header.tsx` file and paste in the following code:

```tsx {{ filename: 'src/components/Header.tsx' }}
'use client'

import Link from 'next/link'
import { Button } from '@/components/ui/button'

const Header = () => {
  return (
    <header className="border-b bg-white">
      <div className="container mx-auto px-4">
        <div className="flex h-16 items-center justify-between">
          <Link href="/" className="text-xl font-bold">
            Q&A platform
          </Link>
          <nav>
            <ul className="flex space-x-4">
              <li>
                <Link href="/">
                  <Button variant="ghost">Home</Button>
                </Link>
              </li>
              <li>
                <Link href="/qa">
                  <Button variant="ghost">Q&A</Button>
                </Link>
              </li>

              <li>
                <Link href="/admin">
                  <Button variant="ghost">Admin</Button>
                </Link>
              </li>
            </ul>
          </nav>
        </div>
      </div>
    </header>
  )
}

export default Header
```

With the header component in place, replace the code in `app/page.tsx` with the following, which will update the homepage to use the header component and match the demo:

```tsx {{ filename: 'app/page.tsx' }}
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import Header from '@/components/Header'

export default function Home() {
  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="flex-grow">
        <section className="flex w-full items-center justify-center py-12 md:py-24 lg:py-32 xl:py-48">
          <div className="container px-4 md:px-6">
            <div className="flex flex-col items-center space-y-4 text-center">
              <div className="space-y-2">
                <h1 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl lg:text-6xl/none">
                  Welcome to Our Q&A platform
                </h1>
                <p className="mx-auto max-w-[700px] text-gray-500 md:text-xl dark:text-gray-400">
                  Join our community to ask questions, share knowledge, and learn from others.
                </p>
              </div>
              <div className="space-x-4">
                <Link href="/qa">
                  <Button size="lg">Start Exploring</Button>
                </Link>
              </div>
            </div>
          </div>
        </section>
      </main>
      <footer className="flex w-full items-center justify-center bg-gray-100 py-6 dark:bg-gray-800">
        <div className="container px-4 md:px-6">
          <p className="text-center text-sm text-gray-500 dark:text-gray-400">
            (c) 2024 Q&A platform. All rights reserved.
          </p>
        </div>
      </footer>
    </div>
  )
}
```

### Create the Q\&A section

Next, you'll build out the Q\&A section, where signed-in users can ask and answer questions and anonymous users can view questions. We'll start by creating a few shared components that will be used throughout the application before creating the page that will display the Q\&A section.

Let's start by creating a file named `types/types.d.ts` to define the types we'll be using. Populate it with the following code:

```ts {{ filename: 'types/types.d.ts' }}
interface Answer {
  id: number | null
  ans: string
  approved?: boolean | null
  contributor: string
  contributorId: string
  questionId: number
  timestamp?: string // ISO 8601 string format
}

interface Question {
  id: number | null
  quiz: string
  approved: boolean | null
  answers: Answer[]
  contributor: string
  contributorId: string
  timestamp?: string // ISO 8601 string format
}

type Roles = 'admin' | 'moderator' | 'contributor' | 'viewer'
```

Now you'll create the component that renders the form where users can ask questions. Create the `src/components/QuestionForm.tsx` file and paste in the following:

```tsx {{ filename: 'src/components/QuestionForm.tsx' }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

interface QuestionFormProps {
  onSubmit: (question: string) => void
}

export default function QuestionForm({ onSubmit }: QuestionFormProps) {
  const [quiz, setQuiz] = useState('')
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (quiz.trim()) {
      onSubmit(quiz)
      setQuiz('')
      setShowSubmitText(true)
    }
  }

  return (
    <form onSubmit={handleSubmit} className="mb-6">
      <div className="flex gap-2">
        <div className="flex-grow">
          <Input
            type="text"
            name="quiz"
            id="quiz"
            value={quiz}
            onChange={(e) => setQuiz(e.target.value)}
            placeholder="Ask a question..."
            className="flex-grow"
          />
          <div className="h-4 text-sm text-green-500 transition-all">
            {showSubmitText ? 'Your question has been submitted for review.' : ''}
          </div>
        </div>
        <Button type="submit">Ask</Button>
      </div>
    </form>
  )
}
```

Next, modify the `src/lib/utils.ts` file to add a helper function used to format dates in a more friendly way, which will be used in the `QuestionItem` and `AnswerItem` components you'll create in a moment:

```ts {{ filename: 'src/lib/utils.ts', ins: [[8, 17]], del: [] }}
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

export function formatDate(dateString: string) {
  const date = new Date(dateString)
  return date.toLocaleString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}
```

Now create a `QuestionItem` component that manages and displays a question and its answers. Moreover, the component allows adding, editing, and deleting of questions or answers.

Create the `src/components/QuestionItem.tsx` file and paste the following code into the file:

```tsx {{ filename: 'src/components/QuestionItem.tsx' }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Pencil, Trash2 } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import AnswerItem from './AnswerItem'
import { formatDate } from '@/lib/utils'

interface Props {
  question: Question
  onEditQuestion: (id: number, newText: string) => void
  onDeleteQuestion: (id: number) => void
  onAddAnswer: (questionId: number, answerText: string) => void
  onEditAnswer: (answerId: number, newText: string) => void
  onDeleteAnswer: (answerId: number) => void
}

export default function QuestionItem({
  question,
  onEditQuestion,
  onDeleteQuestion,
  onAddAnswer,
  onEditAnswer,
  onDeleteAnswer,
}: Props) {
  const [answer, setAnswer] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [editedQuestion, setEditedQuestion] = useState(question.quiz)
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (answer.trim()) {
      if (question.id !== null) {
        onAddAnswer(question.id, answer)
        setShowSubmitText(true)
      }
      setAnswer('')
    }
  }

  const handleQuestionEdit = () => {
    if (editedQuestion.trim() && editedQuestion !== question.quiz) {
      if (question.id !== null) {
        onEditQuestion(question.id, editedQuestion)
      }
      setIsEditing(false)
    }
  }

  const handleAnswerEdit = async (answerId: number | null, newText: string) => {
    if (answerId !== null && question.id !== null) {
      await onEditAnswer(answerId, newText)
    }
  }

  const handleAnswerDelete = async (answerId: number | null) => {
    if (answerId !== null && question.id !== null) {
      await onDeleteAnswer(answerId)
    }
  }

  return (
    <Card>
      <CardHeader>
        {isEditing ? (
          <div className="flex gap-2">
            <Input
              value={editedQuestion}
              onChange={(e) => setEditedQuestion(e.target.value)}
              className="flex-grow"
            />
            <Button onClick={handleQuestionEdit}>Save</Button>
            <Button variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <div>
            <div className="mb-2 flex items-center justify-between">
              <CardTitle>{question.quiz}</CardTitle>

              <div>
                <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                  <Pencil className="h-4 w-4" />
                </Button>
                <Button
                  variant="ghost"
                  size="icon"
                  onClick={() => question.id !== null && onDeleteQuestion(question.id)}
                >
                  <Trash2 className="h-4 w-4" />
                </Button>
              </div>
            </div>
            <div className="text-sm text-gray-500">
              <span>{question.contributor}</span>
              <span> • </span>
              <span>{question.timestamp && formatDate(question.timestamp)}</span>
            </div>
          </div>
        )}
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.filter((a) => a.approved !== false).length > 0 ? (
          <ul className="space-y-4">
            {question.answers
              .filter((a) => a.approved !== false)
              .map((answer, index, filteredAnswers) => (
                <li key={answer.id}>
                  <AnswerItem
                    answer={answer}
                    onEditAnswer={(newText) => handleAnswerEdit(answer.id, newText)}
                    onDeleteAnswer={() => handleAnswerDelete(answer.id)}
                  />
                  {index < filteredAnswers.length - 1 && <Separator className="my-2" />}
                </li>
              ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>

      <CardFooter>
        <form onSubmit={handleSubmit} className="w-full">
          <div className="flex gap-2">
            <div className="flex-grow">
              <Input
                type="text"
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Add an answer..."
              />

              <div className="h-4 text-sm text-green-500 transition-all">
                {showSubmitText ? 'Your answer has been submitted for review.' : ''}
              </div>
            </div>
            <Button type="submit">Answer</Button>
          </div>
        </form>
      </CardFooter>
    </Card>
  )
}
```

Your editor may be displaying an error regarding the `AnswerItem` component, which does not yet exist. This component is used to render answers for each question.

Create the `src/components/AnswerItem.tsx` file and paste the following code into the file:

```tsx {{ title: 'src/components/AnswerItem.tsx' }}
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Pencil, Trash2 } from 'lucide-react'
import { formatDate } from '@/lib/utils'

interface Props {
  answer: Answer
  onEditAnswer: (newText: string) => void
  onDeleteAnswer: () => void
}

function AnswerItem({ answer, onEditAnswer, onDeleteAnswer }: Props) {
  const [isEditing, setIsEditing] = useState(false)
  const [editedAnswer, setEditedAnswer] = useState(answer.ans)

  const handleEdit = () => {
    if (editedAnswer.trim() && editedAnswer !== answer.ans) {
      onEditAnswer(editedAnswer)
      setIsEditing(false)
    }
  }

  return (
    <div>
      {isEditing ? (
        <div className="flex w-full gap-2">
          <Input
            value={editedAnswer}
            onChange={(e) => setEditedAnswer(e.target.value)}
            className="flex-grow"
          />
          <Button onClick={handleEdit}>Save</Button>
          <Button variant="outline" onClick={() => setIsEditing(false)}>
            Cancel
          </Button>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="flex items-start justify-between">
            <p>{answer.ans}</p>

            <div>
              <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                <Pencil className="h-4 w-4" />
              </Button>
              <Button variant="ghost" size="icon" onClick={onDeleteAnswer}>
                <Trash2 className="h-4 w-4" />
              </Button>
            </div>
          </div>
          <div className="text-sm text-gray-500">
            <span>{answer.contributor}</span>
            <span> • </span>
            <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default AnswerItem
```

Now you'll create the page that manages and displays questions and answers, handles form submissions, and interacts with the user. Note that the code below contains function placeholders that will be implemented later in this article to interact with the database.

Create the `app/qa/page.tsx` file and paste the following code into the file:

```tsx {{ filename: 'app/qa/page.tsx' }}
'use client'

import { useState, useEffect } from 'react'
import QuestionForm from '../../components/QuestionForm'
import QuestionItem from '@/components/QuestionItem'
import Header from '../../components/Header'

export default function QAPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const addQuestion = async (question: string) => {}
  const editQuestion = async (id: number, newText: string) => {}
  const deleteQuestion = async (id: number) => {}
  const addAnswer = async (questionId: number, answer: string) => {}
  const editAnswer = async (answerId: number, newText: string) => {}
  const deleteAnswer = async (answerId: number) => {}

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <QuestionForm onSubmit={addQuestion} />
        {Array.isArray(questions) && (
          <div className="space-y-4">
            {questions.map((question) => (
              <QuestionItem
                key={question.id}
                question={question}
                onEditQuestion={editQuestion}
                onDeleteQuestion={deleteQuestion}
                onAddAnswer={addAnswer}
                onEditAnswer={editAnswer}
                onDeleteAnswer={deleteAnswer}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}
```

### Creating the admin area

With the home page and Q\&A page created, it's time to create the admin area. The admin area will be used to manage questions and answers.

Start by creating the `src/components/QuestionCard.tsx` which is used by the admin page for approving and managing questions and answers. Paste the following into that file:

```tsx {{ filename: 'src/components/QuestionCard.tsx' }}
'use client'

import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { CheckCircle, XCircle } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { formatDate } from '@/lib/utils'

interface Props {
  question: Question
  onQuestionApproved: (id: number) => void
  onQuestionDisapproved: (id: number) => void
  onAnswerApproved: (answerId: number) => void
  onAnswerDisapproved: (answerId: number) => void
}

export default function QuestionCard({
  question,
  onQuestionApproved,
  onQuestionDisapproved,
  onAnswerApproved,
  onAnswerDisapproved,
}: Props) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex flex-col">
          <div className="mb-2 flex items-start justify-between">
            <div className="flex items-center space-x-2">
              <span className="text-xl">{question.quiz}</span>
              <ApprovalBadge approved={question.approved} />
            </div>

            <div>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => question.id !== null && onQuestionApproved(question.id)}
              >
                <CheckCircle className="h-4 w-4 text-green-500" />
              </Button>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => question.id !== null && onQuestionDisapproved(question.id)}
              >
                <XCircle className="h-4 w-4 text-red-500" />
              </Button>
            </div>
          </div>
          <div className="text-sm text-gray-500">
            <span>{question.contributor}</span>
            <span> • </span>
            <span>{question.timestamp && formatDate(question.timestamp)}</span>
          </div>
        </CardTitle>
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.length > 0 ? (
          <ul className="space-y-4">
            {question.answers.map((answer) => (
              <li key={answer.id} className="flex flex-col">
                <div className="mb-2 flex items-start justify-between">
                  <div className="flex items-center space-x-2">
                    <span>{answer.ans}</span>
                    <ApprovalBadge approved={answer.approved ?? null} />
                  </div>
                  <div>
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => {
                        if (question.id !== null && answer.id !== null) {
                          onAnswerApproved(answer.id)
                        }
                      }}
                    >
                      <CheckCircle className="h-4 w-4 text-green-500" />
                    </Button>
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => {
                        if (question.id !== null && answer.id !== null) {
                          onAnswerDisapproved(answer.id)
                        }
                      }}
                    >
                      <XCircle className="h-4 w-4 text-red-500" />
                    </Button>
                  </div>
                </div>
                <div className="text-sm text-gray-500">
                  <span>{answer.contributor}</span>
                  <span> • </span>
                  <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
                </div>
              </li>
            ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>
    </Card>
  )
}

function ApprovalBadge({ approved }: { approved: boolean | null }) {
  if (approved === true) {
    return (
      <Badge variant="outline" className="border-green-300 bg-green-100 text-green-800">
        Approved
      </Badge>
    )
  } else if (approved === false) {
    return (
      <Badge variant="outline" className="border-red-300 bg-red-100 text-red-800">
        Disapproved
      </Badge>
    )
  } else {
    return (
      <Badge variant="outline" className="border-yellow-300 bg-yellow-100 text-yellow-800">
        Pending
      </Badge>
    )
  }
}
```

Create the `app/admin/page.tsx` file, used to render the area for admins and moderators, and paste the following code into the file:

```tsx {{ filename: 'app/admin/page.tsx' }}
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import Header from '@/components/Header'
import QuestionCard from '@/components/QuestionCard'

export default function AdminPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const onQuestionApproved = async (id: number) => {}
  const onQuestionDisapproved = async (id: number) => {}
  const onAnswerApproved = async (answerId: number) => {}
  const onAnswerDisapproved = async (answerId: number) => {}

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <h1 className="mb-6 text-3xl font-bold">Admin Dashboard</h1>
        <div className="mb-4 flex justify-end">
          <Button>
            <Link href="/admin/set-user-roles">Set Roles</Link>
          </Button>
        </div>
        <div className="space-y-4">
          {questions.map((question) => (
            <QuestionCard
              key={question.id}
              question={question}
              onQuestionApproved={onQuestionApproved}
              onQuestionDisapproved={onQuestionDisapproved}
              onAnswerApproved={onAnswerApproved}
              onAnswerDisapproved={onAnswerDisapproved}
            />
          ))}
        </div>
      </main>
    </div>
  )
}
```

### Testing the application

With all of our pages created, you can test the application by running the following command in the terminal, which will start the dev server and allow you to view the application in your browser:

```bash
npm run dev
```

By default it runs on `localhost:3000`, but may use a different port if `3000` is already in use. Use the provided URL to access the application.

## Adding Clerk for authentication and authorization

Now let's add authentication to the application using Clerk. In your browser, go to [the Clerk dashboard](https://dashboard.clerk.com/) to create an account if you don't already have one, which will automatically walk you through setting up your first application with Clerk. If you already have an account, sign in and create a new application, which will also guide you through setting up Clerk.

Follow steps 1-3 shown in the onboarding guide to install and configure Clerk in your Next.js application. Return to this page once you are finished to continue the tutorial.

### Setting up Clerk in the application

At this point, the Clerk SDK should be installed, and the middleware should be defined per the quickstart instructions. Next, you'll need to wrap the application with the `<ClerkProvider>` which will allow Clerk to protect pages that require authentication.

Update the `app/layout.tsx` file to wrap the application with the `<ClerkProvider>`:

```tsx {{ filename: 'app/layout.tsx', ins: [4, 27, 33], del: [] }}
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import { ClerkProvider } from '@clerk/nextjs'

const geistSans = Geist({
  variable: '--font-geist-sans',
  subsets: ['latin'],
})

const geistMono = Geist_Mono({
  variable: '--font-geist-mono',
  subsets: ['latin'],
})

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

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

Add the following environment variables to your `.env.local` file, which tell Clerk where to redirect users when they sign up or sign in:

```env {{ filename: '.env.local' }}
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
```

Next, update the `header` component to include a `UserButton` if the user is signed in, or a `SignInButton` if the user is not signed in:

```tsx {{ filename: 'src/app/components/header.tsx', ins: [3, [34, 43]], del: [] }}
'use client'

import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import Link from 'next/link'
import { Button } from '@/components/ui/button'

const Header = () => {
  return (
    <header className="border-b bg-white">
      <div className="container mx-auto px-4">
        <div className="flex h-16 items-center justify-between">
          <Link href="/" className="text-xl font-bold">
            Q&A platform
          </Link>
          <nav>
            <ul className="flex space-x-4">
              <li>
                <Link href="/">
                  <Button variant="ghost">Home</Button>
                </Link>
              </li>
              <li>
                <Link href="/qa">
                  <Button variant="ghost">Q&A</Button>
                </Link>
              </li>

              <li>
                <Link href="/admin">
                  <Button variant="ghost">Admin</Button>
                </Link>
              </li>

              <SignedIn>
                <li className="flex items-center">
                  <UserButton />
                </li>
              </SignedIn>
              <SignedOut>
                <li className="flex items-center rounded bg-black px-2 font-bold text-white">
                  <SignInButton mode="modal" />
                </li>
              </SignedOut>
            </ul>
          </nav>
        </div>
      </div>
    </header>
  )
}

export default Header
```

### Test it out

If your application is no longer running, start it up again with `npm run dev` and use the updated header to log into the application. This will let you create a user account and redirect you back to the home page.

Once logged in, notice how the header includes the `UserButton` instead of the `SignInButton`.

## Configuring RBAC with Clerk

Now let's add RBAC to the application using Clerk metadata. The role for a specific user will be set in the Clerk metadata, which is arbitrary data that is stored alongside a user that can be accessed and modified through the Clerk API, as well as directly in the Dashboard.

### Set a role for the user

Log into the Clerk dashboard and navigate to the **Users** page and select your user account. Scroll down to the *User metadata* section and select **Edit** next to the *Public* option.

Add the following JSON and select Save to manually add the admin role to your own user account in order for it to have all the system permissions. Later in the tutorial, you will add a basic admin tool to change a user's role.

```json
{
  "role": "admin"
}
```

### Include the user role with the Clerk metadata

Next, you'll need to update the token created by Clerk to include the metadata when it's created. This will allow you to check the role of the user without having to make an additional API call.

In the Clerk Dashboard, navigate to the **Sessions** page. Under the *Customize session token* section, select **Edit**. In the modal that opens, enter the following JSON and select **Save**.

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

### Declare the role types for the Metadata

Go back to the project and create a global type file to add type definitions for the metadata. Create the `types/global.d.ts` file and paste the following code into the file:

```ts {{ filename: 'types/global.d.ts' }}
export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      role?: Roles
    }
  }
}
```

### Updating your middleware

The middleware is used to check each request it comes in and apply authentication logic. Let's update the middleware to check the role of the user and redirect them to the appropriate page.

Update `src/middleware.ts` as follows:

```ts {{ filename: 'src/middleware.ts', ins: [4, 5, [8, 17]], del: [7] }}
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

// The route matcher defines routes that should be protected
const isAdminRoute = createRouteMatcher(['/admin(.*)'])

export default clerkMiddleware()
export default clerkMiddleware(async (auth, req) => {
  // Fetch the user's role from the session claims
  const userRole = (await auth()).sessionClaims?.metadata?.role

  // Protect all routes starting with `/admin`
  if (isAdminRoute(req) && !(userRole === 'admin' || userRole === 'moderator')) {
    const url = new URL('/', req.url)
    return NextResponse.redirect(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)(.*)',
  ],
}
```

### Setting user roles from the application

Now we can define a new page in the application that will let admins set user roles. We'll start by creating the server actions that will be used to set the user role by passing in the role name.

Create the `src/app/admin/set-user-roles/actions.ts` file and paste the following code into the file:

```ts {{ filename: 'src/app/admin/set-user-roles/actions.ts' }}
'use server'

import { clerkClient } from '@clerk/nextjs/server'
import { checkRole } from './utils'

export async function setRole(formData: FormData): Promise<void> {
  const client = await clerkClient()

  try {
    const res = await client.users.updateUser(formData.get('id') as string, {
      publicMetadata: { role: formData.get('role') },
    })
    console.log({ message: res.publicMetadata })
  } catch (err) {
    throw new Error(err instanceof Error ? err.message : String(err))
  }
}

export async function removeRole(formData: FormData): Promise<void> {
  const client = await clerkClient()

  try {
    const res = await client.users.updateUser(formData.get('id') as string, {
      publicMetadata: { role: null },
    })
    console.log({ message: res.publicMetadata })
  } catch (err) {
    throw new Error(err instanceof Error ? err.message : String(err))
  }
}
```

Finally, we'll create a page that allows admins to search through users using the Clerk Backend API and the above server actions to set their role.

Create a file called `src/app/admin/set-user-roles/page.tsx` and paste in the following code to populate the page:

```tsx {{ filename: 'src/app/admin/set-user-roles/page.tsx' }}
// import { SearchUsers } from "./SearchUsers";
import { clerkClient } from '@clerk/nextjs/server'
import { removeRole, setRole } from './actions'
import Header from '@/components/Header'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

export default async function AdminDashboard(params: {
  searchParams: Promise<{ search?: string }>
}) {
  const query = (await params.searchParams).search

  const client = await clerkClient()
  const users = query ? (await client.users.getUserList({ query })).data : []

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <form className="mb-6">
          <div className="flex flex-col gap-2">
            <label htmlFor="search">Search for users</label>
            <div className="flex gap-2">
              <Input id="search" name="search" type="text" className="flex-grow" />
              <Button type="submit">Submit</Button>
            </div>
          </div>
        </form>
        {users.map((user) => (
          <div key={user.id} className="flex min-h-screen flex-col">
            <div className="space-y-4 rounded-md bg-white p-4 shadow-md">
              <div className="text-lg font-semibold text-gray-800">
                {user.firstName} {user.lastName}
              </div>

              <div className="text-sm text-gray-600">
                {
                  user.emailAddresses.find((email) => email.id === user.primaryEmailAddressId)
                    ?.emailAddress
                }
              </div>

              <div className="text-sm font-medium text-blue-600">
                Role: {user.publicMetadata.role as string}
              </div>
              <div className="mt-2 flex space-x-4">
                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="admin" name="role" />
                  <Button type="submit">Make Admin</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="moderator" name="role" />
                  <Button type="submit">Make Moderator</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="contributor" name="role" />
                  <Button type="submit">Make Contributor</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="viewer" name="role" />
                  <Button type="submit">Make Viewer</Button>
                </form>

                <form action={removeRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <Button
                    type="submit"
                    className="rounded-md bg-red-600 px-4 py-2 text-white transition hover:bg-red-700"
                  >
                    Remove Role
                  </Button>
                </form>
              </div>
            </div>
          </div>
        ))}
      </main>
    </div>
  )
}
```

Add three more users to the Q\&A platform, then go to Admin page and click the **Set Roles** button. Search for the users you added and set their roles by clicking either the **Make Admin**, **Make Moderator**, **Make Contributor**, or **Make Viewer** button.

## Integrate with Postgres using Neon

In this section, you will learn how to integrate Neon Postgres with Clerk in the Q\&A platform, using `drizzle-orm` and `drizzle-kit` to interact with the database.

### Creating the database

Start by creating a new Neon database. Open your browser and go to neon.tech. Create an account if you don't already have one, then create a new database. Once the database is created, you'll be presented with a Quickstart screen. Select the **Copy snippet** button to copy it to your clipboard:

![The Neon Quickstart screen](./neon-connection-string.png)

Paste it into your `.env.local` file as `DATABASE_URL` like so:

```env {{ filename: '.env.local' }}
DATABASE_URL=postgresql://neondb_owner:***************@ep-black-boat-a8ryq543-pooler.eastus2.azure.neon.tech/neondb?sslmode=require
```

### Install the dependencies

Now you'll need to install the following dependencies:

- `drizzle-orm` - The ORM that the application will use to interact with the database.
- `drizzle-kit` - The tool that will generate migrations and interact with the database.
- `@neondatabase/serverless` - The driver that will be used to connect to the database.

Run the following command to install the dependencies:

```bash
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit 
```

### Setting up the database schema

Next you'll create the schema file which `drizzle-orm` will use to interact with the database, while `drizzle-kit` will be used to apply schema changes to the database.

Create a new file called `src/db/schema.ts` and paste in the following code:

```ts {{ filename: 'src/db/schema.ts' }}
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'

// Questions table
export const questions = pgTable('questions', {
  id: serial('id').primaryKey(),
  quiz: text('quiz').notNull(),
  approved: boolean('approved'),
  contributor: text('contributor').notNull(),
  contributorId: text('contributor_id').notNull(),
  timestamp: timestamp('timestamp', { withTimezone: true }).defaultNow(),
})

// Answers table
export const answers = pgTable('answers', {
  id: serial('id').primaryKey(),
  ans: text('ans').notNull(),
  approved: boolean('approved'),
  contributor: text('contributor').notNull(),
  contributorId: text('contributor_id').notNull(),
  questionId: integer('question_id')
    .notNull()
    .references(() => questions.id),
  timestamp: timestamp('timestamp', { withTimezone: true }).defaultNow(),
})

// Define relationships using Drizzle's relations function
export const questionsRelations = relations(questions, ({ many }) => ({
  answers: many(answers),
}))

export const answersRelations = relations(answers, ({ one }) => ({
  question: one(questions, {
    fields: [answers.questionId],
    references: [questions.id],
  }),
}))
```

Create the `src/db/index.ts` file and paste in the following code, which is used by the application to establish a connection to the database:

```ts {{ filename: 'src/db/index.ts' }}
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import { questions, answers, questionsRelations, answersRelations } from './schema'

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be a Neon postgres connection string')
}
const sql = neon(process.env.DATABASE_URL!)

export const db = drizzle(sql, {
  schema: { questions, answers, questionsRelations, answersRelations },
})
```

In the root of the project, create the `drizzle.config.ts` used by `drizzle-kit` to manage the database schema:

```ts {{ filename: 'drizzle.config.ts' }}
import { defineConfig } from 'drizzle-kit'
import { loadEnvConfig } from '@next/env'

loadEnvConfig(process.cwd())

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be a Neon postgres connection string')
}

export default defineConfig({
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL,
  },
  schema: './src/db/schema.ts',
})
```

Finally, run the following command from your terminal to push the schema to the Neon database:

```bash
npx drizzle-kit push
```

If you go to the tables section in your Neon dashboard, you should see that two tables named `questions` and `answers` were created.

### Defining database interactions

Now that the database schema is set up, you can start defining database interactions. We're going to start with the database calls used by the main Q\&A section of the app.

Create `src/app/qa/actions.ts` and paste in the following:

```ts {{ filename: 'src/app/qa/actions.ts' }}
'use server'
import { db } from '@/db/index'
import { questions, answers } from '@/db/schema'
import { and, desc, eq } from 'drizzle-orm'
import { currentUser } from '@clerk/nextjs/server'

// Fetches all questions, available to authenticated and anonymous users
export async function getAllQuestions(): Promise<Question[]> {
  const data = await db
    .select()
    .from(questions)
    .where(eq(questions.approved, true))
    .orderBy(desc(questions.timestamp))
  const res: Question[] = data.map((question) => ({
    id: question.id,
    quiz: question.quiz,
    approved: question.approved,
    contributor: question.contributor,
    contributorId: question.contributorId,
    timestamp: question.timestamp?.toISOString(),
  }))
  for (const question of res) {
    const answerData = await db
      .select()
      .from(answers)
      .where(and(eq(answers.questionId, question.id as number), eq(answers.approved, true)))
      .orderBy(desc(answers.timestamp))
    question.answers = answerData.map((answer) => ({
      id: answer.id,
      ans: answer.ans,
      approved: answer.approved,
      contributor: answer.contributor,
      contributorId: answer.contributorId,
      questionId: answer.questionId,
      timestamp: answer.timestamp?.toISOString(),
    }))
  }
  return res
}

// Creates a new question, available only to authenticated users
export const createQuestion = async (quiz: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  await db.insert(questions).values({
    quiz: quiz,
    contributor: user.fullName as string,
    contributorId: user.id,
  })
}

// Creates a new answer, available only to authenticated users
export const createAnswer = async (answer: string, questionId: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  await db.insert(answers).values({
    ans: answer,
    contributor: user.fullName as string,
    contributorId: user.id,
    questionId: questionId,
  })
}

// Deletes a question, available only to the question's contributor
export const deleteQuestion = async (id: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    const result = await db
      .delete(questions)
      .where(and(eq(questions.id, id), eq(questions.contributorId, user.id)))
    return result
  } catch (error) {
    console.error('Error deleting question:', error)
    throw new Error('Failed to delete question')
  }
}

// Deletes an answer, available only to the answer's contributor
export const deleteAnswer = async (id: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db.delete(answers).where(and(eq(answers.id, id), eq(answers.contributorId, user.id)))
  } catch (error) {
    console.error('Error deleting answer:', error)
    throw new Error('Failed to delete answer')
  }
}

// Updates a question, available only to the question's contributor
export const updateQuestion = async (id: number, newText: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db
      .update(questions)
      .set({ quiz: newText })
      .where(and(eq(questions.contributorId, user.id), eq(questions.id, id)))
  } catch (error) {
    console.error('Error updating question:', error)
    throw new Error('Failed to update question')
  }
}

// Updates an answer, available only to the answer's contributor
export const updateAnswer = async (id: number, newText: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db
      .update(answers)
      .set({ ans: newText })
      .where(and(eq(answers.contributorId, user.id), eq(answers.id, id)))
  } catch (error) {
    console.error('Error updating answer:', error)
    throw new Error('Failed to update answer')
  }
}
```

Now we can wire up the placeholder functions in `src/app/qa/actions.ts` with the new database interactions we just created.

Update the `src/app/qa/page.tsx` file like so:

```tsx {{ filename: 'src/app/qa/page.tsx', ins: [7, [25, 58]], del: [[16, 23]] }}
'use client'

import { useState, useEffect } from 'react'
import QuestionForm from '../../components/QuestionForm'
import QuestionItem from '@/components/QuestionItem'
import Header from '../../components/Header'
import * as actions from '../../app/qa/actions'

export default function QAPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const addQuestion = async (question: string) => {}
  const editQuestion = async (id: number, newText: string) => {}
  const deleteQuestion = async (id: number) => {}
  const addAnswer = async (questionId: number, answer: string) => {}
  const editAnswer = async (questionId: number, answerId: number, newText: string) => {}
  const deleteAnswer = async (questionId: number, answerId: number) => {}

  const fetchQuestions = async () => {
    const questions = await actions.getAllQuestions()
    setQuestions(questions)
  }

  const addQuestion = async (quiz: string) => {
    await actions.createQuestion(quiz)
    fetchQuestions()
  }

  const editQuestion = async (id: number, newText: string) => {
    await actions.updateQuestion(id, newText)
    fetchQuestions()
  }

  const deleteQuestion = async (id: number) => {
    await actions.deleteQuestion(id)
    fetchQuestions()
  }

  const addAnswer = async (questionId: number, answer: string) => {
    await actions.createAnswer(answer, questionId)
    fetchQuestions()
  }

  const editAnswer = async (answerId: number, newText: string) => {
    await actions.updateAnswer(answerId, newText)
    fetchQuestions()
  }

  const deleteAnswer = async (answerId: number) => {
    await actions.deleteAnswer(answerId)
    fetchQuestions()
  }

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <QuestionForm onSubmit={addQuestion} />
        {Array.isArray(questions) && (
          <div className="space-y-4">
            {questions.map((question) => (
              <QuestionItem
                key={question.id}
                question={question}
                onEditQuestion={editQuestion}
                onDeleteQuestion={deleteQuestion}
                onAddAnswer={addAnswer}
                onEditAnswer={editAnswer}
                onDeleteAnswer={deleteAnswer}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}
```

Now the server actions will prevent users from editing questions or answers that do not belong to them, but we can create a better user experience by making sure only the person who posted the question or answer can edit or delete it. The `useUser` hook from Clerk can be used to get the current user's information.

Update the `QuestionItem` component like so:

```tsx {{ filename: 'src/components/QuestionItem.tsx', ins: [9, 28, 94, 107], del: [] }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Pencil, Trash2 } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import AnswerItem from './AnswerItem'
import { formatDate } from '@/lib/utils'
import { useUser } from '@clerk/nextjs'

interface Props {
  question: Question
  onEditQuestion: (id: number, newText: string) => void
  onDeleteQuestion: (id: number) => void
  onAddAnswer: (questionId: number, answerText: string) => void
  onEditAnswer: (answerId: number, newText: string) => void
  onDeleteAnswer: (answerId: number) => void
}

export default function QuestionItem({
  question,
  onEditQuestion,
  onDeleteQuestion,
  onAddAnswer,
  onEditAnswer,
  onDeleteAnswer,
}: Props) {
  const { user } = useUser()
  const [answer, setAnswer] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [editedQuestion, setEditedQuestion] = useState(question.quiz)
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (answer.trim()) {
      if (question.id !== null) {
        onAddAnswer(question.id, answer)
        setShowSubmitText(true)
      }
      setAnswer('')
    }
  }

  const handleQuestionEdit = () => {
    if (editedQuestion.trim() && editedQuestion !== question.quiz) {
      if (question.id !== null) {
        onEditQuestion(question.id, editedQuestion)
      }
      setIsEditing(false)
    }
  }

  const handleAnswerEdit = async (answerId: number | null, newText: string) => {
    if (answerId !== null && question.id !== null) {
      await onEditAnswer(answerId, newText)
    }
  }

  const handleAnswerDelete = async (answerId: number | null) => {
    if (answerId !== null && question.id !== null) {
      await onDeleteAnswer(answerId)
    }
  }

  return (
    <Card>
      <CardHeader>
        {isEditing ? (
          <div className="flex gap-2">
            <Input
              value={editedQuestion}
              onChange={(e) => setEditedQuestion(e.target.value)}
              className="flex-grow"
            />
            <Button onClick={handleQuestionEdit}>Save</Button>
            <Button variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <div>
            <div className="mb-2 flex items-center justify-between">
              <CardTitle>{question.quiz}</CardTitle>

              {user?.id === question.contributorId && (
                <div>
                  <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                    <Pencil className="h-4 w-4" />
                  </Button>
                  <Button
                    variant="ghost"
                    size="icon"
                    onClick={() => question.id !== null && onDeleteQuestion(question.id)}
                  >
                    <Trash2 className="h-4 w-4" />
                  </Button>
                </div>
              )}
            </div>
            <div className="text-sm text-gray-500">
              <span>{question.contributor}</span>
              <span> • </span>
              <span>{question.timestamp && formatDate(question.timestamp)}</span>
            </div>
          </div>
        )}
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.filter((a) => a.approved !== false).length > 0 ? (
          <ul className="space-y-4">
            {question.answers
              .filter((a) => a.approved !== false)
              .map((answer, index, filteredAnswers) => (
                <li key={answer.id}>
                  <AnswerItem
                    answer={answer}
                    onEditAnswer={(newText) => handleAnswerEdit(answer.id, newText)}
                    onDeleteAnswer={() => handleAnswerDelete(answer.id)}
                  />
                  {index < filteredAnswers.length - 1 && <Separator className="my-2" />}
                </li>
              ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>

      <CardFooter>
        <form onSubmit={handleSubmit} className="w-full">
          <div className="flex gap-2">
            <div className="flex-grow">
              <Input
                type="text"
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Add an answer..."
              />

              <div className="h-4 text-sm text-green-500 transition-all">
                {showSubmitText ? 'Your answer has been submitted for review.' : ''}
              </div>
            </div>
            <Button type="submit">Answer</Button>
          </div>
        </form>
      </CardFooter>
    </Card>
  )
}
```

And the `AnswerItem` component:

```tsx {{ filename: 'src/components/AnswerItem.tsx', ins: [6, 15, 44, 53], del: [] }}
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Pencil, Trash2 } from 'lucide-react'
import { formatDate } from '@/lib/utils'
import { useUser } from '@clerk/nextjs'

type Props = {
  answer: Answer
  onEditAnswer: (newText: string) => void
  onDeleteAnswer: () => void
}

function AnswerItem({ answer, onEditAnswer, onDeleteAnswer }: Props) {
  const { user } = useUser()
  const [isEditing, setIsEditing] = useState(false)
  const [editedAnswer, setEditedAnswer] = useState(answer.ans)

  const handleEdit = () => {
    if (editedAnswer.trim() && editedAnswer !== answer.ans) {
      onEditAnswer(editedAnswer)
      setIsEditing(false)
    }
  }

  return (
    <div>
      {isEditing ? (
        <div className="flex w-full gap-2">
          <Input
            value={editedAnswer}
            onChange={(e) => setEditedAnswer(e.target.value)}
            className="flex-grow"
          />
          <Button onClick={handleEdit}>Save</Button>
          <Button variant="outline" onClick={() => setIsEditing(false)}>
            Cancel
          </Button>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="flex items-start justify-between">
            <p>{answer.ans}</p>
            {user?.id === answer.contributorId && (
              <div>
                <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                  <Pencil className="h-4 w-4" />
                </Button>
                <Button variant="ghost" size="icon" onClick={onDeleteAnswer}>
                  <Trash2 className="h-4 w-4" />
                </Button>
              </div>
            )}
          </div>
          <div className="text-sm text-gray-500">
            <span>{answer.contributor}</span>
            <span> • </span>
            <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default AnswerItem
```

Next let's create a set of server actions used by the admin area to manage questions and answers. Notice in the following code we dont need to check the user's role, because we are using the Clerk middleware to protect this route.

Create the `src/app/admin/actions.ts` file and add the following content:

```tsx {{ filename: 'src/app/admin/actions.ts' }}
'use server'
import { db } from '@/db/index'
import { questions, answers } from '@/db/schema'
import { eq, desc } from 'drizzle-orm'

export const getAllQuestionsWithAnswers = async () => {
  const questionsData = await db.select().from(questions).orderBy(desc(questions.timestamp))
  const res: Question[] = questionsData.map((question) => ({
    id: question.id,
    quiz: question.quiz,
    approved: question.approved,
    contributor: question.contributor,
    contributorId: question.contributorId,
    timestamp: question.timestamp?.toISOString(),
  }))
  for (const question of res) {
    const answerData = await db
      .select()
      .from(answers)
      .where(eq(answers.questionId, question.id as number))
      .orderBy(desc(answers.timestamp))
    if (!answerData) continue
    question.answers = answerData.map((answer) => ({
      id: answer.id,
      ans: answer.ans,
      approved: answer.approved,
      contributor: answer.contributor,
      contributorId: answer.contributorId,
      questionId: answer.questionId,
      timestamp: answer.timestamp?.toISOString(),
    }))
  }
  return res
}

export const approveQuestion = async (id: number) => {
  try {
    await db.update(questions).set({ approved: true }).where(eq(questions.id, id))
  } catch (error) {
    console.error('Error approving question:', error)
    throw new Error('Failed to approve question')
  }
}

export const disapproveQuestion = async (id: number) => {
  try {
    await db.update(questions).set({ approved: false }).where(eq(questions.id, id))
  } catch (error) {
    console.error('Error disapproving question:', error)
    throw new Error('Failed to disapprove question')
  }
}

export const approveAnswer = async (id: number) => {
  try {
    await db.update(answers).set({ approved: true }).where(eq(answers.id, id))
  } catch (error) {
    console.error('Error approving answer:', error)
    throw new Error('Failed to approve answer')
  }
}

export const disapproveAnswer = async (id: number) => {
  try {
    await db.update(answers).set({ approved: false }).where(eq(answers.id, id))
  } catch (error) {
    console.error('Error disapproving answer:', error)
    throw new Error('Failed to disapprove answer')
  }
}
```

Now let's do the same thing to the `admin` page as the `qa` page. Update the `src/app/admin/page.tsx` file like so:

```tsx {{ filename: 'app/admin/page.tsx', ins: [[8, 14], [30, 53]], del: [[23, 28]] }}
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import Header from '@/components/Header'
import QuestionCard from '@/components/QuestionCard'
import {
  approveQuestion,
  disapproveQuestion,
  getAllQuestionsWithAnswers,
  approveAnswer,
  disapproveAnswer,
} from './actions'

export default function AdminPage() {
  const [questions, setQuestions] = useState<Question[]>([])

  useEffect(() => {
    fetchQuestions()
  }, [])

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const onQuestionApproved = async (id: number) => {}
  const onQuestionDisapproved = async (id: number) => {}
  const onAnswerApproved = async (answerId: number) => {}
  const onAnswerDisapproved = async (answerId: number) => {}

  const fetchQuestions = async () => {
    const questions = await getAllQuestionsWithAnswers()
    setQuestions(questions)
  }

  const onQuestionApproved = async (id: number) => {
    await approveQuestion(id)
    fetchQuestions()
  }

  const onQuestionDisapproved = async (id: number) => {
    await disapproveQuestion(id)
    fetchQuestions()
  }

  const onAnswerApproved = async (answerId: number) => {
    await approveAnswer(answerId)
    fetchQuestions()
  }

  const onAnswerDisapproved = async (answerId: number) => {
    await disapproveAnswer(answerId)
    fetchQuestions()
  }

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <h1 className="mb-6 text-3xl font-bold">Admin Dashboard</h1>
        <div className="mb-4 flex justify-end">
          <Button>
            <Link href="/admin/set-user-roles">Set Roles</Link>
          </Button>
        </div>
        <div className="space-y-4">
          {questions.map((question) => (
            <QuestionCard
              key={question.id}
              question={question}
              onQuestionApproved={onQuestionApproved}
              onQuestionDisapproved={onQuestionDisapproved}
              onAnswerApproved={onAnswerApproved}
              onAnswerDisapproved={onAnswerDisapproved}
            />
          ))}
        </div>
      </main>
    </div>
  )
}
```

## Test it out!

After completing all the steps throughout this guide, you can now start up the application once more to test out all the features!

Here are a couple of things to try:

- Create a new question as a user of each role.
- Try approving and disapproving questions and answers.
- Try editing and deleting questions and answers.
- Explore the data in the Neon database.

## Conclusion

In this tutorial, you have learned how to integrate Clerk for authentication, configure RBAC using metadata, and enforce role-based restrictions to ensure users only access features appropriate to their roles. Also, you learned how to integrate Neon Postgres database with Drizzle ORM for seamless data management and how to conditionally render UI based on user roles.

By following this tutorial, developers can build secure applications by implementing Role-Based Access Control (RBAC) with Clerk.

Here is the [source code](https://github.com/bmorrisondev/qa-app) (remember to give it a star ⭐).

---

# Build a Next.js sign-up form with React Hook Form
URL: https://clerk.com/blog/nextjs-sign-up-form.md
Date: 2025-02-04
Category: Guides
Description: Learn how to capture user credentials and save them securely with Argon2 password hashing.

In this post, you will learn how to build a sign-up form using the Next.js [App Router](/glossary/app-router) and the following technologies:

> This guide shows how to build authentication from scratch. For a production-ready solution, see our [Next.js Authentication](/nextjs-authentication) offering or explore our [comprehensive Next.js authentication guide](/blog/nextjs-authentication).

- [**Argon2**](https://en.wikipedia.org/wiki/Argon2) - Secure password hashing algorithm that provides strong protection against attacks.
- [**Drizzle**](https://orm.drizzle.team/) - ORM (Object-Relational Mapping) tool used to define the database schema and perform database operations, such as inserting or querying users.
- [**Zod**](https://zod.dev/) - TypeScript-first schema declaration and validation library.
- [**shadcn/ui**](https://ui.shadcn.com) - An assortment of beautifully-designed components you can copy into your app.
- [**React Hook Form**](https://react-hook-form.com/) - Library to simplify React form management and validation.

By the end of this guide, you will have a fully functional and secure sign-up form with the following features:

1. **Dynamic form validation** - Users receive feedback on the validity of their input when they type.
2. **Password strength feedback** - Input validation ensures users follow password best practices to create strong passwords.
3. **Secure password storage** - Passwords are hashed using Argon2 before being stored.

We won't be building the sign-up form step-by-step. Instead, you'll find the complete [source code for the post on GitHub](https://github.com/bookercodes/nextjs-sign-up-form-example-code). I will guide you through the key parts of the code, explaining how each section functions and contributes to the final product.

## Database schema

Let's begin with the database schema, as it defines the structure of the sign-up form and serves as its foundation.

```ts {{ filename: '@/db/schema.ts' }}
import { sql } from 'drizzle-orm'
import { AnyPgColumn, integer, pgTable, timestamp, uniqueIndex, varchar } from 'drizzle-orm/pg-core'

export const usersTable = pgTable(
  'users',
  {
    id: integer().primaryKey().generatedAlwaysAsIdentity(),
    createdAt: timestamp('created_at').notNull().defaultNow(),
    email: varchar({ length: 254 }).notNull().unique(),
    passwordHash: varchar('password_hash', { length: 255 }).notNull(),
  },
  (table) => [uniqueIndex('emailUniqueIndex').on(lower(table.email))],
)

export function lower(email: AnyPgColumn) {
  return sql`lower(${email})`
}
```

I'm using Drizzle with Postgres, but one of the advantages of using an ORM like Drizzle is its flexibility - you can adapt it to work with almost any database with minimal adjustments.

The table includes these columns: `id`, `createdAt`, `email`, and `passwordHash`.

An important aspect often overlooked is ensuring emails are stored as unique and case-insensitive. While PostgreSQL offers the `citext` module for this purpose, I've opted for an index using the `lower` function. This approach keeps everything within the application code and avoids the need to run additional PostgreSQL queries.

While basic constraints like length are useful at the database level, validations like email format are best handled in the application layer. In the next section, we'll explore using Zod to define and validate the email and other inputs before storing them in the database.

## Zod validation

```ts {{ filename: '@/definitions/sign-up.ts' }}
import { z } from 'zod'

export const signUpFormSchema = z.object({
  email: z.string().email({ message: 'Please enter a valid email.' }).toLowerCase().trim(),
  password: z
    .string()
    .min(8, { message: 'Be at least 8 characters long' })
    .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
    .regex(/[0-9]/, { message: 'Contain at least one number.' })
    .regex(/[^a-zA-Z0-9]/, {
      message: 'Contain at least one special character.',
    }),
})

export type SignUpFormData = z.infer<typeof signUpFormSchema>
type SignUpFieldErrors = z.inferFlattenedErrors<typeof signUpFormSchema>['fieldErrors']

export type SignUpActionState = {
  formData?: SignUpFormData
  fieldErrors?: SignUpFieldErrors
}
```

It's important to validate user input on both the frontend (in the client component) and the backend (in the server function):

- Client-side validation provides instant feedback to users and improves the user experience by catching errors before submitting the form. However, client-side validation can be bypassed or may fail if JavaScript doesn't load correctly.
- Server-side validation acts as a crucial security layer, ensuring that invalid and potentially malicious data is caught and handled properly before it reaches the database, even if the client-side validation is circumvented.

Instead of duplicating validation code on the server and client, we use Zod to define the "shape" of a valid form in one place. By exporting the schema, it can be referenced on both the server and client and we keep the code nice and [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).

> \[!TIP]
> Zod is more than just a validation library — it can also normalize inputs. In the snippet above, I use `trim()` on `email` to remove whitespace that a user might accidentally include at the end of their email.
>
> When you use Zod's `safeParse` method, it not only validates the input but also returns the formatted value.

## Server action

```ts {{ title: '@/actions/sign-up.ts' }}
'use server'

export async function signUp(
  _initialState: SignUpActionState,
  formData: FormData,
): Promise<SignUpActionState> {
  const form = Object.fromEntries(formData) as SignUpFormData

  const parsedForm = signUpFormSchema.safeParse(form)
  if (!parsedForm.success) {
    // If validation fails, return the form data and field errors
    return {
      formData: form,
      fieldErrors: parsedForm.error.flatten().fieldErrors,
    }
  }

  const [user] = await db
    .select()
    .from(usersTable)
    .where(eq(lower(usersTable.email), parsedForm.data.email))
  if (user) {
    // If the email is already taken, return the form data and an error message
    return {
      formData: form,
      fieldErrors: {
        email: ['The email you entered has already been taken.'],
      },
    }
  }

  const passwordHash = await hash(parsedForm.data.password)
  await db.insert(usersTable).values({
    email: parsedForm.data.email,
    passwordHash,
  })

  // Here is where you would create an active session for the user before redirecting

  redirect('/')
}
```

A [server action](https://react.dev/reference/rsc/server-functions) is a server-side function that can be called directly from client components. This allows you to run backend code, such as database queries and mutations, without needing to create separate API endpoints.

A common security oversight with server functions is assuming client-side validation is sufficient. However, server functions are essentially HTTP endpoints and a malicious actor could send invalid data directly using a tool like [cURL](https://en.wikipedia.org/wiki/CURL). This may lead to inconsistencies in your database and could even pose a security risk. For this reason, we use Zod to validate all incoming data on the server, even though we already have client-side validation in place.

The server function checks for existing users by querying the database with the provided email. If a user is found, it returns a field-level error message stating: `"The email you entered has already been taken"`.

## Password hashing

In the server action above, we first hash the password before storing it in the database:

```tsx {{ filename: '@/actions/sign-up.ts' }}
const passwordHash = await hash(parsedForm.data.password)

await db.insert(usersTable).values({
  email: parsedForm.data.email,
  passwordHash,
})
```

What is hashing and why is it important?

Storing passwords in plain text creates a significant security vulnerability. If an attacker gains access to your database through a data breach, they immediately have access to every user's account. Worse yet, since many people reuse passwords across services, compromised credentials could lead to breaches of users' accounts on other platforms.

This is where [password hashing](/glossary#hash) becomes crucial. A hash function transforms a password into an irreversible string of characters. When a user attempts to sign in, the system hashes their input password and compares it with the stored hash. If they match, you know the credentials are valid. This all happens without ever storing or exposing the actual password.

The code uses [Argon2](https://en.wikipedia.org/wiki/Argon2) for password hashing, which is considered one of the most secure hashing algorithms available today. While older algorithms like MD5 were once common, they've proven vulnerable to reverse-engineering attacks. Other popular options like Bcrypt are still secure, but Argon2 offers additional benefits - it's memory-hard (making it resistant to specialized hardware attacks) and was specifically designed to be future-proof against advances in password cracking technology.

## Creating the form with React Hook Form

```tsx {{ title: '@/components/sign-up-form.tsx', collapsible: true }}
'use client'

import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { SignUpActionState, signUpFormSchema, SignUpFormData } from '@/definitions/sign-up'
import { useActionState, useTransition } from 'react'
import InputError from './ui/input-error'

interface SignUpFormProps {
  action: (initialState: SignUpActionState, formData: FormData) => Promise<SignUpActionState>
}

export default function SignUpForm({ action }: SignUpFormProps) {
  const [actionState, submitAction, isPending] = useActionState(action, {})
  const [, startTransition] = useTransition()

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SignUpFormData>({
    resolver: zodResolver(signUpFormSchema),
    mode: 'onTouched',
    defaultValues: actionState.formData,
  })

  return (
    <Card className="mx-auto w-full max-w-sm">
      <CardHeader>
        <CardTitle>Create your account</CardTitle>
        <CardDescription>Welcome! Please fill in the details to get started.</CardDescription>
      </CardHeader>
      <CardContent>
        <form
          action={submitAction}
          onSubmit={handleSubmit((_, e) => {
            startTransition(() => {
              const formData = new FormData(e?.target)
              submitAction(formData)
            })
          })}
          className="space-y-4"
          noValidate
        >
          <div className="space-y-2">
            <Label htmlFor="email" className={errors.email ? 'text-destructive' : ''}>
              Email
            </Label>
            <Input
              {...register('email')}
              id="email"
              type="email"
              placeholder="Enter your email"
              defaultValue={actionState.formData?.email}
              className={errors.email ? 'border-destructive ring-destructive' : ''}
              aria-invalid={errors.email ? 'true' : 'false'}
            />
            <InputError error={errors.email?.message} />
            <InputError error={actionState.fieldErrors?.email} />
          </div>
          <div className="space-y-2">
            <Label htmlFor="password" className={errors.password ? 'text-destructive' : ''}>
              Password
            </Label>
            <Input
              {...register('password')}
              id="password"
              type="password"
              placeholder="Enter your password"
              defaultValue={actionState.formData?.password}
              className={errors.password ? 'border-destructive ring-destructive' : ''}
              aria-invalid={errors.password ? 'true' : 'false'}
            />
            <InputError error={errors.password?.message} />
            <InputError error={actionState.fieldErrors?.password} />
          </div>
          <Button className="w-full" type="submit" disabled={isPending}>
            Sign Up
          </Button>
        </form>
        <p className="text-muted-foreground mt-4 text-center text-sm">
          By joining, you agree to our{' '}
          <a href="/terms" className="hover:text-primary underline">
            Terms of Service
          </a>{' '}
          and{' '}
          <a href="/privacy" className="hover:text-primary underline">
            Privacy Policy
          </a>
        </p>
      </CardContent>
    </Card>
  )
}
```

While server-side validation is essential for security, relying on it alone creates a suboptimal user experience. Without client-side validation, users would need to submit the form to see if their input was valid - an experience that feels clunky and outdated.

The `SignUpForm` component uses React Hook Form to provide immediate, dynamic feedback as users type.

By passing the same Zod schema we use on the server to React Hook Form's `zodResolver`, we get automatic validation of password strength requirements and email format.

This creates a layered validation approach - immediate client-side feedback for a smooth user experience, backed by robust server-side validation for security.

As an added benefit, if JavaScript is disabled, the form gracefully falls back to server-side validation, displaying errors returned from the server function via [`useActionState`](https://react.dev/reference/react/hooks).

## Advanced sign-up form features

This concludes our guide to building a secure sign-up form with Next.js, React Hook Form, and Argon2. You now have a solid foundation with robust form validation and proper password hashing. Additionally, the form is built using progressive enhancement, meaning it works even without JavaScript. This means you'll never miss a potential sign-up, even if JavaScript fails to load due to network issues, browser settings, or extensions.

While this is a good start, production-ready sign-up forms usually require more sophisticated features. Here are some advanced capabilities to consider for your implementation:

User experience improvements:

- **Social Sign-In Options** - Improve conversion rate by enabling your users to sign up quickly by [authenticating with Google](/blog/nextjs-google-authentication) and other SSO providers.
- **Biometric Authentication with Passkeys** - Enable users to sign-up using fingerprint or facial recognition.
- **Web3 Authentication Options** - Enables users to authenticate using blockchain-based methods.

Security measures:

- **Email Verification** - Ensure user authenticity and prevent spam accounts by confirming the user's email address.
- **Bot Detection** - Utilize CAPTCHA or similar technologies to prevent automated and spam sign-ups.
- **Rate Limiting** - Protect against abuse by limiting the number of sign-up attempts from a single source.
- **Blocklist** - Block specific account identifiers, such as accounts with your competitor's email domain, from signing up.
- **Block Email Subaddresses** - Prevent sign-ups using email addresses with characters like `+`, `=`, or `#`.
- **Block High-Risk Disposable Email Addresses** - Reject sign-ups using email addresses from disposable email domains.

## So why Clerk then?

[Clerk](/) is a user management and authentication platform, so it might surprise you that we're publishing an article that explains how to implement user registration in Next.js yourself.

While implementing the sophisticated features listed above from scratch is possible, it requires significant development effort and security expertise. If these advanced features are important for your application but you don't want to build them yourself, consider using a complete user management and authentication platform like Clerk that provides these capabilities out of the box.

In addition to sign-up, Clerk provides sign-in and manages the entire session, allowing you to authenticate access to pages and access information about the current user wherever you need it.

Learn how to add not only a sign-up form but complete sign-in and session management in minutes:

The best part? [*Clerk uses components as the API*](/blog/a-component-is-worth-a-thousand-apis).

Instead of building your own form component and manually building all the necessary logic, you can just drop a Next.js [`<SignUp />`](/docs/components/authentication/sign-up) component in your page like so:

```tsx {{ title: 'app/sign-up/[[...sign-up]]/page.tsx' }}
import { SignUp } from '@clerk/nextjs'

export default function MySignUpPage() {
  return <SignUp />
}
```

Clerk's component-driven approach makes setup incredibly easy. You can further customise your sign-up process and manage advanced features directly from the Clerk dashboard once you create a free application following the link below.

---

# Build a Next.js login page template
URL: https://clerk.com/blog/building-a-nextjs-login-page-template.md
Date: 2025-01-31
Category: Guides
Description: Learn how to implement session-based authentication into a Next.js application from scratch.

Session-based authentication, introduced in 1960 at MIT, is still one of the most commonly implemented authentication strategies.

With session-based authentication, every user sign-in creates a session on the server that is associated with the user record in the database. These sessions include details such as a creation timestamp, expiration timestamp, and session status. The session ID is set in a cookie and sent back to the client so that the server can determine the user making any future requests from that client.

In this article, you’ll learn how to build session-based authentication into a Next.js application, from implementing the proper database tables to updating the website with authentication forms.

## Implementation overview

There are a set of common requirements when it comes to implementing session-based authentication in any application.

### Database schema

At least two tables are required:

- **users** - When users sign up, the application needs to at least store a user ID, username, and password so they can return in the future.
- **sessions** - The sessions table tracks the information described in the previous section, allowing the application to look up a session by ID and determine the user it’s associated with.

Any tables with records associated with specific users will also need a `userId` or similar column added to make the association.

### Backend changes

Beyond the required functionality to interact with the new database tables, the backend also needs to be able to hash and salt the user passwords so they are not stored in plain text. It’s also best practice to implement sign-up and login [form](/blog/validate-create-style-react-bootstrap-forms) validation server-side so that bad data is not committed to the database.

Protections also need to be added so that the session ID is checked with the database on each request, and that the user's permissions are verified before performing the requested operation or returning data to the client.

### Frontend changes

Since the frontend is what the user interacts with, there are some expected elements required such as sign-in forms, sign-up forms, and a sign-out button. To provide the best user experience, it’s also recommended to implement validation on the forms so that users get immediate feedback if their input is not acceptable before they submit. It also has the added benefit of preventing bad data from being sent to the server.

You should also ensure that unauthenticated users cannot access routes that are reserved for users who are signed in.

### Cookies

Cookies are a way to store small bits of arbitrary data in your browser. While they can be set in the browser, they are more commonly sent to the client from a server for an HTTP request. Cookies set by a server are automatically sent back to that server with every request.

In the context of session-based [authentication](/nextjs-authentication), the server will create a cookie to store the session identifier and send it back to the client upon successful sign-in. When a request is made, the server checks the session ID associated with the request and looks up which user the session belongs to so it can properly identify who is making the request and apply the appropriate authorization rules.

If you want to learn how to implement the same strategy in a standard React application, check out [our blog post covering how to do this with React and Express](/blog/building-a-react-login-page-template).

## Clerk for user management

While you’ll learn how to build a typical authentication system in this article, user management is a much bigger topic than simply allowing users to create accounts and sign in to your application.

Clerk is a [user management platform](/) that's designed to get you up and running with authentication quickly by providing [drop-in UI components](/docs/components/overview). For example, the following snippet demonstrates the code required to build a sign-in page into a Next.js application using Clerk:

```tsx {{ filename: 'src/app/sign-in/[[...sign-in]]/page.tsx' }}
import { SignIn } from '@clerk/nextjs'

export default function Page() {
  return <SignIn />
}
```

When using Clerk, you can easily configure the traditional email & password strategy as well as others like [social sign-in](/docs/authentication/social-connections/oauth) providers, [passkeys](/blog/what-are-passkeys), and even email & [SMS code](/glossary/sms-passcodes) authentication.

You’ll also provide your users an elegant way to manage their own account data, reset passwords, and connect multiple authentication providers, giving them the flexibility to sign-in to your application the way they want.

Add user management to your Next.js application with Clerk in as little as 2 minutes. Check out [our docs](/docs/quickstarts/nextjs) to learn how to get started!

## Introducing the demo project, Quillmate

Quillmate is an AI-powered application for writers. Users can use Quillmate to help them develop ideas, draft pieces, and ask the AI assistant to help with various tasks.

Quillmate is built with the following tech:

- **Next.js** - The entire application is built with Next.js
- **Vercel** - Since it is built with Next.js, it is easily deployable to Vercel.
- **OpenAI** - The AI functionality utilizes OpenAI’s APIs.
- **Neon** - All data is stored in a Postgres database provided by Neon.
- **Prisma** - Prisma is the ORM used to talk to the database.

If you want to follow along, clone the `build-nextjs-login-page-start` branch from the [GitHub repository](https://github.com/bmorrisondev/quillmate/tree/build-nextjs-login-page-start). Follow the instructions provided in the project’s README before proceeding.

## Install new dependencies

There are two new dependencies that need to be installed before modifying the existing codebase:

- `bcryptjs` - bcryptjs is a very popular hashing library that will be used to hash and salt the passwords before saving them to the database.
- `zod` - zod will be used for both client and server-side form validation, ensuring our data is always clean and providing a better user experience.

Install those dependencies with the following command:

```bash
npm install zod bcryptjs
```

## Updating the database schema

Now let’s get the database schema updated. Navigate your code editor to `prisma/schema.prisma` and make the following changes to define the new database tables:

```{{"filename": "prisma/schema.prisma", "ins": [[31, 51]], "del": []}}
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Article {
  id          String        @id @default(uuid())
  title       String
  content     String
  createdAt   DateTime      @default(now())
  updatedAt   DateTime      @default(now()) @updatedAt
  chatMessages ChatMessage[]

  @@map("articles")
}

model ChatMessage {
  id        String   @id @default(uuid())
  articleId String?
  role      String
  content   String
  createdAt DateTime @default(now())
  article   Article? @relation(fields: [articleId], references: [id], onDelete: Cascade)

  @@map("chat_messages")
}

model User {
  id            String        @id @default(uuid())
  email         String       @unique
  passwordHash  String
  createdAt     DateTime     @default(now())
  updatedAt     DateTime     @default(now()) @updatedAt
  sessions      Session[]

  @@map("users")
}

model Session {
  id        String   @id @default(uuid())
  userId    String
  expiresAt DateTime
  createdAt DateTime @default(now())
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@map("sessions")
}
```

Run the following command in the terminal to update the Prisma client and push the changes to the Neon database:

```bash
npx prisma generate
npx prisma db push
```

## Create the middleware

As mentioned earlier in this guide, you’ll need to separate your public routes from those that require the user to be signed in. In an application with dedicated backends and frontends, you’d typically separate the views in the frontend to prevent unauthorized users from accessing those views, and protect the backend API routes so that tech savvy users can’t bypass protections in the frontend.

Since Next.js is a full-stack framework, you can actually do both using middleware, which provides a way for you to intercept requests and apply your own logic to the request before the user reaches their destination.

Create `src/middleware.ts` and paste in the following code:

```tsx {{ filename: 'src/middleware.ts' }}
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

// Add paths that don't require authentication
const publicPaths = ['/signin', '/signup', '/']

export function middleware(request: NextRequest) {
  // Get the session ID from the cookies
  const sessionId = request.cookies.get('sessionId')
  const { pathname } = request.nextUrl

  // Allow access to public paths
  if (publicPaths.includes(pathname)) {
    // Redirect to articles if already authenticated
    if (sessionId) {
      return NextResponse.redirect(new URL('/articles', request.url))
    }
    return NextResponse.next()
  }

  // Require authentication for all other paths
  if (!sessionId) {
    const signInUrl = new URL('/signin', request.url)
    signInUrl.searchParams.set('from', pathname)
    return NextResponse.redirect(signInUrl)
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
```

## Creating the sign-up and login routes

With the database changes and middleware in place, you can now add the necessary forms and logic to allow users to create an account and sign into the application. Each of these routes contain three files as follows:

- `page.tsx` - The client-side sign-up or login page the user will interact with.
- `actions.ts` - Server actions that are used to interact with the database to create new users and create sessions.
- `validation.ts` - Validation models that are shared between the sign-up or login page and server action, enabling validation on both ends of the application.

There are also a few functions that will be shared between both the sign-up and sign-in routes, so let’s get that set up before building them.

Create a the `src/app/auth/actions.ts` file and populate it with the following:

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

import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { db } from '@/db'

// Gets the current user info, redirecting to /signin if there is none
export async function requireAuth() {
  const user = await getCurrentUser()
  if (!user) {
    redirect('/signin')
  }
  return user
}

// Removes the session from the database and removes the cookie
export async function signOut() {
  const c = await cookies()
  const sessionId = c.get('sessionId')?.value

  if (sessionId) {
    await db.session.delete({
      where: { id: sessionId },
    })
  }

  c.delete('sessionId')
  redirect('/signin')
}

// Gets the current user info based on the sessionId cookie
export async function getCurrentUser() {
  const c = await cookies()
  const sessionId = c.get('sessionId')?.value
  if (!sessionId) return null

  const session = await db.session.findUnique({
    where: { id: sessionId },
    include: { user: true },
  })

  if (!session || session.expiresAt < new Date()) {
    c.delete('sessionId')
    return null
  }

  return session.user
}

// Create a session and set the sessionId cookie
export async function createSessionAndCookie(userId: string) {
  const SESSION_DURATION_DAYS = 7
  const expiresAt = new Date()
  expiresAt.setDate(expiresAt.getDate() + SESSION_DURATION_DAYS)

  const session = await db.session.create({
    data: {
      userId,
      expiresAt,
    },
  })

  const c = await cookies()

  // Set session cookie
  c.set('sessionId', session.id, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    expires: new Date(session.expiresAt),
  })
}
```

### Handling sign-up

Since `validation.ts` is the simplest of the three files, start by creating `src/app/signup/validation.ts` and paste in the following:

```tsx {{ filename: 'src/app/signup/validation.ts' }}
import { z } from 'zod'

// Validation schemas
export const signUpSchema = z
  .object({
    email: z.string().toLowerCase().email('Invalid email address'),
    password: z
      .string()
      .min(8, 'Password must be at least 8 characters')
      .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
      .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
      .regex(/[0-9]/, 'Password must contain at least one number'),
    confirmPassword: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Passwords don't match",
    path: ['confirmPassword'],
  })
```

Next, create the server actions file at `src/app/signup/actions.ts` and paste in the following:

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

import { redirect } from 'next/navigation'
import bcrypt from 'bcryptjs'
import { db } from '@/db'
import { signUpSchema } from './validation'
import { createSessionAndCookie } from '../auth/actions'

export async function signUp(formData: FormData) {
  // Perform server-side validation
  const validatedFields = signUpSchema.safeParse({
    email: formData.get('email'),
    password: formData.get('password'),
    confirmPassword: formData.get('confirmPassword'),
  })

  // Return errors if there are any
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  const { email, password } = validatedFields.data

  // Check if user already exists
  const existingUser = await db.user.findUnique({
    where: { email },
  })

  if (existingUser) {
    return {
      errors: {
        email: ['User with this email already exists'],
      },
    }
  }

  // Hash password and create user
  const passwordHash = await bcrypt.hash(password, 10)
  const user = await db.user.create({
    data: {
      email,
      passwordHash,
    },
  })

  // Create session, allowing the user to be immediately signed in
  await createSessionAndCookie(user.id)

  // Redirect the user to the protected route
  redirect('/articles')
}
```

Finally, create `src/app/signup/page.tsx` and paste in the following:

```tsx {{ filename: 'src/app/signup/page.tsx' }}
'use client'

import { useState } from 'react'
import Link from 'next/link'
import { signUp } from './actions'
import { signUpSchema } from './validation'

export default function SignUp() {
  const [errors, setErrors] = useState<{ [key: string]: string[] }>({})
  const [clientErrors, setClientErrors] = useState<{ [key: string]: string[] }>({})

  async function handleSubmit(formData: FormData) {
    // Reset errors
    setClientErrors({})

    // Validate form data
    const result = signUpSchema.safeParse({
      email: formData.get('email'),
      password: formData.get('password'),
      confirmPassword: formData.get('confirmPassword'),
    })

    if (!result.success) {
      const formattedErrors: { [key: string]: string[] } = {}
      result.error.errors.forEach((error) => {
        const path = error.path[0].toString()
        if (!formattedErrors[path]) {
          formattedErrors[path] = []
        }
        formattedErrors[path].push(error.message)
      })
      setClientErrors(formattedErrors)
      return
    }

    const serverResult = await signUp(formData)
    if (serverResult?.errors) {
      setErrors(serverResult.errors)
    }
  }

  function handleInputChange(field: string, value: string, formElement: HTMLFormElement) {
    const formData = new FormData(formElement)
    formData.set(field, value)

    const result = signUpSchema.safeParse({
      email: formData.get('email'),
      password: formData.get('password'),
      confirmPassword: formData.get('confirmPassword'),
    })

    if (!result.success) {
      const fieldErrors = result.error.errors
        .filter((error) => error.path[0] === field)
        .map((error) => error.message)

      if (fieldErrors.length > 0) {
        setClientErrors((prev) => ({
          ...prev,
          [field]: fieldErrors,
        }))
      } else {
        setClientErrors((prev) => ({
          ...prev,
          [field]: [],
        }))
      }
    } else {
      setClientErrors((prev) => ({
        ...prev,
        [field]: [],
      }))
    }
  }

  return (
    <div className="flex min-h-screen flex-col justify-center bg-gray-50 py-12 sm:px-6 lg:px-8">
      <div className="sm:mx-auto sm:w-full sm:max-w-md">
        <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
          Create a new account
        </h2>
        <p className="mt-2 text-center text-sm text-gray-600">
          Or{' '}
          <Link href="/signin" className="font-medium text-blue-600 hover:text-blue-500">
            sign in to your account
          </Link>
        </p>
      </div>

      <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
        <div className="bg-white px-4 py-8 shadow sm:rounded-lg sm:px-10">
          <form
            onSubmit={(e) => {
              e.preventDefault()
              const formData = new FormData(e.target as HTMLFormElement)
              handleSubmit(formData)
            }}
            className="space-y-6"
          >
            <div>
              <label htmlFor="email" className="block text-sm font-medium text-gray-700">
                Email address
              </label>
              <div className="mt-1">
                <input
                  id="email"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  className="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none sm:text-sm"
                  onChange={(e) => handleInputChange('email', e.target.value, e.target.form!)}
                />
              </div>
              {(clientErrors.email || errors.email)?.map((error) => (
                <p key={error} className="mt-1 text-sm text-red-600">
                  {error}
                </p>
              ))}
            </div>

            <div>
              <label htmlFor="password" className="block text-sm font-medium text-gray-700">
                Password
              </label>
              <div className="mt-1">
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="new-password"
                  required
                  className="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none sm:text-sm"
                  onChange={(e) => handleInputChange('password', e.target.value, e.target.form!)}
                />
              </div>
              {(clientErrors.password || errors.password)?.map((error) => (
                <p key={error} className="mt-1 text-sm text-red-600">
                  {error}
                </p>
              ))}
            </div>

            <div>
              <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">
                Confirm password
              </label>
              <div className="mt-1">
                <input
                  id="confirmPassword"
                  name="confirmPassword"
                  type="password"
                  autoComplete="new-password"
                  required
                  className="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none sm:text-sm"
                  onChange={(e) =>
                    handleInputChange('confirmPassword', e.target.value, e.target.form!)
                  }
                />
              </div>
              {(clientErrors.confirmPassword || errors.confirmPassword)?.map((error) => (
                <p key={error} className="mt-1 text-sm text-red-600">
                  {error}
                </p>
              ))}
            </div>

            <div>
              <button
                type="submit"
                className="flex w-full justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none"
              >
                Sign up
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  )
}
```

### Handling sign-in

Now let’s create the sign-in logic and views starting with the validation file as we did in the previous section. Create `src/app/signin/validation.ts` and paste in the following code:

```tsx {{ filename: 'src/app/signin/validation.ts' }}
import { z } from 'zod'

export const signInSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(1, 'Password is required'),
})
```

Next, create the server actions at `src/app/signin/actions.ts` and populate the file with the following:

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

import { redirect } from 'next/navigation'
import bcrypt from 'bcryptjs'
import { db } from '@/db'
import { signInSchema } from './validation'
import { createSessionAndCookie } from '../auth/actions'

export async function signIn(formData: FormData) {
  const validatedFields = signInSchema.safeParse({
    email: formData.get('email'),
    password: formData.get('password'),
  })

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  const { email, password } = validatedFields.data

  const user = await db.user.findUnique({
    where: { email },
  })

  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return {
      errors: {
        email: ['Invalid email or password'],
      },
    }
  }

  // Create session
  await createSessionAndCookie(user.id)

  redirect('/articles')
}
```

Then create the login page at `src/app/signin/page.tsx` and paste in the following:

```tsx {{ filename: 'src/app/signin/page.tsx' }}
'use client'

import { useState } from 'react'
import Link from 'next/link'
import { signIn } from './actions'
import { signInSchema } from './validation'

export default function SignIn() {
  const [errors, setErrors] = useState<{ [key: string]: string[] }>({})
  const [clientErrors, setClientErrors] = useState<{ [key: string]: string[] }>({})

  async function handleSubmit(formData: FormData) {
    // Reset errors
    setClientErrors({})

    // Validate form data
    const result = signInSchema.safeParse({
      email: formData.get('email'),
      password: formData.get('password'),
    })

    if (!result.success) {
      const formattedErrors: { [key: string]: string[] } = {}
      result.error.errors.forEach((error) => {
        const path = error.path[0].toString()
        if (!formattedErrors[path]) {
          formattedErrors[path] = []
        }
        formattedErrors[path].push(error.message)
      })
      setClientErrors(formattedErrors)
      return
    }

    const serverResult = await signIn(formData)
    if (serverResult?.errors) {
      setErrors(serverResult.errors)
    }
  }

  // Validate the fields as the user types
  function handleInputChange(field: string, value: string) {
    const result = signInSchema.safeParse({
      email: field === 'email' ? value : '',
      password: field === 'password' ? value : '',
    })

    if (!result.success) {
      const fieldError = result.error.errors.find((error) => error.path[0] === field)
      if (fieldError) {
        setClientErrors((prev) => ({
          ...prev,
          [field]: [fieldError.message],
        }))
      }
    } else {
      setClientErrors((prev) => ({
        ...prev,
        [field]: [],
      }))
    }
  }

  return (
    <div className="flex min-h-screen flex-col justify-center bg-gray-50 py-12 sm:px-6 lg:px-8">
      <div className="sm:mx-auto sm:w-full sm:max-w-md">
        <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
          Sign in to your account
        </h2>
        <p className="mt-2 text-center text-sm text-gray-600">
          Or{' '}
          <Link href="/signup" className="font-medium text-blue-600 hover:text-blue-500">
            create a new account
          </Link>
        </p>
      </div>

      <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
        <div className="bg-white px-4 py-8 shadow sm:rounded-lg sm:px-10">
          <form
            onSubmit={(e) => {
              e.preventDefault()
              const formData = new FormData(e.target as HTMLFormElement)
              handleSubmit(formData)
            }}
            className="space-y-6"
          >
            <div>
              <label htmlFor="email" className="block text-sm font-medium text-gray-700">
                Email address
              </label>
              <div className="mt-1">
                <input
                  id="email"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  className="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none sm:text-sm"
                  onChange={(e) => handleInputChange('email', e.target.value)}
                />
              </div>
              {(clientErrors.email || errors.email)?.map((error) => (
                <p key={error} className="mt-1 text-sm text-red-600">
                  {error}
                </p>
              ))}
            </div>

            <div>
              <label htmlFor="password" className="block text-sm font-medium text-gray-700">
                Password
              </label>
              <div className="mt-1">
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="current-password"
                  required
                  className="block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none sm:text-sm"
                  onChange={(e) => handleInputChange('password', e.target.value)}
                />
              </div>
              {(clientErrors.password || errors.password)?.map((error) => (
                <p key={error} className="mt-1 text-sm text-red-600">
                  {error}
                </p>
              ))}
            </div>

            <div>
              <button
                type="submit"
                className="flex w-full justify-center rounded-md border border-transparent bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none"
              >
                Sign in
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  )
}
```

## Associating articles with users

At this point, the user can now create an account and sign in as needed, but the `/articles` route which contains the protected pages still needs to have several pieces updated to ensure users can only work with articles associated with their account and not ALL articles.

Update `prisma/schema.prisma` one more time to update the `Article` model so those records are associated with a user by creating the `userId` column and Prisma relation:

```{{"filename": "prisma/schema.prisma", "ins": [12, 17, 40], "del": []}}
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Article {
  id          String        @id @default(uuid())
  userId      String
  title       String
  content     String
  createdAt   DateTime      @default(now())
  updatedAt   DateTime      @default(now()) @updatedAt
  user        User          @relation(fields: [userId], references: [id], onDelete: Cascade)
  chatMessages ChatMessage[]

  @@map("articles")
}

model ChatMessage {
  id        String   @id @default(uuid())
  articleId String?
  role      String
  content   String
  createdAt DateTime @default(now())
  article   Article? @relation(fields: [articleId], references: [id], onDelete: Cascade)

  @@map("chat_messages")
}

model User {
  id            String        @id @default(uuid())
  email         String       @unique
  passwordHash  String
  createdAt     DateTime     @default(now())
  updatedAt     DateTime     @default(now()) @updatedAt
  articles      Article[]
  sessions      Session[]

  @@map("users")
}

model Session {
  id        String   @id @default(uuid())
  userId    String
  expiresAt DateTime
  createdAt DateTime @default(now())
  user      User     @reauthaulation(fields: [userId], references: [id], onDelete: Cascade)

  @@map("sessions")
}
```

Apply your changes with the same terminal commands as before:

```bash
npx prisma generate
npx prisma db push
```

Next, you’ll update the server actions used for the `/articles` route to store the user’s ID whenever an article record is created, and filter returned articles when the user requests them, ensuring that users can only access the articles they are supposed to.

Update `src/app/articles/actions.ts` as follows:

```tsx {{ filename: 'src/app/articles/actions.ts', ins: [7, [10, 12], 20, 26, 32, 37], del: [] }}
'use server'

import { db } from '@/db'
import { requireAuth } from '@/app/auth/actions'

export async function fetchArticles() {
  const user = await requireAuth()

  return await db.article.findMany({
    where: {
      userId: user.id,
    },
    orderBy: {
      updatedAt: 'desc',
    },
  })
}

export async function createNewArticle() {
  const user = await requireAuth()

  return await db.article.create({
    data: {
      title: 'New Article',
      content: '# New Article\n\nStart writing your content here...',
      userId: user.id,
    },
  })
}

export async function saveArticle(id: string, title: string, content: string) {
  const user = await requireAuth()

  return await db.article.update({
    where: {
      id,
      userId: user.id,
    },
    data: {
      title,
      content,
      updatedAt: new Date(),
    },
  })
}

export async function getChatMessages(userId: string, articleId: string, since?: Date) {
  return await db.chatMessage.findMany({
    where: {
      articleId,
      ...(since && {
        createdAt: {
          gte: since,
        },
      }),
    },
    orderBy: {
      createdAt: 'asc',
    },
  })
}

export async function createChatMessage(
  userId: string,
  articleId: string,
  role: 'user' | 'assistant',
  content: string,
) {
  return await db.chatMessage.create({
    data: {
      articleId,
      role,
      content,
    },
  })
}
```

Finally, you’ll update `src/app/articles/page.tsx` to add a sign-out button that leverages the `signOut` function in our shared [authentication](/nextjs-authentication) utility file:

```tsx {{ filename: 'src/app/articles/page.tsx', ins: [8, [147, 156]], del: [] }}
'use client'

import { useState, useEffect, useCallback, useRef } from 'react'
import { ChatSidebar } from './components/ChatSidebar'
import { createNewArticle, fetchArticles, saveArticle } from './actions'
import { useDebounce } from '@/hooks/useDebounce'
import { MarkdownEditor } from './components/MarkdownEditor'
import { signOut } from '@/app/auth/actions'

interface Article {
  id: string
  title: string
  content: string
}

export default function ArticlesPage() {
  const [articles, setArticles] = useState<Article[]>([])
  const [selectedArticle, setSelectedArticle] = useState<Article | null>(null)
  const [content, setContent] = useState<string>('')
  const [isLoading, setIsLoading] = useState(true)
  const [isSaving, setIsSaving] = useState(false)
  const [context, setContext] = useState<string>()

  useEffect(() => {
    loadArticles()
  }, [])

  async function loadArticles() {
    try {
      const fetchedArticles = await fetchArticles()
      setArticles(fetchedArticles)
      if (fetchedArticles.length > 0 && !selectedArticle) {
        setSelectedArticle(fetchedArticles[0])
        setContent(fetchedArticles[0].content)
      }
      setIsLoading(false)
    } catch (error) {
      console.error('Failed to load articles:', error)
      setIsLoading(false)
    }
  }

  const handleArticleSelect = (article: Article) => {
    setSelectedArticle(article)
    setContent(article.content)
  }

  const handleNewArticle = async () => {
    try {
      const newArticle = await createNewArticle()
      if (newArticle) {
        setArticles((prev) => [...prev, newArticle])
        handleArticleSelect(newArticle)
      }
    } catch (error) {
      console.error('Failed to create article:', error)
    }
  }

  const extractTitleFromContent = (content: string): string | null => {
    const h1Match = content.match(/^#\s+(.+)$/m)
    return h1Match ? h1Match[1].trim() : null
  }

  const saveContent = useCallback(
    async (articleId: string, currentTitle: string, newContent: string) => {
      setIsSaving(true)
      try {
        const newTitle = extractTitleFromContent(newContent) || currentTitle
        await saveArticle(articleId, newTitle, newContent)

        // Update the articles list with the new title if it changed
        if (newTitle !== currentTitle) {
          setArticles((prev) =>
            prev.map((article) =>
              article.id === articleId ? { ...article, title: newTitle } : article,
            ),
          )
          if (selectedArticle?.id === articleId) {
            setSelectedArticle((prev) => (prev ? { ...prev, title: newTitle } : prev))
          }
        }
      } catch (error) {
        console.error('Failed to save article:', error)
      } finally {
        setIsSaving(false)
      }
    },
    [selectedArticle?.id],
  )

  const debouncedSave = useDebounce(saveContent, 1000)

  const handleContentChange = (newContent: string | undefined) => {
    if (!selectedArticle || !newContent) return
    setContent(newContent)
    debouncedSave(selectedArticle.id, selectedArticle.title, newContent)
  }

  const handleAskAssistant = useCallback((selectedText: string) => {
    setContext(selectedText)
  }, [])

  const handleAppendToArticle = useCallback(
    (text: string) => {
      if (!selectedArticle) return
      const newContent = content + text
      setContent(newContent)
      debouncedSave(selectedArticle.id, selectedArticle.title, newContent)
    },
    [content, selectedArticle, debouncedSave],
  )

  if (isLoading) {
    return <div className="flex flex-1 items-center justify-center">Loading...</div>
  }

  return (
    <div className="flex flex-1 overflow-hidden">
      {/* Article List Sidebar */}
      <div className="w-64 overflow-y-auto border-r border-gray-200 bg-white">
        <div className="flex h-full flex-col p-4">
          <div className="mb-4 flex items-center justify-between">
            <h2 className="text-lg font-semibold text-gray-900">Articles</h2>
            <button
              onClick={handleNewArticle}
              className="rounded-lg bg-blue-500 px-2 py-1 text-sm text-white hover:bg-blue-600"
            >
              New
            </button>
          </div>
          <div className="flex-1 space-y-1">
            {articles.map((article) => (
              <button
                key={article.id}
                onClick={() => handleArticleSelect(article)}
                className={`w-full rounded-lg px-3 py-2 text-left text-sm ${
                  selectedArticle?.id === article.id
                    ? 'bg-blue-100 text-blue-700'
                    : 'text-gray-700 hover:bg-gray-100'
                }`}
              >
                {article.title}
              </button>
            ))}
          </div>
          <div className="mt-4">
            <form action={signOut}>
              <button
                type="submit"
                className="w-full rounded-lg bg-gray-50 px-3 py-2 text-left text-sm text-gray-900 transition-all hover:bg-red-600 hover:text-white"
              >
                Sign out
              </button>
            </form>
          </div>
        </div>
      </div>

      {/* Markdown Editor */}
      <div className="relative h-full flex-1">
        {selectedArticle ? (
          <>
            <MarkdownEditor
              value={content}
              onChange={handleContentChange}
              height="100%"
              onAskAssistant={handleAskAssistant}
            />
            {isSaving && (
              <div className="absolute top-2 right-2 rounded-md bg-gray-800 px-2 py-1 text-xs text-white opacity-75">
                Saving...
              </div>
            )}
          </>
        ) : (
          <div className="flex h-full items-center justify-center p-8 text-gray-500">
            Select an article or create a new one
          </div>
        )}
      </div>

      {/* Chat Sidebar */}
      {selectedArticle ? (
        <ChatSidebar
          content={selectedArticle.content}
          articleId={selectedArticle.id}
          context={context}
          onClearContext={() => setContext(undefined)}
          onAppendToArticle={handleAppendToArticle}
        />
      ) : null}
    </div>
  )
}
```

## Update the homepage link

The last thing to do is update the “Get started” button on the home page to go to `/signin` instead of `/articles`, which will let users sign into the application if they are not already:

```tsx {{ filename: 'src/app/page.tsx', ins: [23], del: [22], prettier: false }}
import Link from 'next/link'
import { Button } from "@/components/ui/button"
import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { ArrowRight, Sparkles, Zap, RefreshCw } from 'lucide-react'

export default function LandingPage() {

  return (
    <div className="min-h-screen bg-gradient-to-b from-purple-100 to-white">
      <main className="container mx-auto px-4 py-16 space-y-24">
        {/* Hero Section */}
        <section className="text-center space-y-6">
          <h1 className="text-5xl font-extrabold tracking-tight text-gray-900 sm:text-6xl">
            Elevate Your Writing with{' '}
            <span className="inline-block text-transparent bg-clip-text bg-gradient-to-r from-purple-600 to-pink-500">
              QuillMate
            </span>
          </h1>
          <p className="text-xl text-gray-700 max-w-2xl mx-auto">
            Unlock your creativity and boost your productivity with our AI-powered writing assistant.
          </p>
          <Link href="/articles">
          <Link href="/signin">
            <Button size="lg" className="mt-8 bg-purple-600 hover:bg-purple-700 text-white">
              Get Started with QuillMate <ArrowRight className="ml-2 h-4 w-4" />
            </Button>
          </Link>
        </section>

        {/* Feature Cards */}
        <section className="space-y-8 max-w-2xl mx-auto">
          <Card className="border-purple-200 bg-white/80 backdrop-blur-sm shadow-md hover:shadow-lg transition-all duration-300">
            <CardHeader>
              <CardTitle className="flex items-center text-gray-900">
                <Sparkles className="mr-2 h-5 w-5 text-purple-500" />
                AI-Powered Suggestions
              </CardTitle>
              <CardDescription className="text-gray-600">
                Get intelligent writing suggestions and improvements in real-time as you type with QuillMate.
              </CardDescription>
            </CardHeader>
          </Card>

          <Card className="border-purple-200 bg-white/80 backdrop-blur-sm shadow-md hover:shadow-lg transition-all duration-300">
            <CardHeader>
              <CardTitle className="flex items-center text-gray-900">
                <Zap className="mr-2 h-5 w-5 text-purple-500" />
                Instant Content Generation
              </CardTitle>
              <CardDescription className="text-gray-600">
                Generate high-quality content for various purposes with just a few clicks using QuillMate's AI.
              </CardDescription>
            </CardHeader>
          </Card>

          <Card className="border-purple-200 bg-white/80 backdrop-blur-sm shadow-md hover:shadow-lg transition-all duration-300">
            <CardHeader>
              <CardTitle className="flex items-center text-gray-900">
                <RefreshCw className="mr-2 h-5 w-5 text-purple-500" />
                Style Adaptation
              </CardTitle>
              <CardDescription className="text-gray-600">
                Easily adapt your writing style for different audiences and purposes with QuillMate's intelligent assistance.
              </CardDescription>
            </CardHeader>
          </Card>
        </section>
      </main>
    </div>
  )
}
```

## Test it out!

Now that all the changes are implemented, execute the following command in your terminal to start up the dev server:

```bash
npm run dev
```

Open your browser and navigate to the URL displayed in the terminal and you’ll be able to create a user account, sign into the application, and start creating articles!

![QuillMate Demo](./quillmate-create-article.png)

After creating an article and chatting with the AI assistant, you can head to the Neon console to explore how the data is structured in the database.

![Neon Console](./neon-tables.png)

## Conclusion

You are now well-equipped to implement session-based authentication within your own application. By following the general guidance introduced at the beginning of this post, you can provide your users the ability to create accounts and sign in to your Next.js site.

While this guide outlines the steps required to implement authentication, adding sign up and sign in to a web application is only one aspect of user management. Consider giving Clerk a try for a complete user management platform that can be configured in minutes, saving you and your team hours of development, testing, and debugging effort.

Feel free to use the provided repository as a resource when building Next.js web applications going forward!

---

# How to implement Google authentication in Next.js 15
URL: https://clerk.com/blog/nextjs-google-authentication.md
Date: 2025-01-24
Category: Guides
Description: Learn how to add Google authentication to your Next.js app, implement a user button for profile management, and enable Google One Tap using Clerk.

This guide walks you through adding Google authentication to your Next.js 15 application in record time.

> For a comprehensive overview of all authentication methods in Next.js, see our [Ultimate Guide to Next.js Authentication](/blog/nextjs-authentication).

By the end, you'll implement essential authentication features including:

- Google authentication for sign-up and sign-in
- Route protection from unauthenticated access
- Google One Tap integration (optional)
- Access to Google services like Calendar on behalf of the authenticated user using [OAuth](/glossary#oauth) (optional)

You'll also learn how to add a polished user button dropdown that gives your users control over their [session](/glossary#session) and profile.

![Google sign-in, user button dropdown, and One Tap UI](./1.png)

To implement Google authentication, you can choose between building it yourself with an open-source library - giving you complete control over the implementation - or using Clerk, which offers the quickest path to integration in Next.js. Both approaches have their place, but in this guide, we'll use Clerk.

## An introduction to Clerk

![Clerk homepage showing user management platform and components](./2.png)

[Clerk](/nextjs-authentication) is a user management and authentication platform that makes it quick to add secure authentication to your Next.js application. We provide pre-built components like ⁠`<SignIn/>` and `<SignUp />` that you can configure to support various authentication methods, including Google.

Using familiar Next.js patterns like components and [middleware](/blog/what-is-middleware-in-nextjs), Clerk handles all the complex backend logic — from session management to route protection.

## How to implement Google authentication using Clerk

Before you implement Google authentication, you'll need to [**create a Clerk account**](/sign-up) to manage your users and authentication settings. Creating an account is free for your first 10,000 monthly users, and no credit card is required.

Sign in to the Clerk dashboard and create your first application. Give it a name and enable Google authentication (you can enable additional authentication methods at any time), then click "Create application".

To proceed with this guide, follow the quickstart steps in the Clerk dashboard. Once you've completed the setup, return here to continue with the next steps.

---

Welcome back!

Start your development server and visit [http://localhost:3000](http://localhost:3000) to sign up and create your first Clerk user.

The quickstart code demonstrates a basic Clerk implementation in your root layout. It wraps your application in ⁠`<ClerkProvider />` and adds authentication UI components — showing a `<SignInButton />` button for unauthenticated users and a `<UserButton />` for those signed in.

The authentication components work as expected, but positioning them in the main navigation bar would create a more predictable user experience. Let's quickly explore how to do that next before diving into route protection.

> \[!NOTE]
> If you've set up [Single Sign-On (SSO)](/glossary/single-sign-on-sso) with Google before, you know it usually starts with configuring Google Cloud credentials. With Clerk in development mode, you can skip this setup and start building immediately using our shared development keys. For production, you'll still need [custom Google credentials](/docs/authentication/social-connections/google#configure-for-your-production-instance).

## Adding authentication to your navigation bar

While some authentication solutions focus solely on authentication, Clerk also handles user management. This means you get access to components like `⁠<UserButton />` - a dropdown that shows which account is signed in and lets users manage their session and profile data.

Since `⁠<UserButton />` and ⁠`<SignInButton />` are standard React components, you can style and position them anywhere in your application.

Here's a quick example using [Tailwind CSS](/glossary/tailwind-css) to illustrate how it’s done:

```tsx {{ filename: 'src/app/layout.tsx', ins: [19, 20, 21, 22, 30, 31, 32, 28], del: [27], prettier: false }}
import { 
  ClerkProvider,
  SignInButton,
  SignedIn,
  SignedOut,
  UserButton 
} from '@clerk/nextjs'
import './globals.css'

export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>
          <nav className="bg-white p-4 shadow-md">
            <div className="container mx-auto flex items-center justify-between">
              <h1 className="text-xl">My App</h1>
              <div className="flex items-center">
                <SignedOut>
                  <SignInButton />
                </SignedOut>
                <SignedIn>
                  <UserButton />
                  <UserButton showName />
                </SignedIn>
              </div>
            </div>
          </nav>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

## Protecting routes from unauthenticated access

Now that users can sign in, let's explore how to restrict page access to authenticated users only. While there are several approaches to [route protection in Next.js](/docs/reference/nextjs/app-router/route-handlers), we will focus on using middleware here.

During the quickstart, you added Clerk middleware to your application. By default, all routes are public. Let's update the middleware to protect specific routes or patterns of routes from unauthenticated access:

```tsx {{ filename: 'src/middlware.ts', ins: [2, 3, 4, 7, 8, 9], del: [1, 6] }}
import { clerkMiddleware } from '@clerk/nextjs/server'
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/forum(.*)'])

export default clerkMiddleware()
export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) await 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)(.*)',
  ],
}
```

Above, we create a route matcher for paths starting with "⁠/dashboard" or ⁠"/forum" and then check each request against these patterns. If the route matches, use `⁠auth.protect()` to automatically redirect users to sign in. In effect, only authenticated users can access these pages.

> \[!NOTE]
> **Authentication vs authorization**
>
> [Authentication](/glossary#authentication) only verifies that a user is signed in. While this guide shows you how to protect routes from unauthenticated access, you might also need [authorization](/glossary#authorization) — checking if an authenticated user has *permission* to access specific resources based on ownership or roles.

## Adding Google One Tap support

[Google One Tap](https://developers.google.com/identity/gsi/web/guides/features) proactively prompts users to sign in with their Google account in a single click when they visit your site. This convenient approach can help increase your sign-in conversion rate compared to traditional authentication flows.

To implement Google One Tap, first, [update your Clerk application to use custom Google credentials](/docs/authentication/social-connections/google#configure-for-your-production-instance) instead of shared credentials.

Then, add ⁠`<GoogleOneTap />` to your layout:

```tsx {{ filename: 'src/app/layout.tsx', ins: [7, 25], prettier: false }}
import {
  ClerkProvider,
  SignInButton,
  SignedIn,
  SignedOut,
  UserButton,
  GoogleOneTap,
} from '@clerk/nextjs'
import './globals.css'

export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>
          <nav className="bg-white p-4 shadow-md">
            <div className="container mx-auto flex items-center justify-between">
              <h1 className="text-xl">My App</h1>
              <div className="flex items-center">
                <SignedOut>
                  <GoogleOneTap />
                  <SignInButton />
                </SignedOut>
                <SignedIn>
                  <UserButton showName />
                </SignedIn>
              </div>
            </div>
          </nav>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

When new users visit your application, Google One Tap will conveniently prompt them to sign in directly from the corner of the screen.

## Accessing Google services on behalf of the authenticated user using OAuth

![Accessing Google services via OAuth after Google sign-in](./3.png)
When users sign in with Google using Clerk, you're not just authenticating them — you're establishing a secure connection through OAuth that can do much more. While the authentication token proves who the user is, OAuth also enables your application to access Google services on their behalf.

Clerk simplifies this process. Beyond providing SSO with various providers, Clerk makes it straightforward to access user data from connected services. For example, you can retrieve an authenticated user's Google Calendar availability with just a few lines of code. For a detailed walkthrough, check out our [guide to accessing Google Calendar data](/blog/using-clerk-sso-access-google-calendar) with a complete demo application.

## Conclusion

In this guide, you've learned how to add Google authentication to your Next.js application using Clerk. Rather than building complex authentication logic yourself, Clerk provided pre-built components and middleware that enabled you to implement secure Google authentication in minutes. With Clerk's foundation in place, adding additional features like Google One Tap and OAuth access to Google services might have been easier to implement than you expected!

While we used Clerk's [Account Portal](/docs/customization/account-portal/overview) (a hosted authentication page) for the fastest implementation, you can also build your own sign-in and sign-up pages then render `<SignIn />` and `<SignUp />` directly in your application without the need for an external redirect. Learn how in this [guide](/docs/references/nextjs/custom-sign-in-or-up-page) or by following along with this video:

For more information about how to add Google as a social connection and an important note on switching to production, please refer to the [Clerk documentation](/docs/authentication/social-connections/google).

---

# What is middleware in Next.js?
URL: https://clerk.com/blog/what-is-middleware-in-nextjs.md
Date: 2025-01-16
Category: Guides
Description: Learn all about middleware in Next.js and how it works, as well as some of its common use cases, in this comprehensive guide.

Next.js middleware provides you with an incredible opportunity to customize the way your [Next.js application](https://nextjs.org) handles requests.

Middleware enables developers to intercept requests and perform operations like session validation, logging, and caching. While it may be tempting to use middleware to process and apply logic to every request to the application, doing so improperly might lead to massive performance depredations for your application. Once you understand how middleware works, you'll be better equipped to use middleware and understand when it shouldn't be used.

In this comprehensive guide, you'll learn what middleware is as it pertains to Next.js, how it works, and some of it's common use cases.

## What is Next.js middleware?

[Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) in Next.js refers to functions that run automatically for every incoming request, allowing you to inspect or modify the request data before it reaches your application's routing system.

Middleware can be used for a variety of purposes, such as [authentication](/nextjs-authentication), logging, and error handling. For example, you could use [middleware to authenticate](/docs/references/nextjs/clerk-middleware) incoming requests by checking tokens or credentials before allowing the request to proceed to your application's routing system.

Another benefit of using middleware in Next.js is its flexibility and customizability.

You can write your own middleware functions to fit the specific needs of your application to set application-wide settings or policies. This prevents you from having to worry about the complexity of having multiple layers of routing configuration.

By leveraging middleware, you can create a more robust, scalable, and maintainable application that meets the demands of complex web applications.

## When does Next.js process the middleware?

Next.js performs a series of operations when a request is received, so it helps to understand where middleware is handled in the order of operations:

### 1. `headers`

The `headers` configuration from `next.config.js` is applied first, setting the initial headers for every incoming request. This stage can be used to set security-related headers, such as content security policy or cross-origin resource sharing (CORS) headers.

### 2. `redirects`

The `redirects` configuration from `next.config.js` follows, determining how requests are redirected to other URLs. This stage handles URL rewriting and redirects, allowing you to manage routing rules that affect multiple pages or entire applications.

### 3. Middleware evaluation

Once `headers` and `redirects` are processed from the Next.js config file, the middleware is evaluated, and any logic within is executed. As you might expect, we’re going to dive deeper into this step throughout the guide.

### 4. `beforeFiles`

Next, the `beforeFiles` (`rewrites`) from `next.config.js` is applied. This stage allows you to perform additional rewriting or file-specific logic before routing takes place.

### 5. File system routes

The application's file system routes come into play next, including directories like `public/` and `_next/static/`, as well as individual pages and apps. This stage is where your application's static files are served.

### 6. `afterFiles`

Next up the `afterFiles` (`rewrites`) from `next.config.js` apply, providing a final chance to modify request data before dynamic routing takes place.

### 7. Dynamic Routes

Dynamic routes, like `/blog/[slug]`, execute next in the sequence. These routes require specific handling and rewriting logic to accommodate variables or parameters.

### 8. `fallback`

Finally, the `fallback` from `next.config.js` is applied, determining what happens when a request can't be routed using other configurations. This stage provides an opportunity to implement error handlers or fallback routes.

## What are some common use cases for Next.js middleware?

### Authentication

[Authentication](/nextjs-authentication) can be used with a login system where a user's credentials are validated before accessing sensitive routes or data. For instance, you might use Next.js middleware to validate a user's session on every request, redirecting them to the login page if their token is invalid.

[Clerk](https://clerk.com) uses Next.js middleware to intercept the request and determine the user's authentication state, something we'll explore in more detail later in this article.

### Logging

Logging logic can be added to middleware to track important events in your application, such as user actions or errors. You might implement logging using Next.js middleware to log every request to a centralized server, allowing you to analyze and debug issues more efficiently.

### Data fetching

While there are certain limitations to what kind of fetching can be performed, middleware can technically be used to load data from an API or database on every request, providing the most up-to-date information to users.

We'll explore the limitations of Next.js middleware in a later section.

### Request routing

Middleware can be used to customize the routing behavior of your application, such as catching all requests to a certain path and redirecting them to another route. This might be useful for implementing a catch-all error handler or for rewriting URLs to use a different domain.

### Cacheing

Cacheing can be used to improve performance by storing frequently used resources in memory and controlling the number of requests from individual users. The following example would check a cache object for the content of the request. If found, it is returned, otherwise, the response is intercepted and added to the cache for the next request.

```tsx
import { NextResponse } from 'next/server'

const cache = new Map()

export function middleware(request) {
  const { pathname } = request.nextUrl

  // Check if the response is cached
  if (cache.has(pathname)) {
    return new NextResponse(cache.get(pathname), {
      headers: { 'X-Cache': 'HIT' },
    })
  }

  // If not cached, proceed with the request
  const response = NextResponse.next()

  // Cache the response for future requests
  response.then((res) => {
    const clonedRes = res.clone()
    clonedRes.text().then((body) => {
      cache.set(pathname, body)
    })
  })

  response.headers.set('X-Cache', 'MISS')
  return response
}

export const config = {
  matcher: '/api/:path*',
}
```

### Rate limiting

Similarly, you could use middleware to keep track of requests coming from a single user or IP address and block the request if that user is making too many requests too frequently. This can help prevent upstream resources (ie: database) from being impacted for other uses.

### Page transforms

HTML rewrites and data transforms can be used to customize the behavior of your application when serving HTML files or transforming data in real-time. For instance, you might use Next.js middleware to rewrite URLs for images and other static assets, allowing you to host them on a different domain or with a custom subdomain.

### Analytics/reporting

Analytics and reporting can be used to track user behavior and monitor application performance, providing insights for improving the overall experience. You could use Next.js middleware to modify cookies on the fly, allowing you to integrate tracking scripts from third-party analytics providers without affecting the application's functionality.

### Internationalization

Internationalization can be used to deliver content in multiple languages and adapt the UI based on the user's locale. For example, you might determine a user's location by their IP or an HTTP header using middleware, redirecting users to a different language version of your application when they access it with a specific query parameter or cookie.

## How can I use middleware in a Next.js project?

To use middleware in a Next.js project, you'd create a single file at the root of the project called `middleware.ts` and add the necessary components.

Creating middleware involves defining a `middleware` function and (optionally) a matcher.

### The `middleware` function

The `middleware` function is where the logic of the middleware is stored. It uses a request as the single parameter and returns a response like so:

```tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Your middleware logic here
  return NextResponse.next()
}
```

Using the `NextRequest` and `NextResponse` objects, you could write a basic middleware to redirect requests to `/dashboard`, while allowing requests to other routes to proceed:

```tsx
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api')) {
    return NextResponse.next()
  }

  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/sign-in', request.url))
  }
}
```

It's important to note that the `middleware` function must return one of the following responses:

- `NextResponse.next()` - Allows the request to proceed to its destination.
- `NextResponse.redirect()` - Redirects the response to another route.
- `NextResponse.rewrite()` - Transparently renders an alternate route internally without forcing the browser to redirect.
- `NextResponse.json()` - Returns raw JSON to the caller.
- `Response`/`NextResponse` - You can craft a custom response to the caller.

### The matcher

The matcher is how Next.js decides if a request should be processed by middleware. The matcher is defined in and exported via the `config` object like so:

```tsx
export const config = {
  matcher: '/hello',
}
```

You can also define a matcher in a number of ways. The above example matches a single route, but you can also include an array of routes:

```tsx
export const config = {
  matcher: ['/hello', '/world'],
}
```

And for more complex scenarios, you can use regex:

```tsx
export const config = {
  matcher: ['/hello', '/world', '//[a-zA-Z]+/'],
}
```

If a matcher is not specified, Next.js will use the middleware for ALL routes. This can cause the middleware to run when it really doesn't need to, which can lead to degraded performance and potentially increased hosting costs.

## How to combine multiple Next.js middleware

Next.js only supports one middleware file and function per project. If you need to use multiple functions, you'd have to create separate functions that return the appropriate response and call them in sequence, conditionally returning a response if a middleware generates one.

For example, let's say you want to use both a logging middleware and an authentication middleware.

First, create two separate middleware functions:

```tsx
export function logRequest(req) {
  console.log(`Request made to: ${req.nextUrl.pathname}`)
}
```

```tsx
import { NextResponse } from 'next/server'

// Assuming an in-memory cache for sessions
const sessions = new Map()

export function checkAuth(req) {
  const token = req.cookies.get('auth-token')
  if (!token) {
    return NextResponse.redirect(new URL('/login', req.url))
  }

  // Check if the session exists and is not expired
  if (sessions.has(token)) {
    const session = sessions.get(token)
    if (session.expiration < Date.now()) {
      // Session has expired, clear it from cache and redirect to login
      sessions.delete(token)
      return NextResponse.redirect(new URL('/login', req.url))
    }
    // Valid session, proceed with the request
    return
  } else {
    // Session not found or invalid, redirect to login
    return NextResponse.redirect(new URL('/login', req.url))
  }
}
```

Then, in your `middleware.ts` file, use both of these functions in sequence, returning the response from `checkAuth` if the authentication checks fail:

```tsx
import { NextResponse } from 'next/server'
import { checkAuth } from './middleware/checkAuth'
import { logRequest } from './middleware/logRequest'

// Main Middleware File
export function middleware(req) {
  logRequest(req)

  const authResponse = checkAuth(req)
  if (authResponse) return authResponse

  return NextResponse.next()
}

export const config = {
  matcher: ['/hello', '/world', '//[a-zA-Z]+/'],
}
```

## How does Clerk use Next.js middleware?

Clerk uses middleware to protect routes as they come into your Next.js application. The `clerkMiddleware` function actually wraps the typical middleware logic and internally will parse the cookies coming into the request and verify them with your userbase in Clerk.

```tsx
import { clerkMiddleware } from '@clerk/nextjs/server'

export default clerkMiddleware()

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)(.*)',
  ],
}
```

Since we wrap the middleware logic, we can extend it and provide helper functions like `auth` which makes it easier to protect routes:

```tsx
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/forum(.*)'])

export default clerkMiddleware(async (auth, req) => {
  if (isProtectedRoute(req)) await 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)(.*)',
  ],
}
```

The body of the callback in `clerkMiddlware` works just like a standard middleware so you can also apply custom routing rules. For example, the following snippet shows you how to reroute the incoming request to `/onboarding` only if the user is logging in for the first time:

```tsx
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

const isPublicRoute = createRouteMatcher(['/'])

export default clerkMiddleware(async (auth, req) => {
  const { userId, sessionClaims, redirectToSignIn } = await auth()

  // If the user isn't signed in and the route is private, redirect to sign-in
  if (!userId && !isPublicRoute(req)) {
    return redirectToSignIn({ returnBackUrl: req.url })
  }

  // Catch users who do not have `onboardingComplete: true` in their publicMetadata
  // Redirect them to the /onboading route to complete onboarding
  if (
    userId &&
    !sessionClaims?.metadata?.onboardingComplete &&
    req.nextUrl.pathname !== '/onboarding'
  ) {
    const onboardingUrl = new URL('/onboarding', req.url)
    return NextResponse.redirect(onboardingUrl)
  }

  // All other routes are protected and the user is authenticated, let them view the requested page
})

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)(.*)',
  ],
}
```

> To learn more about how this onboarding flow works, check out [How to Add an Onboarding Flow for your Application with Clerk on our blog](/blog/add-onboarding-flow-for-your-application-with-clerk).

## Limitations to consider with Next.js middleware

Next.js middleware has several limitations that developers should be aware of:

### Edge Runtime Constraints

Middleware runs on the Edge Runtime, limiting the APIs and libraries that can be used. The Edge Runtime provides a subset of Node.js APIs, which means that middleware must rely on these limited resources. While this limitation may seem restrictive, it helps ensure that middleware functions are fast and efficient.

Because of the Edge Runtime, they also cannot use native Node.js APIs or perform operations like reading and writing to the file system.

### Size Restriction

Middleware functions are limited to 1MB in size, including all bundled code. This restriction is in place to ensure that middleware does not consume too much memory and can handle a large number of requests efficiently.

### ES Modules Only

Only ES Modules can be used in middleware. CommonJS modules are not supported. This is because ES Modules provide a more secure and efficient way of managing dependencies, which is essential for middleware functions.

### No String Evaluation

JavaScript's `eval` and `new Function(evalString)` are not allowed within the middleware runtime. This restriction helps prevent potential security vulnerabilities by blocking access to arbitrary code execution.

### Performance Considerations

Since middleware runs before every request with a matched route, complex or time-consuming operations in middleware can block users from receiving responses quickly. To mitigate this issue, developers should focus on writing lightweight and efficient middleware code that does not hinder the performance of their application.

It's also the reason that accessing a database within the middleware is generally not a good idea. It not only adds latency to the request but could also impact the performance of your database.

### Limited Access to Request/Response

Middleware does not have full access to complete request and response objects, which can limit certain dynamic operations. Specifically, the middleware cannot access the full URL path name, the request/response body, and some of the headers.

To work around this limitation, developers can use techniques like callbacks or promises to interact with the request and response objects.

## Conclusion

You are now better equipped to use Next.js middleware in the real world.

In this article, we have explored Next.js middleware from an introductory level but also discussed how it works, when it runs in relation to other operations Next performs with every request, and some of the best use cases for middleware.

---

# How to customize Next.js metadata
URL: https://clerk.com/blog/how-to-customize-nextjs-metadata.md
Date: 2025-01-09
Category: Guides
Description: Learn all about metadata and how to set it in your Next.js application

Improperly configured website metadata can cause drastic issues in user experience and website discoverability.

It’s important to not only understand what metadata is, but how it’s used by the greater internet, and how to configure it in your [Next.js](https://nextjs.org/) application. Next.js offers a number of different options when it comes to setting metadata, and the best option depends on the version of the framework you are using, and the way you are using it to generate pages for your visitors.

In this article, you'll learn the various ways you can customize and optimize your Next.js website's metadata to improve its SEO and user experience, as well as suggestions on when to use each approach.

## How is metadata used?

Wikipedia defines [metadata](https://en.wikipedia.org/wiki/Metadata) as "data that provides information about other data".

In the context of websites, metadata refers to the invisible data that describes the content of a website. This metadata is used by search engines, social media platforms, and other web services to understand the structure, content, and meaning of your website.

Depending on the configuration, missing or incorrect metadata can actively hurt the performance of your website. Here are some ways metadata is used:

- **Search engine optimization (SEO)**: Search engines use metadata to determine the relevance of your webpage for specific searches, as well as how those search engines rank a specific web page. The more accurate and defined the metadata is, the better a search engine will know to serve it when it matches a query.
- **Social media sharing**: Social media platforms use Open Graph metadata to display your website's content in their feeds, such as Facebook, Twitter, and LinkedIn. This is the information that's used to show stylized cards when a link is pasted in.
- **Content discovery**: Metadata helps users discover content that matches their interests and preferences.
- **Rich snippets**: Search engines can extract structured data from your website to display rich snippets in search results. Below is an example of rich snippets that are displayed with a movie to show ratings:

![An example of rich snippets that are displayed with a movie to show ratings](./schema-pic.png)

## What problems can occur with misconfigured metadata?

A number of issues can arise when metadata is not configured correctly.

For instance, poorly configured metadata can lead to lower search rankings, making it less likely users will find your website (e.g. ranking outside of the first search engine results page (SERP). This can be extremely detrimental to websites that thrive from organic traffic such as online shops. Furthermore, inaccurate or misleading metadata can damage a website's reputation and credibility, leading users to question the trustworthiness of its content.

Broken links and 404 errors can occur when metadata is incorrect or missing, creating a poor user experience. Other issues that create a poor user experience include incomplete page titles, descriptions, or images, duplicate content issues, inconsistent branding across different pages or sections, mobile usability issues, which increase the likelihood of users abandoning  if errors or inconsistencies are encountered.

By properly setting up your metadata, you can avoid these problems and create a better experience for your users.

## How does Next.js handle metadata by default?

By default, Next.js includes basic metadata such as the page title, character set, and viewport settings in the HTML head section of each page. The following is the HTML `head` of a newly generated Next.js project:

```html
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Create Next App</title>
  <meta name="description" content="Generated by create next app" />
  <meta name="next-size-adjust" content="" />
  <!-- script and link tags removed for brevity -->
</head>
```

However, this default behavior does not provide any specific metadata for SEO or social media optimization. You’ll want to add your own metadata for the application, and often on a per-page basis.

## How to customize metadata in Next.js

Next.js offers several solutions for customizing metadata depending on the application's needs.

### Using `next/head`

The first approach uses the built-in `next/head` component that can include Open Graph tags, Twitter cards, and other relevant metadata. This allows you to tailor your metadata to specific pages or routes, giving you more control over how your content is presented in search results and social media platforms.

```tsx
import Head from 'next/head'

export default function Home() {
  return (
    <>
      <Head>
        <title>My Custom Title</title>
        <meta name="description" content="This is my custom description for SEO." />
        <meta name="keywords" content="Next.js, metadata, SEO" />
        <meta property="og:title" content="My Custom Title" />
        <meta property="og:description" content="Open Graph description for sharing." />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <h1>Welcome to My Page</h1>
      </main>
    </>
  )
}
```

This approach is primarily used for client-side components where the `head` of the page needs to be changed dynamically based on the client interaction. For example, if you need to update the title or favicon of a tab in the browser.

### Exporting the `metadata` object

In versions of Next.js that use the App Router, you may also define the page metadata by exporting a variable aptly named `metadata`. This lets you define your metadata in a structure:

```tsx {{ filename: 'app/page.tsx' }}
export const metadata = {
  title: 'Clerk | Authentication and User Management',
  description:
    'The easiest way to add authentication and user management to your application. Purpose-built for React, Next.js, Remix, and “The Modern Web”.',
  keywords: ['Next.js', 'Authentication', 'User Management'],
  openGraph: {
    title: 'Clerk',
    description: 'The best user management and authentication platform.',
    url: 'https://clerk.com',
  },
}

// Code removed for brevity...
```

If you want to apply the same values across multiple pages, you can also export `metadata` from a layout file as well. This would apply the metadata to any child route of that layout.

Using this approach is best suited for pages with metadata that does not change frequently.

### Generating metadata dynamically

Finally, if you are generating metadata values on page load, you can use the `generateMetadata()` function to dynamically set the values. This is used in situations where a page template renders differently depending on the data that's loaded into it.

Using a blog as an example, there is typically one page template that's used to render every post, with the post slug being passed in as a page parameter. The following snippet demonstrates how this approach works. The slug is used to fetch the data for the post from an API before generating the metadata for that page:

```tsx {{ filename: 'app/blog/[slug]/page.tsx' }}
export async function generateMetadata({ params }) {
  const res = await fetch(`/api/posts/${params.slug}`)
  const post = await res.json()

  return {
    title: post.title,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      url: `https://clerk.com/blog/${params.slug}`,
      images: [{ url: post.image }],
    },
  }
}

export default function BlogPost({ params }) {
  return <h1>Blog Post: {params.slug}</h1>
}
```

Any place where the page is generated dynamically based on a data source would use this method.

## Next.js metadata inheritance

When customizing Next.js metadata, you don't need to specify the same values in all routes.

Next.js will automatically apply inheritance rules from the parent route to any child route if they are not defined. Using the blog example, if the root of the website has the following metadata defined in the topmost layout file:

```tsx
export const metadata = {
  title: 'Clerk | Authentication and User Management',
  description:
    'The easiest way to add authentication and user management to your application. Purpose-built for React, Next.js, Remix, and “The Modern Web”.',
  keywords: ['Next.js', 'Authentication', 'User Management'],
  openGraph: {
    title: 'Clerk',
    description: 'The best user management and authentication platform.',
    url: 'https://clerk.com',
  },
}
```

And also has this function that generates metadata for blog posts:

```tsx
export async function generateMetadata({ params }) {
  const res = await fetch(`/api/posts/${params.slug}`)
  const post = await res.json()

  return {
    title: post.title,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      url: `https://clerk.com/blog/${params.slug}`,
    },
  }
}
```

The `keywords` value would still be set on the blog post even though it is not explicitly defined in the `generateMetadata` function. This is because child metadata overwrites parent metadata, but only if a value is present.

## Conclusion

As you've learned throughout this guide, customizing metadata in Next.js can have a significant impact on user experience and how search engines rank your website. By understanding how to define and generate metadata dynamically, you'll be able to create a seamless and informative experience for your visitors.

---

# How to set environment variables in Node.js
URL: https://clerk.com/blog/how-to-set-environment-variables-in-nodejs.md
Date: 2024-12-27
Category: Company
Description: Explore the best practices and techniques you can use to set environment variables in Node.js, ensuring your app runs smoothly across different environments.

As software developers, we're constantly juggling the need for flexibility, scalability, and security in our applications.

One important aspect of achieving this balance is the proper use of environment variables. In this post, you'll learn various ways to set environment variables in Node.js, exploring best practices for their usage, as well as discussing key considerations for maintaining the security and integrity of your application. From validating environment variables at startup to avoiding committing sensitive files with Git, we'll cover essential guidelines for making the most of environment variables while minimizing potential risks.

Whether you're a seasoned developer or just starting out with Node.js, this piece aims to provide valuable insights and actionable advice for ensuring your applications are secure, maintainable, and scalable.

## What are environment variables?

[Environment variables](/glossary#environment-variables) are key-value pairs stored outside your codebase, often in a configuration file or system settings. They hold data like API keys, database credentials, or other environment-specific settings. This ensures sensitive information is not hardcoded into your application, keeping it secure and easier to manage across different environments, such as development, testing, and production.

In Node.js development, environment variables are an important concept to understand because they allow you to configure your application dynamically without modifying the code. For example, you can use the same codebase but point to different databases or APIs depending on whether you’re in a development or production environment. This flexibility improves security, simplifies deployment, and makes your app more adaptable.

Unlike regular JavaScript variables, environment variables are typically not defined in your code. Instead, they are stored in the operating system or external files (like `.env`) and accessed using `process.env`. Regular variables are scoped to your application, while environment variables exist independently and can influence multiple applications on the same system.

## Accessing environment variables in Node.js

In Node.js, the `process.env` object is used to access and manage environment variables. To retrieve a variable, you reference it using `process.env.VARIABLE_NAME`. For example, `process.env.API_KEY` fetches the value of `API_KEY`. You can technically set the value of an environment variable within the code, but this is generally counterproductive and defeats the purpose of using environment variables.

The following snippet shows how the `API_KEY` would be referenced in an Express API:

```ts
const express = require('express')
const app = express()

// Access API key from environment variables
const apiKey = process.env.API_KEY

if (!apiKey) {
  console.error('Error: API key is not defined.')
  process.exit(1)
}

app.get('/', (req, res) => {
  res.send('API key is successfully loaded.')
})

// Start the server
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
})
```

## Setting environment variables in Node.js

Now that you understand what environment variables are used for and how to access them, let's dive into the various ways to set them.

### 1. Using `dotenv`

One popular method for setting environment variables is the `dotenv` package. This package allows you to separate your environment-specific variables from your code and load them easily into your application.

You can specify the key-value pairs in a `.env` file like so:

```makefile
PORT=3000
DB_USERNAME=dbuser

```

You can then import the package and run the `config()` function which will import the values from `.env` by default:

```ts
import * as dotenv from 'dotenv'
dotenv.config()
```

Then, in your Node.js code, you can access these variables using the `process.env` object:

```ts
console.log(process.env.PORT) // Output: 3000
console.log(process.env.DB_USERNAME) // Output: dbuser
```

You can also load different environment variable files using the `path` parameter:

```ts
dotenv.config({ path: './path/to/another.env' })
```

While `dotenv` is very useful for development, other methods should likely be considered for running in a production environment.

### 2. Setting on the system level

On a Unix-based system (such as Linux or macOS), you can set environment variables at the system level by adding them to your shell configuration file such as  `~/.bashrc` for Bash or  `~/.zshrc` for ZShell. This will apply to all processes running in that shell session.

For example, add the following lines to your `~/.bashrc` file:

```{{ filename: '~/.bashrc' }}
export PORT=3000
export DB_USERNAME=myuser

```

Then, restart your terminal or run `source ~/.bashrc` to apply the changes. You can then access these variables in your Node.js application using the `process.env` object.

If you are running your application as a system process, the same values can be added to `/etc/environment` for global system access.

### 3. Setting in a launch script

You can also set environment variables at launch time by creating a script that sets the variables and then runs your Node.js application. For example, you can create a `launch.sh` file:

```bash
#!/bin/bash
export PORT=3000
export DB_USERNAME=myuser
node app.js
```

Make the script executable with `chmod +x launch.sh`, then run it using `./launch.sh`. This will set the specified environment variables and then run your Node.js application.

### 4. Setting in PM2

PM2 (Process Manager 2) is a popular process manager for Node.js applications. You can set environment variables when starting your application with PM2:

```bash
pm2 start app.js --env PORT=3000 --env DB_USERNAME=myuser
```

This will set the `PORT` and `DB_USERNAME` environment variables when running your application. You can also use a configuration file named `ecosystem.config.js` and specify values for different environments:

```js {{ filaname: 'ecosystem.config.js' }}
module.exports = {
  apps: [
    {
      name: 'my-app',
      script: 'app.js',
      instances: 1,
      env: {
        NODE_ENV: 'development',
        API_KEY: 'dev-key',
      },
      env_production: {
        NODE_ENV: 'production',
        API_KEY: 'prod-key',
      },
    },
  ],
}
```

Then, you can specify the environment to use when starting PM2:

```bash
pm2 start ecosystem.config.js --env production

```

### 5. Setting in Docker

When running a Node.js application in a Docker container, there are multiple ways that environment variables can be set. When defining the `Dockerfile`, you can use the `ENV` keyword to specify default values for environment variables:

```
FROM node:22

WORKDIR /app

ENV PORT=3000
ENV DB_USERNAME=myuser

COPY package*.json ./

RUN npm install

COPY . .

CMD ["node", "app.js"]

```

This will set the environment variables when building and running your Docker image.

When running the container, you can specify them in the command to override the default values as well:

```bash
docker run -e PORT=5173 my-image

```

Finally, when using `docker-compose` to run containers, you can specify the environment variables in the YAML:

```yaml
version: '3.8'

services:
  app:
    image: node:22
    container_name: my-app
    environment:
      - PORT=5173
```

## Best practices for using environment variables in Node.js applications

When working with environment variables in your Node.js application, there are some best practices you can follow to ensure security, maintainability, and scalability. Here are some key guidelines:

### Use descriptive names and document their purpose

When naming your environment variables, use descriptive names that clearly indicate their purpose. It's also a good idea to mention them in the project's README file with a short description documenting what each variable is used form, making it easier for other developers (or you in the future) to understand.

### Validate environment variables on app startup

Before using any environment variables, make sure they are properly set by validating them during startup. This can be done by checking if the variable exists and has a value. If not, you can default to a specific value or throw an error.

For example:

```jsx
const env = process.env
if (!env.USERNAME) {
  throw new Error('USERNAME environment variable is required')
}
```

### Avoid committing `.env` files with Git

By default, `.env` files are committed to your project's version control system (such as Git). If you are using a `.env` file to store environment variables, make sure to exclude your VCS from tracking the file. In Git, it's as simple as adding the following line to your `.gitignore` file:

```{{ filename: '.gitignore' }}
build/
dist/
node_modules

.env

```

### Consider using a KMS to increase security

To further increase the security of your environment variables, consider using a Key Management System (KMS). A KMS provides an additional layer of protection by encrypting and securely storing your sensitive data. This is especially important if you're working with sensitive information such as API keys or encryption keys.

### Specify default values

When defining environment variables, it's a good idea to specify default values for those that may not be set. This ensures that your application will continue to function even if certain environment variables are missing or unset. The default values should not contain sensitive information though.

For example, storing the connection string for a hosted database can be considered a risk. However, assuming that the project setup involves running the database locally, specifying `localhost` for the connection string would not be risky.

### Never expose environment variables in the front end

Most frameworks and tools have naming conventions developers can use to inject environment variables in to the development and building process. For example, you can use the `NEXT_PUBLIC_` prefix and any client-side code will be able to access those variables.

One of the most critical best practices is to never expose sensitive environment variables directly to the front-end. API keys and database connection strings should only ever be accessible from the server. When building with front end frameworks, make sure you understand how to securely use environment variables to avoid leaking secrets.

## How Clerk uses environment variables in Node.js projects

Clerk offers various SDKs supporting different Node.js configurations, with [Express](/docs/quickstarts/express) being our latest.

We use [environment variables](/docs/deployments/clerk-environment-variables) to configure the SDK and associate it with an application in the Clerk dashboard, which also loads any configuration items from the dashboards and allows users of that application to authenticate:

```{{ filename: '.env' }}
CLERK_PUBLISHABLE_KEY=pk_test_YmlnLXdlYXNlbC00NC5jbGVyay5hY2NvdW50cy5kZXYk
CLERK_SECRET_KEY=sk_test_frHrr**************************************

```

This allows Node.js projects to make [backend requests](/docs/backend-requests/handling/nodejs) to Clerk, as well as [front end projects to use Express](/blog/securing-node-express-apis-clerk-react) for validating API requests.

## Conclusion

In today's interconnected world, keeping your application's secrets safe is more crucial than ever. Environment variables are a fundamental part of any modern software project, but they can also be a significant security risk if not handled properly.

In this article, we've explored the best practices for using environment variables in Node.js applications, from validating their existence to avoiding exposing sensitive information directly to the front end. By following these guidelines and being mindful of potential risks, you'll be well on your way to creating secure and maintainable software that's ready for production.

---

# Building a React Login Page Template
URL: https://clerk.com/blog/building-a-react-login-page-template.md
Date: 2024-12-20
Category: Guides
Description: Learn how session-based authentication works and how to implement it in a React app with Express.

[Authentication](/glossary#authentication) is a crucial aspect of any web application that requires users to sign in and manage their accounts.

In this article, we'll be exploring how to implement a basic [authentication system using Express](/docs/quickstarts/express) as well as a signup and login form in [React.js](/glossary#react). You'll learn the difference between the JWT- and session-based authentication and some associated best practices. You'll then learn how to implement session authentication step by step using a real-world demo that's before getting access to a ready-to-use React login page template based on the steps outlined in this guide.

> For a production-ready solution, [learn more about our React support](/react-authentication) which handles all the complexity shown in this guide.

By the end of this tutorial, you'll have a solid understanding of how to create a login page using React, handle user registration, and protect routes from unauthenticated users.

We also have a guide on how to [build this same functionality into a Next.js application](/blog/building-a-nextjs-login-page-template) on our blog.

## Types of authentication

There are different ways to authenticate users, but the primary methods today are JWT-based authentication and session-based authentication.

### Session-based authentication

Session-based authentication is a method of tracking a user's login state by creating a unique [session](/glossary#session) for each authenticated user. When a user logs in, the server generates a session ID and stores session information server-side, typically in memory or a database. This session ID is then sent to the client, usually as a cookie, which the client includes with subsequent requests to prove authentication.

Cookies are used since they are sent back to the server with each request. When the server receives the session ID, it can look up the session in the database to both confirm its validity, as well as reference the associated user.

This article covers how to implement session-based authentication into a React application.

### JWT-based authentication

[JSON Web Token (JWT)](/glossary#json-web-token) authentication is an authentication method where a signed, encoded token is generated upon user login and returned to the client.

The JWT contains information such as the user ID, issue date, expiration date, etc. Once verified by the server using a private key, the server can trust that the information within the JWT correctly identifies the party that made the request.

Unlike session-based authentication, JWTs are self-contained and eliminate the need for server-side session storage.

You can read more about JWT vs. sessions in our article [Combining then benefits of session tokens and JWTs](/blog/combining-the-benefits-of-session-tokens-and-jwts).

## What is required to implement session authentication?

Implementing session-based authentication includes several key requirements.

### Storage

A storage mechanism is required to hold information about the users and the sessions. Using a database would require two tables, one for each entity. The following table diagram outlines the minimum requirements to implement session-based authentication.

![A database table diagram showing the minimum requirements to implement session-based authentication](./dbdiagram.png)

The `users` table will store general information about the user such as their name, email address, and a hashed version of the password. The `sessions` table will store details about each session.

Notice how the sessions table contains a userId field which is a foreign key of the users table. This is used to associate that session with a specific user.

### Handling sign up

Signing up the user will require a [form](/blog/validate-create-style-react-bootstrap-forms) to be created on the front end that is accessible if the user is not already authenticated. This typically includes, at minimum, inputs for username and password.

The server needs a route handler that can accept the user’s credentials when they click “Sign up”. The simplest implementation of this will create a user record and store the username and a hashed version of the password.

NEVER store the user credentials in plain text. If anyone obtains unauthorized access to your database, all of your users’ credentials will be exposed.

It’s also a best practice to implement validation on both the form the user interacts with and the route handler on the server. This ensures that any requirements for the inputs (ex: password complexity) are met before any records are created.

![A sequence diagram showing the steps involved in signing up a user](./sequence-diagram.png)

1. Using the login page in React, the user submits their username and password to the server.
2. The server creates a user record in the database.
3. The database returns the ID of the new record.
4. The server creates a session with the user ID.
5. The database returns the ID of the new record.
6. The server sets the cookie and responds back to the front end.

### Handling sign in

A login form is also required to allow the user to sign in. Again, the simplest implementation is at least a username and password.

As with handling sign-up, a route handler also needs to be created on the server to handle the credentials when they are submitted. Instead of creating a new user record, the credentials are verified with the existing user record.

If the React login page contains the proper credentials and they match when checked with the database, a session is created in the database. The token is added to a cookie that is sent back to the client.

Cookies are used for several reasons:

- They are automatically sent with each request to the server.
- Cookies can be configured to prevent client-side scripts from accessing them, making them more secure than local storage.
- An expiration can be set directly on the cookie, preventing the browser from trying to access a protected route when the session is expired.

![A sequence diagram showing the steps involved in signing in a user](./sequence-diagram.png)

1. Using the login page in React, the user submits their credentials to the server.
2. The server queries the user record to check the credentials.
3. The database returns the user record if found.
4. The server compares the hashed passwords. If successful, create a new session in the database.
5. The database responds with the ID of the session
6. The server sets the session ID in the cookie and sends it back to the client.

While this article will guide you through implementing session authentication in your React app, [check out how Clerk can accomplish it with only a few lines of code!](/docs/quickstarts/react)

### Protecting authorized routes

Finally, routes on both the front end and back end need to be configured to be protected. In a typical React application using `react-router-dom`, Routes can be wrapped in a parent component which will check the authorization status with the server before rendering them. When the React login page is used to authenticate a user, the `<ProtectedRoute />` component will automatically permit access to those views.

```tsx
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider } from '@/hooks/use-auth'
import { ProtectedRoute } from '@/components/protected-route'
import Home from '@/views/home'
import AppView from '@/views/app'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          {/* The following route is protected */}
          <Route
            path="/app"
            element={
              <ProtectedRoute>
                <AppView />
              </ProtectedRoute>
            }
          />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

On the server, the session ID that is sent to the server will be checked with the `sessions` table in the database to make sure the sessions is valid before processing the request. In [Express](https://expressjs.com), middleware can be created that wraps routes or route collections to make sure this is done automatically on the proper requests.

## Follow along with Quillmate

To demonstrate how session-based authentication can be implemented, we’ll use a realistic project called Qulllmate.

Quillmate is an open-source web application where writers can create articles with the help of an AI assistant. The core entity of Quillmate is an article, and each article has its own AI assistant that understands what has already been written and helps when asked questions about the material.

The following video demonstrates the finished product, where the user can sign up, sign in, create articles, and ask AI for assistance:

Quillmate is built with React and uses Express to both serve the application, as well as handle backend requests. The project uses Open AI to ask questions about articles, and Neon to store the data.

Quillmate is built with the following tech stack:

- **React** - The front end of the application is built with React.
- **Express** - The application is hosted with Express. Requests to paths starting with `/api` will be handled by various API routes, whereas any other requests will serve up the React app.
- **Neon** - Neon is a PostgreSQL database platform that is used for storing structured data.
- **Prisma** - To simplify requests to the database, Prisma is used as an ORM.
- **Open AI** - The AI Assistant functionality is backed by requests to the Open AI API.

You may optionally clone the [quillmate-react](https://github.com/bmorrisondev/quillmate-react) repository to follow along yourself with this article. Follow the directions in the readme to get the project running on your own system.

## Adding the database tables

Let’s start by adding the `users` table and `sessions` table. This can be done by modifying the Prisma schema and running a script to apply the changes to the Neon database.

Modify the `prisma/schema.prisma` file and add the `User` and `Session` models:

```{{ filename: 'prisma/schema.prisma', prettier: false }}
model User {
  id           Int       @id @default(autoincrement())
  email        String    @unique
  name         String?
  passwordHash String    @map("password_hash")
  sessions     Session[]
  createdAt    DateTime  @default(now()) @map("created_at")
  updatedAt    DateTime  @default(now()) @updatedAt @map("updated_at")
  articles     Article[]

  @@map("users")
}

model Session {
  id        Int      @id @default(autoincrement())
  token     String   @unique
  userId    Int      @map("user_id")
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  @@map("sessions")
}
```

Next, update the `Article` model to include a relationship with the user:

```{{ filename: 'prisma/schema.prisma', ins: [9,10], prettier: false }}
model Article {
  id        Int      @id @default(autoincrement())
  title     String   @db.VarChar(256)
  content   String   @db.Text
  summary   String?  @db.Text
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
  messages  Message[]
  user      User     @relation(fields: [userId], references: [id])
  userId    Int      @map("user_id")

  @@map("articles")
}
```

Finally, push the schema changes to Neon and update the local client by running the following commands in your terminal:

```bash
npx prisma db push
npx prisma generate
```

## Updating Express to support authentication

Next, you’ll need to update the backend to support creating accounts, creating sessions, and protecting the necessary routes.

### Install dependencies

The following dependencies are required:

- `bcryptjs` - Used for salting and hashing passwords before saving them to the database.
- `cookie-parser` - An Express middleware that makes working with cookies easier.
- [`zod`](https://zod.dev) - Used for type validation.

Run the following commands to install those packages and their types:

```bash
npm install bcryptjs cookie-parser zod
npm install -D @types/bcryptjs @types/cookie-parser
```

### Create the Express auth middleware

Next, create the Express middleware that will be used to protect routes. This will be used by protected routes to make sure that the request is authorized, returning a 401 status if it is not. It will also handle removing the session from the database if it’s expired.

Create a file at `server/middleware/auth.ts` and paste in the following:

```ts {{ filename: 'server/middleware/auth.ts' }}
import { Request, Response, NextFunction } from 'express'
import { prisma } from '../db/prisma'

export async function requireAuth(req: Request, res: Response, next: NextFunction) {
  try {
    const token = req.cookies.session

    if (!token) {
      res.status(401).json({ error: 'Authentication required' })
      return
    }

    const session = await prisma.session.findUnique({
      where: { token },
      include: { user: true },
    })

    if (!session) {
      res.clearCookie('session')
      res.status(401).json({ error: 'Invalid session' })
      return
    }

    if (session.expiresAt < new Date()) {
      await prisma.session.delete({ where: { id: session.id } })
      res.clearCookie('session')
      res.status(401).json({ error: 'Session expired' })
      return
    }

    req.user = {
      id: session.user.id,
      email: session.user.email,
      name: session.user.name,
    }
    req.session = {
      id: session.id,
      token: session.token,
    }

    next()
  } catch (error) {
    console.error('Auth middleware error:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
}
```

If your editor is complaining about setting the `req.user` and `req.session` values, create a file at `server/types/express.d.ts` and paste in the following:

```ts {{ filename: 'server/types/express.d.ts' }}
declare global {
  namespace Express {
    interface Request {
      user?: {
        id: number
        email: string
        name: string | null
      }
      session?: {
        id: number
        token: string
      }
    }
  }
}

export {}
```

### Add routes to handle auth functions

Now let’s add the routes required to enable sign-up and sign-in. The route file will also include validation from  There will be four routes in total:

- `/api/signin` - Used by the React login page to create a session and return it back to the user.
- `/api/signup` - Used to create an account, which we’ll use to also create a session right away.
- `/api/signout` - Used to sign the user out, which deletes the session. This route is protected by the middleware.
- `/api/me` - Used by the front end to verify that the session is valid before loading the articles. This route is also protected by the middleware.

Create a file at `server/routes/auth.ts` and paste in the following:

```ts {{ filename: 'server/routes/auth.ts' }}
import { Router } from 'express'
import { prisma } from '../db/prisma'
import bcrypt from 'bcryptjs'
import { randomBytes } from 'crypto'
import { ZodError } from 'zod'
import { requireAuth } from '../middleware/auth'
import { z } from 'zod'

const router = Router()

const signUpSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
    .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
    .regex(/[0-9]/, 'Password must contain at least one number'),
  name: z.string().optional(),
})

// Sign up
router.post('/signup', async (req, res) => {
  try {
    const result = signUpSchema.safeParse(req.body)
    if (!result.success) {
      res.status(400).json({
        error: 'Validation failed',
        details: result.error.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }

    const { email, password, name } = result.data

    // Validate input
    if (!email || !password) {
      res.status(400).json({ error: 'Email and password are required' })
      return
    }

    // Check if user exists
    const existingUser = await prisma.user.findUnique({
      where: { email },
    })

    if (existingUser) {
      res.status(400).json({ error: 'Email already registered' })
      return
    }

    // Hash password
    const salt = await bcrypt.genSalt(10)
    const passwordHash = await bcrypt.hash(password, salt)

    // Create user
    const user = await prisma.user.create({
      data: {
        email,
        passwordHash,
        name,
      },
    })

    // Create session
    const token = randomBytes(32).toString('hex')
    const expiresAt = new Date()
    expiresAt.setDate(expiresAt.getDate() + 30) // 30 days from now

    await prisma.session.create({
      data: {
        token,
        userId: user.id,
        expiresAt,
      },
    })

    // Set cookie
    res.cookie('session', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      expires: expiresAt,
    })

    res.json({
      id: user.id,
      email: user.email,
      name: user.name,
    })
  } catch (err) {
    console.error('Sign up error:', err)
    if (err instanceof ZodError) {
      res.status(400).json({
        error: 'Validation failed',
        details: err.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }
    res.status(500).json({ error: 'Internal server error' })
  }
})

const signInSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(1, 'Password is required'),
})
// Sign in
router.post('/signin', async (req, res) => {
  try {
    const result = signInSchema.safeParse(req.body)
    if (!result.success) {
      res.status(400).json({
        error: 'Validation failed',
        details: result.error.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }

    const { email, password } = result.data

    // Validate input
    if (!email || !password) {
      res.status(400).json({ error: 'Email and password are required' })
      return
    }

    // Find user
    const user = await prisma.user.findUnique({
      where: { email },
    })

    if (!user) {
      res.status(401).json({ error: 'Invalid credentials' })
      return
    }

    // Verify password
    const isValid = await bcrypt.compare(password, user.passwordHash)
    if (!isValid) {
      res.status(401).json({ error: 'Invalid credentials' })
      return
    }

    // Create session
    const token = randomBytes(32).toString('hex')
    const expiresAt = new Date()
    expiresAt.setDate(expiresAt.getDate() + 30) // 30 days from now

    await prisma.session.create({
      data: {
        token,
        userId: user.id,
        expiresAt,
      },
    })

    // Set cookie
    res.cookie('session', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      expires: expiresAt,
    })

    res.json({
      id: user.id,
      email: user.email,
      name: user.name,
    })
  } catch (err) {
    console.error('Sign in error:', err)
    if (err instanceof ZodError) {
      res.status(400).json({
        error: 'Validation failed',
        details: err.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }
    res.status(500).json({ error: 'Internal server error' })
  }
})

// Sign out
router.post('/signout', requireAuth, async (req, res) => {
  try {
    const token = req.cookies.session
    if (token) {
      await prisma.session.delete({
        where: { token },
      })
      res.clearCookie('session')
    }
    res.json({ message: 'Signed out successfully' })
  } catch (error) {
    console.error('Signout error:', error)
    res.status(500).json({ error: 'Failed to sign out' })
  }
})

// Get current user
router.get('/me', requireAuth, async (req, res) => {
  try {
    const token = req.cookies.session
    if (!token) {
      res.json({ user: null })
      return
    }

    const session = await prisma.session.findUnique({
      where: { token },
      include: { user: true },
    })

    if (!session || session.expiresAt < new Date()) {
      res.clearCookie('session')
      res.json({ user: null })
      return
    }

    res.json({
      id: session.user.id,
      email: session.user.email,
      name: session.user.name,
    })
  } catch (error) {
    console.error('Get current user error:', error)
    res.status(500).json({ error: 'Failed to get current user' })
  }
})

export default router
```

### Update the `/api/articles` route to filter by user

Next, we’ll need to update the `/api/articles` routes to ensure that when database records are created, the user information is saved with the record as well. This will allow queries to return only the articles created by that user.

Update `server/routes/articles.ts` like so:

```ts {{ filename: 'server/routes/articles.ts', ins: [[10, 13], [16, 18], [22, 30], [42, 45], [48, 60], [78, 81], [90, 100], [112, 115], 124, 125, [134, 136], [143, 151], [164, 168], 175, 176] }}
import { Router } from 'express'
import { prisma } from '../db/prisma'
import { requireAuth } from '../middleware/auth'

const router = Router()

// Get all articles
router.get('/', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const allArticles = await prisma.article.findMany({
      where: {
        userId: req.user.id,
      },
      orderBy: {
        updatedAt: 'desc',
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })
    res.json(allArticles)
  } catch (error) {
    console.error('Error fetching articles:', error)
    res.status(500).json({ error: 'Failed to fetch articles' })
  }
})

// Get single article
router.get('/:id', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const article = await prisma.article.findUnique({
      where: {
        id: parseInt(req.params.id),
        userId: req.user.id,
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    if (!article) {
      res.status(404).json({ error: 'Article not found' })
      return
    }

    res.json(article)
  } catch (error) {
    console.error('Error fetching article:', error)
    res.status(500).json({ error: 'Failed to fetch article' })
  }
})

// Create article
router.post('/', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const { title, content, summary } = req.body

    const newArticle = await prisma.article.create({
      data: {
        title,
        content,
        summary,
        userId: req.user.id,
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })
    res.json(newArticle)
  } catch (error) {
    console.error('Error creating article:', error)
    res.status(500).json({ error: 'Failed to create article' })
  }
})

// Update article
router.put('/:id', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const { title, content, summary } = req.body
    const articleId = parseInt(req.params.id)

    // First verify the article belongs to the user
    const article = await prisma.article.findUnique({
      where: {
        id: articleId,
        userId: req.user.id,
      },
    })

    if (!article) {
      res.status(404).json({ error: 'Article not found' })
      return
    }

    const updatedArticle = await prisma.article.update({
      where: {
        id: articleId,
      },
      data: {
        title,
        content,
        summary,
        updatedAt: new Date(),
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    res.json(updatedArticle)
  } catch (error) {
    console.error('Error updating article:', error)
    res.status(500).json({ error: 'Failed to update article' })
  }
})

// Delete article
router.delete('/:id', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const articleId = parseInt(req.params.id)

    // First verify the article belongs to the user
    const article = await prisma.article.findUnique({
      where: {
        id: articleId,
        userId: req.user.id,
      },
    })

    if (!article) {
      res.status(404).json({ error: 'Article not found' })
      return
    }

    const deletedArticle = await prisma.article.delete({
      where: {
        id: articleId,
      },
    })

    res.json(deletedArticle)
  } catch (error) {
    console.error('Error deleting article:', error)
    res.status(500).json({ error: 'Failed to delete article' })
  }
})

export default router
```

### Update the Express server entry point

The last thing to do on the server is update `server/index.ts` to set up `cookie-parser`, register the `/api/auth` routes, and protect the `/api/articles` and `/api/ai` routes.

Update `server/index.ts` as follows:

```ts {{ filename: 'server/index.ts', ins: [[10, 12], 27, 28, 37, 40, 41] }}
import express from 'express'
import path from 'path'
import cors from 'cors'
import dotenv from 'dotenv'
dotenv.config()

import { createProxyMiddleware } from 'http-proxy-middleware'
import articlesRouter from './routes/articles'
import aiRouter from './routes/ai'
import cookieParser from 'cookie-parser'
import authRoutes from './routes/auth'
import { requireAuth } from './middleware/auth'

console.log(`NODE_ENV: ${process.env.NODE_ENV}`)

const app = express()
const PORT = process.env.PORT || 3000
const VITE_PORT = process.env.VITE_PORT || 5173

// Middleware
app.use(
  cors({
    origin:
      process.env.NODE_ENV === 'production' ? process.env.FRONTEND_URL : 'http://localhost:5173',
    credentials: true,
  }),
)
app.use(cookieParser())
app.use(express.json())

// API routes
app.use('/api', (req, res, next) => {
  console.log(`API Request: ${req.method} ${req.url}`)
  next()
})

// Public routes
app.use('/api/auth', authRoutes)

// Protected routes
app.use('/api/articles', requireAuth, articlesRouter)
app.use('/api/ai', requireAuth, aiRouter)

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' })
})

// Development: Proxy all non-API requests to Vite dev server
if (process.env.NODE_ENV !== 'production') {
  app.use(
    '/',
    createProxyMiddleware({
      target: `http://localhost:${VITE_PORT}`,
      changeOrigin: true,
      ws: true,
      // Don't proxy /api requests
      filter: (pathname: string) => !pathname.startsWith('/api'),
    }),
  )
} else {
  // Production: Serve static files
  app.use(express.static(path.join(__dirname, '../dist')))

  // Handle React routing in production
  app.get('*', (req, res) => {
    if (!req.path.startsWith('/api')) {
      res.sendFile(path.join(__dirname, '../dist/index.html'))
    }
  })
}

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
  if (process.env.NODE_ENV !== 'production') {
    console.log(`Proxying non-API requests to http://localhost:${VITE_PORT}`)
  }
})
```

## Implementing the login page in React

With the backend updated to support authentication, let’s update the frontend to do the same.

### Create a context, provider, and hook for global auth

Since the authentication state can affect the way the entire application behaves, we’ll create a React Context and Provider so we can check if the user is logged in from anywhere. We’ll also add the logic to sign up, sign in, and sign out from the context, making it easy to call those functions from various points of the app.

Create the `src/hooks/use-auth.tsx` file and add the following code:

```tsx {{ filename: 'src/hooks/use-auth.tsx' }}
import { createContext, useContext, useState, useEffect } from 'react'
import { User } from '@/types'

interface AuthContextType {
  user: User | null
  signIn: (email: string, password: string) => Promise<void>
  signUp: (email: string, password: string, name?: string) => Promise<void>
  signOut: () => Promise<void>
  isLoading: boolean
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    checkAuth()
  }, [])

  // Used to check the session status with the server
  const checkAuth = async () => {
    try {
      const response = await fetch('/api/auth/me', {
        credentials: 'include',
      })
      const data = await response.json()
      setUser(data)
    } catch (error) {
      console.error('Failed to check auth status:', error)
      setUser(null)
    } finally {
      setIsLoading(false)
    }
  }

  // Used to create a session and store the user data in the context
  const signIn = async (email: string, password: string) => {
    const response = await fetch('/api/auth/signin', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({ email, password }),
    })

    if (!response.ok) {
      const error = await response.json()
      throw new Error(error.error || 'Failed to sign in')
    }

    const data = await response.json()
    setUser(data)
  }

  // Used to sign up a new user, create a session, and store the user data in the context
  const signUp = async (email: string, password: string, name?: string) => {
    const response = await fetch('/api/auth/signup', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({ email, password, name }),
    })

    if (!response.ok) {
      const error = await response.json()
      throw new Error(error.error || 'Failed to sign up')
    }

    const data = await response.json()
    setUser(data)
  }

  // Used to sign out a user and clear the user data from the context
  const signOut = async () => {
    await fetch('/api/auth/signout', {
      method: 'POST',
      credentials: 'include',
    })
    setUser(null)
  }

  return (
    <AuthContext.Provider value={{ user, signIn, signUp, signOut, isLoading }}>
      {children}
    </AuthContext.Provider>
  )
}

// Custom hook to access the auth context from any component
export function useAuth() {
  const context = useContext(AuthContext)
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider')
  }
  return context
}
```

Next, update the `src/App.tsx` file to wrap the entire application with the provider:

```tsx {{ filename: 'src/App.tsx', ins: [4, 8, 16] }}
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import Home from '@/views/home'
import AppView from '@/views/app'
import { AuthProvider } from '@/hooks/use-auth'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/app" element={<AppView />} />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

### Add sign-up and sign-in views

Next, let’s add the sign-up and sign-in views. These views will both be very similar, each containing form elements where the user can enter their credentials. Both also use `zod` for client-side validation, which prevents an unnecessary network trip to the server if the user does not populate the fields properly. If validation fails, errors will be shown on the respective fields.

The key difference between them is what happens when the user submits the form, specifically, the method called from the `useAuth` hook created earlier.

Create the `src/views/sign-in.tsx` page and populate it with the following code:

```tsx {{ filename: 'src/views/sign-in.tsx' }}
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useAuth } from '@/hooks/use-auth'
import { z, ZodError } from 'zod'

const signInSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(1, 'Password is required'),
})

export default function SignIn() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
  })
  const [errors, setErrors] = useState<{
    email?: string
    password?: string
    submit?: string
  }>({})
  const [isSubmitting, setIsSubmitting] = useState(false)
  const navigate = useNavigate()
  const { signIn } = useAuth()

  const handleSubmit = async (data: typeof formData) => {
    try {
      setIsSubmitting(true)
      setErrors({})

      // Validate the form data
      const validatedData = signInSchema.parse(data)

      // Attempt sign in
      await signIn(validatedData.email, validatedData.password)
      navigate('/app')
    } catch (error) {
      if (error instanceof ZodError) {
        const formattedErrors: Record<string, string> = {}
        error.errors.forEach((err) => {
          if (err.path) {
            formattedErrors[err.path[0]] = err.message
          }
        })
        setErrors(formattedErrors)
      } else {
        setErrors({ submit: 'Failed to sign in. Please try again.' })
      }
    } finally {
      setIsSubmitting(false)
    }
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setFormData((prev) => ({ ...prev, [name]: value }))
    // Clear error when user starts typing
    if (errors[name as keyof typeof errors]) {
      setErrors((prev) => ({ ...prev, [name]: undefined }))
    }
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-purple-50/30 p-4">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle className="text-center text-2xl">Sign In</CardTitle>
        </CardHeader>
        <CardContent>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              handleSubmit(formData)
            }}
            className="space-y-4"
          >
            <div className="space-y-2">
              <label htmlFor="email" className="text-sm font-medium">
                Email
              </label>
              <Input
                id="email"
                name="email"
                type="email"
                value={formData.email}
                onChange={handleChange}
                required
                placeholder="Enter your email"
                className={errors.email ? 'border-red-500' : ''}
              />
              {errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
            </div>
            <div className="space-y-2">
              <label htmlFor="password" className="text-sm font-medium">
                Password
              </label>
              <Input
                id="password"
                name="password"
                type="password"
                value={formData.password}
                onChange={handleChange}
                required
                placeholder="Enter your password"
                className={errors.password ? 'border-red-500' : ''}
              />
              {errors.password && <p className="text-sm text-red-500">{errors.password}</p>}
            </div>
            {errors.submit && <p className="text-center text-sm text-red-500">{errors.submit}</p>}
            <Button type="submit" className="w-full" disabled={isSubmitting}>
              {isSubmitting ? 'Signing In...' : 'Sign In'}
            </Button>
            <div className="text-center text-sm">
              Don't have an account?{' '}
              <a href="/signup" className="text-purple-600 hover:text-purple-500">
                Sign up
              </a>
            </div>
          </form>
        </CardContent>
      </Card>
    </div>
  )
}
```

Do the same with `src/views/sign-up.tsx`:

```tsx {{ filename: 'src/views/sign-up.tsx' }}
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useAuth } from '@/hooks/use-auth'
import { z, ZodError } from 'zod'

const signUpSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
    .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
    .regex(/[0-9]/, 'Password must contain at least one number'),
  name: z.string().optional(),
})

export default function SignUp() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    name: '',
  })
  const [errors, setErrors] = useState<{
    email?: string
    password?: string
    name?: string
    submit?: string
  }>({})
  const [isSubmitting, setIsSubmitting] = useState(false)
  const navigate = useNavigate()
  const { signUp } = useAuth()

  const handleSubmit = async (data: typeof formData) => {
    try {
      setIsSubmitting(true)
      setErrors({})

      // Validate the form data
      const validatedData = signUpSchema.parse(data)

      // Attempt sign up
      await signUp(validatedData.email, validatedData.password, validatedData.name)
      navigate('/app')
    } catch (error) {
      if (error instanceof ZodError) {
        const formattedErrors: Record<string, string> = {}
        error.errors.forEach((err) => {
          if (err.path) {
            formattedErrors[err.path[0]] = err.message
          }
        })
        setErrors(formattedErrors)
      } else {
        setErrors({ submit: 'Failed to create account. Please try again.' })
      }
    } finally {
      setIsSubmitting(false)
    }
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setFormData((prev) => ({ ...prev, [name]: value }))
    // Clear error when user starts typing
    if (errors[name as keyof typeof errors]) {
      setErrors((prev) => ({ ...prev, [name]: undefined }))
    }
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-purple-50/30 p-4">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle className="text-center text-2xl">Sign Up</CardTitle>
        </CardHeader>
        <CardContent>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              handleSubmit(formData)
            }}
            className="space-y-4"
          >
            <div className="space-y-2">
              <label htmlFor="email" className="text-sm font-medium">
                Email
              </label>
              <Input
                id="email"
                name="email"
                type="email"
                value={formData.email}
                onChange={handleChange}
                required
                placeholder="Enter your email"
                className={errors.email ? 'border-red-500' : ''}
              />
              {errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
            </div>
            <div className="space-y-2">
              <label htmlFor="password" className="text-sm font-medium">
                Password
              </label>
              <Input
                id="password"
                name="password"
                type="password"
                value={formData.password}
                onChange={handleChange}
                required
                placeholder="Create a password"
                className={errors.password ? 'border-red-500' : ''}
              />
              {errors.password && <p className="text-sm text-red-500">{errors.password}</p>}
            </div>
            <div className="space-y-2">
              <label htmlFor="name" className="text-sm font-medium">
                Name (optional)
              </label>
              <Input
                id="name"
                name="name"
                type="text"
                value={formData.name}
                onChange={handleChange}
                placeholder="Enter your name"
                className={errors.name ? 'border-red-500' : ''}
              />
              {errors.name && <p className="text-sm text-red-500">{errors.name}</p>}
            </div>
            {errors.submit && <p className="text-center text-sm text-red-500">{errors.submit}</p>}
            <Button type="submit" className="w-full" disabled={isSubmitting}>
              {isSubmitting ? 'Creating Account...' : 'Create Account'}
            </Button>
          </form>
        </CardContent>
      </Card>
    </div>
  )
}
```

Now register the views in `src/App.tsx`:

```tsx {{ filename: 'src/App.tsx', ins: [5, 6, 14, 15] }}
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import Home from '@/views/home'
import AppView from '@/views/app'
import { AuthProvider } from '@/hooks/use-auth'
import SignIn from '@/views/auth/sign-in'
import SignUp from '@/views/auth/sign-up'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/signin" element={<SignIn />} />
          <Route path="/signup" element={<SignUp />} />
          <Route path="/app" element={<AppView />} />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

### Protecting the App page

To protect routes, we’ll create a separate component that will use the context & provider and check the authentication state before allowing the user to proceed. If the authentication state is being checked by the provider, a “Loading…” message will be rendered for the user. If the user is not logged in, they will be redirected to the sign-in view.

Create the `src/components/protected-route.tsx` file and add the following:

```tsx {{ filename: 'src/components/protected-route.tsx' }}
import { Navigate } from 'react-router-dom'
import { useAuth } from '@/hooks/use-auth'

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth()

  if (isLoading) {
    return (
      <div className="flex min-h-screen items-center justify-center bg-purple-50/30">
        <div className="text-purple-600">Loading...</div>
      </div>
    )
  }

  if (!user) {
    return <Navigate to="/signin" replace />
  }

  return <>{children}</>
}
```

Update `src/App.tsx` and wrap the element for the `/app` route in the `<ProtectedRoute>` component:

```tsx {{ filename: 'src/App.tsx', ins: [7, 8, [18, 25]], del: [15] }}
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider } from '@/hooks/use-auth'
import SignIn from '@/views/auth/sign-in'
import SignUp from '@/views/auth/sign-up'
import Home from '@/views/home'
import AppView from '@/views/app'
import { ProtectedRoute } from '@/components/protected-route'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/app" element={<AppView />} />
          <Route path="/signin" element={<SignIn />} />
          <Route path="/signup" element={<SignUp />} />
          <Route
            path="/app"
            element={
              <ProtectedRoute>
                <AppView />
              </ProtectedRoute>
            }
          />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

### Add a sign-out button

The last thing to add is a sign-out button that lets users log out of the app once they are finished. This will use the `signOut` function of the `useAuth` hook to remove the session from the database and clear the cookie set in the browser.

Update `src/components/article-list.tsx` with the following:

```jsx {{ filename: 'src/components/article-list.tsx', ins: [4, 19, [49, 57]], prettier: false }}
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Article } from "@/types"
import { useAuth } from "@/hooks/use-auth"

interface ArticleListProps {
  articles: Article[]
  selectedArticle: Article | null
  onArticleSelect: (article: Article) => void
  onNewArticle: () => void
}

export function ArticleList({
  articles,
  selectedArticle,
  onArticleSelect,
  onNewArticle,
}: ArticleListProps) {
  const { signOut } = useAuth()

  return (
    <div className="flex h-full flex-col">
      <div className="border-b border-purple-100 p-4 bg-white">
        <div className="flex justify-between items-center">
          <h2 className="text-lg font-semibold">Articles</h2>
          <Button onClick={onNewArticle} size="sm">New</Button>
        </div>
      </div>
      <ScrollArea className="flex-1">
        <div className="space-y-4 p-4">
          {articles.map(article => (
            <div
              key={article.id}
              className={`p-4 rounded-lg cursor-pointer transition-colors ${
                selectedArticle?.id === article.id
                  ? 'bg-purple-100'
                  : 'hover:bg-purple-50'
              }`}
              onClick={() => onArticleSelect(article)}
            >
              <h3 className="font-medium mb-1">{article.title}</h3>
              <div className="text-sm text-gray-500">
                <p>Updated {new Date(article.updatedAt).toLocaleDateString()}</p>
              </div>
            </div>
          ))}
        </div>
      </ScrollArea>
      <div className="border-b border-t border-purple-100 p-4 bg-white">
        <Button
          onClick={() => signOut()}
          variant="outline"
          className="w-full"
        >
          Sign Out
        </Button>
      </div>
    </div>
  )
}
```

## Testing the app

With all the changes in place, you may test the application! Run the application with the following command in your terminal:

```bash
npm run dev
```

Now open `localhost:3000` in your browser and try creating a user to use the application, create an article, and experiment with the AI functionality! I encourage you to also log into Neon and check the contents of the database, specifically the `users` and `sessions` tables as they contain the records needed to support authentication.

## So why Clerk then?

Clerk is a user management and authentication platform, so it might surprise you that we’re publishing an article walking you through how to implement authentication yourself.

This article covers how to implement a single method of authentication, but in reality, user management is much more than just session-based authentication. For example, it’s commonplace in modern web applications to also support social login providers like [Google](/blog/add-google-login-next13-app) or Apple. A password reset flow is also a critical requirement for supporting your own authentication setup.

These are just a few of the many features Clerk supports out of the box, oftentimes with a single line of code.

Using Clerk, you can easily create a sign-in page by just importing and rendering the [`<SignIn />`](/docs/components/authentication/sign-in) [component](/docs/components/overview) like so:

```tsx
import { SignIn } from '@clerk/clerk-react'

export default function SignInView() {
  return <SignIn />
}
```

If you want to learn how easy it is to get Clerk working in a React application, check out the [quickstart on our docs](/docs/quickstarts/react).

If you want to download a template version of the React login page template to use in your own project, check out the [react-session-auth-template](https://github.com/bmorrisondev/react-session-auth-quickstart) repository on GitHub.

## Conclusion

In conclusion, this article has walked you through how to implement a basic authentication system using React and Express. By setting up a session-based authentication flow, we've covered how to create a sign-in page, handle user registration, log users in and out, as well as protect routes with a simple `ProtectedRoute` component.

While implementing a custom authentication system can be a great learning experience, it's also important to consider the larger picture of what user management really entails. In reality, you'll often need to support features like [social login](/blog/social-sso-in-next-js) providers, password reset flows, and more.

That's where Clerk comes in - a [user management and authentication platform](https://clerk.com) that simplifies the process of implementing these features with just a few lines of code. If you're interested in learning more about how easy it is to get Clerk working in a React application, check out the [quickstart guide](/docs/quickstarts/react) on our documentation site.

---

# How to implement per-user OAuth scopes with Clerk
URL: https://clerk.com/blog/implement-per-user-oauth-with-clerk.md
Date: 2024-12-13
Category: Guides
Description: Learn how to implement per-user OAuth scopes with Clerk.

When developing a SaaS application that relies on third-party data, it's important to recognize that not all users will want to grant access to their data unless it's essential for the application's functionality.

In a recent article, I covered how you can use [Clerk Single Sign On (SSO)](/docs/authentication/social-connections/overview) connections to access Google Calendar data on behalf of the user. When configuring SSO, scopes are used to inform the service provider (SP) what kind of access is required for the connection that is being established. It’s important to specify only the scopes that are required and no more, a variation of the least privilege access principle, however, some users might require more access than others.

In this article, you’ll learn about the concept of least privilege access, and how to customize OAuth scopes on a per-user basis.

## What is least privilege access?

[Least privilege access](/glossary#least-priveledge-access) is a security principle where users are given the minimum levels of access or permissions needed.

This approach limits potential damage from system breaches by restricting each user's ability to interact with systems, networks, and data beyond their essential requirements. By granting only the precise access rights necessary for a user's role, organizations can significantly reduce their overall cybersecurity risk and potential attack surface.

Typically least privilege access is used in the context of users accessing a system, however, the same principle can be applied when services are accessing user data in another system.

[OAuth scopes](/glossary#oauth-scopes) allow the user to make an educated decision about what data the system should be able to access. A system that only asks for the minimum access required can not only help ease the user's concern about the data being accessed, but it could also protect the developer in situations where attackers gain access to areas of the system they shouldn’t.

For example, if you build a system that only requires access to [Google Calendar](http://calendar.google.com) data, but you simply ask for access to ALL the user's data, you could be held liable if someone deleted the user's Google Docs!

## Implementing user-specific OAuth scopes with Clerk

To demonstrate how applications can be configured to only request access to the necessary permissions, we’ll use BookMate as a demo.

[BookMate](https://bookmate-sigma.vercel.app) is a lightweight clone of popular scheduling tools like [Cal.com](http://Cal.com) or Calendly. Users can link their Google Calendar so visitors can request a meeting with them using a public profile. When a visitor requests a meeting, Bookmate will send both parties a calendar invite via email.

To accommodate this functionality, BookMate uses the following OAuth scopes from Google:

- `https://www.googleapis.com/auth/calendar.readonly`
- `https://www.googleapis.com/auth/calendar.events.readonly`

These scopes are defined in the [Clerk dashboard](https://dashboard.clerk.com/apps) under the Google SSO settings and automatically apply to all users who sign into the app.

Let’s add a feature that also allows the calendar entry to be added directly to the user’s calendar instead of just sending an invite, further streamlining the process. To accommodate this, we’ll also need the following scope to be added to the login process:

- `https://www.googleapis.com/auth/calendar.events`

This feature will be optional and we wouldn't want to apply this scope to all users since it allows BookMate to directly modify the user's calendar. Therefore we cannot simply add it to the list of scopes in the Clerk dashboard.

To accomplish this new functionality, we’ll take the following steps:

1. Add a toggle to the settings that allows BookMate to add events to the user's calendar. When the toggle is enabled, check the existing scopes on the Google [access token](/glossary#access-token) for the current user.
2. If the access token does not have that scope, initiate a reauthorization that requires the user to allow access to their calendar, adding the scope to the token.
3. Storing the user-specific scopes with the user’s public metadata in Clerk.
4. Add a global provider that will automatically handle reauthorization if the scope is required.

The source code for this article is available [on GitHub](https://github.com/bmorrisondev/bookmate). The following sections will cover the functionality of the code at a high level, I encourage you to check the repository to see more specifically how the code works together!

### Adding the toggle to check the user's scopes

The first step is to add a new checkbox that informs the application that the additional scope is required. This checkbox will also render a dropdown for the user to select the calendar they want to add the event to. If the external account does not have the proper scopes, the application will trigger a reauthorization process with Google SSO to gain access to the proper scopes before saving the preferences to the database.

The following snippet is for the `CalendarSelector` component which contains the logic described above, with notable lines commented:

```tsx
'use client'

import { useState, useEffect } from 'react'
import { Calendar, Loader2 } from 'lucide-react'
import { getGoogleToken } from '../actions'
import { useUser } from '@clerk/nextjs'
import { useToast } from '@/hooks/use-toast'
import { saveCalendars } from '../_actions/save-calendars'
import { getCalendars } from '../_actions/get-calendars'
import { saveCalendarPreferences } from '../_actions/save-calendar-preferences'
import { getCalendarPreferences } from '../_actions/get-calendar-preferences'
import { RefreshButton } from './refresh-button'
import { CalendarItem } from './calendar-item'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'

interface GoogleCalendar {
  id: string
  summary: string
  primary: boolean
  selected: boolean
}

export function CalendarSelector() {
  const [isLoading, setIsLoading] = useState(false)
  const [calendars, setCalendars] = useState<GoogleCalendar[]>([])
  const [error, setError] = useState<string | null>(null)
  const [directBooking, setDirectBooking] = useState(false)
  const [selectedCalendarId, setSelectedCalendarId] = useState<string>('')
  const { user } = useUser()
  const { toast } = useToast()

  // If direct booking is disabled, clear the selected calendar
  useEffect(() => {
    if (!directBooking) {
      setSelectedCalendarId('')
    }
  }, [directBooking])

  // Fetch the user's calendars and calendar preferences when the component mounts
  useEffect(() => {
    async function loadCalendars() {
      try {
        const [savedSelections, savedPreferences] = await Promise.all([
          getCalendars(),
          getCalendarPreferences(),
        ])
        await fetchCalendars(savedSelections)
        setDirectBooking(savedPreferences.directBookingEnabled)
        setSelectedCalendarId(savedPreferences.directBookingCalendarId || '')
      } catch (error) {
        console.error('Error loading calendar settings:', error)
        // If getting saved selections fails, still try to load calendars
        await fetchCalendars()
      }
    }
    void loadCalendars()
  }, [user])

  // Fetch the user's calendars from the Google Calendar API
  const fetchCalendars = async (savedSelections?: { id: string; name: string }[]) => {
    setIsLoading(true)
    setError(null)
    try {
      const getGoogleTokenResponse = await getGoogleToken()
      if (!getGoogleTokenResponse?.token) {
        await reauthAccount(['https://www.googleapis.com/auth/calendar.readonly'])
        return
      }

      const response = await fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList', {
        headers: {
          Authorization: `Bearer ${getGoogleTokenResponse.token}`,
        },
      })

      if (!response.ok) {
        throw new Error('Failed to fetch calendars')
      }

      const data = await response.json()
      const formattedCalendars = data.items.map((calendar: any) => ({
        id: calendar.id,
        summary: calendar.summary,
        primary: calendar.primary || false,
        selected: savedSelections?.some((sel) => sel.id === calendar.id) || false,
      }))
      setCalendars(formattedCalendars)
    } catch (err) {
      console.error('Error fetching calendars:', err)
      setError(err instanceof Error ? err.message : 'Failed to fetch calendars')
    } finally {
      setIsLoading(false)
    }
  }

  // This function initiates a reauthorization flow for the user with the proper scopes
  async function reauthAccount(scopes: string[]) {
    if (user) {
      const googleAccount = user.externalAccounts.find((ea) => ea.provider === 'google')

      const reauth = await googleAccount?.reauthorize({
        redirectUrl: window.location.href,
        additionalScopes: scopes,
      })

      if (reauth?.verification?.externalVerificationRedirectURL) {
        window.location.href = reauth?.verification?.externalVerificationRedirectURL.href
      }
    }
  }

  // This function checks if the user has the required scopes and returns true if they do
  const checkScopes = () => {
    const googleAccount = user?.externalAccounts.find((ea) => ea.provider === 'google')
    if (!googleAccount) return false

    const requiredScopes = [
      'https://www.googleapis.com/auth/calendar.readonly',
      'https://www.googleapis.com/auth/calendar.events',
    ]
    const approvedScopes = googleAccount.approvedScopes?.split(' ')
    return requiredScopes.every((scope) => approvedScopes?.includes(scope))
  }

  // Save the calendars and calendar preferences to the database
  const handleSave = async () => {
    setIsLoading(true)
    setError(null)

    // Check if we need additional scopes for direct booking, and reauthorize if needed
    if (directBooking && !checkScopes()) {
      await reauthAccount([
        'https://www.googleapis.com/auth/calendar.readonly',
        'https://www.googleapis.com/auth/calendar.events',
      ])
      return
    }

    try {
      // Save the calendars and calendar preferences to the database
      await saveCalendars(calendars)
      await saveCalendarPreferences(directBooking, directBooking ? selectedCalendarId : null)
      toast({
        title: 'Success',
        description: 'Calendar settings have been saved.',
      })
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to save calendar settings')
      toast({
        title: 'Error',
        description: 'Failed to save calendar settings. Please try again.',
        variant: 'destructive',
      })
    } finally {
      setIsLoading(false)
    }
  }

  // When a calendar is toggled, update the state
  const handleToggleCalendar = (calendarId: string) => {
    setCalendars((prevCalendars) =>
      prevCalendars.map((cal) =>
        cal.id === calendarId ? { ...cal, selected: !cal.selected } : cal,
      ),
    )
  }

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-2">
          <Calendar className="h-5 w-5" />
          <h2 className="text-lg font-semibold">Google Calendar Selection</h2>
        </div>
        <RefreshButton onClick={() => fetchCalendars()} isLoading={isLoading} />
      </div>

      {/* If there's an error, show it */}
      {error && (
        <div className="rounded-md bg-red-50 p-4">
          <p className="text-sm text-red-600">{error}</p>
        </div>
      )}

      <div className="rounded-lg border p-4">
        {/* If loading, show a loading indicator */}
        {isLoading ? (
          <div className="flex justify-center py-8">
            <Loader2 className="h-6 w-6 animate-spin text-gray-400" />
          </div>
        ) : calendars.length === 0 ? (
          // If there are no calendars, show a message
          <div className="flex justify-center py-8 text-sm text-gray-600">
            No calendars found. Click refresh to try again.
          </div>
        ) : (
          // Render a list of the user's calendars which can be toggled
          <div className="space-y-4">
            <div className="space-y-2">
              {calendars.map((calendar) => (
                <CalendarItem
                  key={calendar.id}
                  id={calendar.id}
                  summary={calendar.summary}
                  primary={calendar.primary}
                  selected={calendar.selected}
                  onToggle={handleToggleCalendar}
                />
              ))}
            </div>

            <div className="space-y-4 border-t pt-4">
              {/* This checkbox allows the user to toggle direct booking */}
              <div className="flex items-center justify-between">
                <label className="flex items-center gap-2">
                  <input
                    type="checkbox"
                    checked={directBooking}
                    onChange={(e) => setDirectBooking(e.target.checked)}
                    className="h-4 w-4 rounded border-gray-300"
                  />
                  <span className="text-sm font-medium">Add bookings directly to calendar</span>
                </label>
              </div>

              {/* If direct booking is enabled, show a dropdown to select a calendar to book with */}
              {directBooking && (
                <div className="space-y-2">
                  <div className="flex items-center gap-2">
                    <Select value={selectedCalendarId} onValueChange={setSelectedCalendarId}>
                      <SelectTrigger className="w-full">
                        <SelectValue placeholder="Select a calendar" />
                      </SelectTrigger>
                      <SelectContent>
                        {calendars.map((calendar) => (
                          <SelectItem key={calendar.id} value={calendar.id}>
                            {calendar.summary}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  {directBooking && !selectedCalendarId && (
                    <p className="text-sm text-red-500">
                      Please select a calendar for direct bookings
                    </p>
                  )}
                </div>
              )}
            </div>

            <div className="flex justify-end pt-4">
              <button
                onClick={handleSave}
                disabled={isLoading || (directBooking && !selectedCalendarId)}
                className="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 disabled:opacity-50"
              >
                Save Changes
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  )
}
```

### Initiate a reauthorization by Clerk

Clerk has a helper function attached to every [`ExternalAccount`](/docs/references/javascript/external-account) object to trigger the reauthorization if needed. The `additionalScopes` can contain an array of scopes that will be added to the OAuth URL along with the global scopes set in the Clerk dashboard. This function will craft the required URL that the user needs to be directed to confirm access to the necessary resources:

```tsx
async function reauthAccount(scopes: string[]) {
  if (user) {
    const googleAccount = user.externalAccounts.find((ea) => ea.provider === 'google')

    const reauth = await googleAccount?.reauthorize({
      redirectUrl: window.location.href,
      additionalScopes: scopes,
    })

    if (reauth?.verification?.externalVerificationRedirectURL) {
      window.location.href = reauth?.verification?.externalVerificationRedirectURL.href
    }
  }
}
```

To prevent the user from having to reauthorize manually every time they sign in, we can store these required scopes with the Clerk User object in the [`publicMetadata`](/docs/users/metadata) and use that data for the following steps.

The following snippet is in the server action that handles saving the calendar preferences for the user, with `enabled` being the parameter for the function if `directBooking` is set:

```tsx
const user = await currentUser()
const client = await clerkClient()
const includesAdditionalScopes = user?.publicMetadata.additionalScopes?.includes(
  'https://www.googleapis.com/auth/calendar.events',
)

// Set public metadata
if (enabled && !includesAdditionalScopes) {
  await client.users.updateUserMetadata(userId, {
    publicMetadata: {
      additionalScopes: ['https://www.googleapis.com/auth/calendar.events'],
    },
  })
} else if (!enabled && includesAdditionalScopes) {
  await client.users.updateUserMetadata(userId, {
    publicMetadata: {
      additionalScopes: [],
    },
  })
}
```

### Add `publicMetadata` to the session claims

[When a user authenticates with Clerk](/blog/combining-the-benefits-of-session-tokens-and-jwts), they receive a JWT used to verify their identity on any subsequent requests to the server. The claims in this JWT can be modified to include the `publicMetadata` which includes the additional scopes required. This saves an extra trip to the Clerk API to check for this data, making the application more performant.

The [session claims](/glossary#claim) can be customized [in the Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions) under **Configure** > **Sessions** > **Customize session token**.

![Customize session claims in the Clerk dashboard](./image2.png)

### Automatically handle reauthorization if additional scopes are present

Since the claims are available to both the client and server and include the additional scopes, they can be checked on login to automatically handle reauthorization. Since the user has already approved the scopes, this appears as a simple redirect to Google and then back to the application with no user interaction required.

The following code outlines a `ReauthProvider` component that can wrap the application to handle all of this logic automatically, storing a flag in the browser’s `localStorage` to prevent it from occurring too frequently:

```tsx
'use client'
import { useUser } from '@clerk/nextjs'
import React, { useEffect, useState } from 'react'

const LOCAL_STORAGE_KEY = 'bookmate_isScopeCheckComplete'

interface Props {
  children: React.ReactNode
}

function ReauthProvider({ children }: Props) {
  const [isReauthStarted, setIsReauthStarted] = useState(false)
  const [isLoaded, setIsLoaded] = useState(false)
  const { isSignedIn, user } = useUser()

  useEffect(() => {
    if (isReauthStarted) {
      return
    }
    const isScopeCheckComplete = localStorage.getItem(LOCAL_STORAGE_KEY)

    // If the user is not signed in, remove the localStorage key
    // This should also trigger on logout
    if (!isSignedIn) {
      localStorage.removeItem(LOCAL_STORAGE_KEY)
      return
    }

    // If the flag is set, the scope check has already been completed
    if (isScopeCheckComplete) {
      setIsLoaded(true)
      return
    }

    // Check if additional scopes are required per the user metadata
    const requiredScopes = user?.publicMetadata?.additionalScopes
    if (!requiredScopes) {
      localStorage.setItem(LOCAL_STORAGE_KEY, 'true')
      setIsLoaded(true)
      return
    }

    // If the user is signed in and the scope check has not been completed, check the scopes
    const googleAccount = user?.externalAccounts.find((ea) => ea.provider === 'google')
    const approvedScopes = googleAccount?.approvedScopes?.split(' ')
    const hasAllRequiredScopes = requiredScopes?.every((scope) => approvedScopes?.includes(scope))

    // If the user does not have all required scopes, trigger reauth
    if (!hasAllRequiredScopes) {
      void reauthAcct(requiredScopes)
    } else {
      localStorage.setItem(LOCAL_STORAGE_KEY, 'true')
      setIsLoaded(true)
    }
  }, [user])

  async function reauthAcct(scopes: string[]) {
    setIsReauthStarted(true)

    if (user) {
      const googleAccount = user.externalAccounts.find((ea) => ea.provider === 'google')

      const reauth = await googleAccount?.reauthorize({
        redirectUrl: window.location.href,
        additionalScopes: scopes,
      })

      if (reauth?.verification?.externalVerificationRedirectURL) {
        window.location.href = reauth?.verification?.externalVerificationRedirectURL.href
      }
    }
  }

  if (!isSignedIn) {
    return children
  }

  if (!isLoaded) {
    return null
  }

  return <>{children}</>
}

export default ReauthProvider
```

The provider simply wraps the children in the root layout:

```tsx
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <ClerkProvider>
        <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
          <main className="min-h-screen bg-gradient-to-br from-blue-50 via-pink-50 to-yellow-50 text-gray-800">
            <ReauthProvider>{children}</ReauthProvider>
          </main>
          <Toaster />
        </body>
      </ClerkProvider>
    </html>
  )
}
```

From this point forward, the application will now have the proper rights to add events directly to the user's calendar if needed.

## Why is reauthorization required on every login?

When a user is using OAuth to sign into an application, a special URL is crafted that contains information about the application and the permissions it requires. If you are using a standard set of scopes for all users, those are configured in the Clerk dashboard and are included with the OAuth URL.

![Set default claims in the Clerk dashboard](./image3.png)

The access token stored with Clerk includes the scopes from the most recent authentication attempt. So when users who require additional scopes sign in via Clerk, their scopes will be reset to what’s defined for the entire application.

The reauthorization keeps the proper set of scopes configured at all times.

## Conclusion

You now understand how to implement least privilege access in a SaaS application using [Clerk's SSO](/docs/authentication/social-connections/overview). You've also learned how unique scopes per user can be stored and automatically reused throughout the application lifecycle. The approach provides granular access control and flexible permission management while minimizing potential security risks.

---

# Using Clerk SSO to access Google Calendar and other service data
URL: https://clerk.com/blog/using-clerk-sso-access-google-calendar.md
Date: 2024-12-06
Category: Guides
Description: Learn how to use Clerk to access data on behalf of the current user to request their availability from Google Calendar using an open-source, live demo application.

Allowing users to sign on with services like [Google](/blog/nextjs-google-authentication) and Apple is a staple in modern authentication, but [single sign-on (SSO)](/glossary#single-sign-on-sso) goes farther than that.

When a user signs in with an SSO provider, they receive a token that developers can use to determine who is making the request. With the proper configuration, developers can also access data within that service on behalf of the user. And while most developers know that Clerk can easily allow SSO with a wide range of providers, we also simplify the process involved in accessing user data.

In this article, you'll learn how to leverage Clerk to access data on behalf of the current user to request their availability from Google Calendar using an open-source, live demo application.

## Single sign-on and access tokens

When a user logs into an application using [OAuth](/glossary#oauth), a common protocol for modern SSO, access tokens are created and sent to the client.

Developers will typically store these [access tokens](/glossary#access-token) in a cookie or some other local storage mechanism. With each request to a backend system, the access token minted during the login process is sent along with the request. The system will verify the claims to ensure that the user making the request is who they say they are, and that the token is still valid.

As mentioned in the introduction of this article, access tokens can be used to request the user's data and verify their identity.

When using an SSO provider, developers can define *scopes* for what the specific access tokens minted have access to. Scopes help the service provider inform the user what data the developer is requesting access to so they can make an informed decision to permit or deny access to that data. For example, including the following scopes will enable access tokens minted by this application to read the calendar data for the current user:

- `https://www.googleapis.com/auth/calendar.readonly`
- `https://www.googleapis.com/auth/calendar.events.readonly`

When the user logs in, they will be presented with an access form like so:

![The Google OAuth consent screen](./image2.png)

It's also worth noting that access tokens are time-bound and expire after a certain period.

The login process will often send a longer-lived refresh token that can be used to refresh the access token transparently before it expires. This allows developers to enable users to sign in using their preferred method, all while transparently keeping the access tokens current.

When combined with the proper scopes, developers can use these tokens to verify the user's identity and access data within that service.

## Clerk helps with token management and data access

[Clerk enables SSO](/docs/authentication/configuration/sign-up-sign-in-options#social-connections-o-auth) with [many popular providers](/docs/authentication/social-connections/overview) with a simple toggle, but the capabilities go further than that.

When users log in using Clerk with an SSO provider, we store those tokens in our system and manage the lifecycle for developers. The access tokens are automatically kept current so you don't have to worry about them expiring and having to refresh them. We also provide simple functions to retrieve the access tokens, meaning your application can easily access data within that service on behalf of the user.

This allows you as the developer to focus on building functionality into your application instead of having to manage tokens, as we'll explore in the following demo.

To explore in detail how Clerk manages tokens, read the article “[Combining the benefits of session tokens and JWTs](/blog/combining-the-benefits-of-session-tokens-and-jwts)” on our blog!

## Demo: BookMate

BookMate is an open-source, publicly accessible, lightweight clone of popular booking services like [cal.com](http://cal.com) or [Calendly](https://calendly.com).

Users of the platform can create a profile, set their general availability, and share their profile for others to request meetings.

![The BookMate availability settings screen](./image3.png)

On top of setting general availability, users can also link their Google Calendar to the platform so that when someone tries to request time from them, BookMate will consider events that are already scheduled to avoid users from being double-booked.

![The BookMate calendar settings screen](./image4.png)

Clerk's `getUserOauthAccessToken` is used on the BookMate backend to make requests to Google on behalf of the user.

```ts {{ filename: 'src/app/settings/actions.ts' }}
'use server'
import { auth, clerkClient } from '@clerk/nextjs/server'

export async function getGoogleToken() {
  const { userId } = await auth()

  const client = await clerkClient()
  const token = await client.users.getUserOauthAccessToken(userId || '', 'oauth_google')

  return {
    token: token.data[0].token,
  }
}
```

This token is used to list the calendars the user wants to consider when a visitor tries to book, but also when the visitor tries to check the availability on the user's profile. Note that the following code snippet securely executes regardless of who's logged in:

```ts {{ filename: 'src/lib/google.ts' }}
const client = await clerkClient()
const token = await client.users.getUserOauthAccessToken(userId, 'oauth_google')
const accessToken = token.data[0].token

// Code removed for brevity.

const response = await fetch(
  `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
    calendarId,
  )}/events?timeMin=${monthStart.toISOString()}&timeMax=${monthEnd.toISOString()}&singleEvents=true&orderBy=startTime`,
  {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  },
)
```

To experience this app yourself, check it out at [https://bookmate-sigma.vercel.app](https://bookmate-sigma.vercel.app), or explore the [source code on GitHub](https://github.com/bmorrisondev/bookmate).

## Conclusion

In this article, we explored how Clerk simplifies the process of implementing SSO and accessing third-party services like Google Calendar. By managing token lifecycles and providing straightforward functions to retrieve access tokens, Clerk allows developers to focus on building features rather than dealing with authentication infrastructure.

The BookMate demo demonstrates how easy it is to integrate Google Calendar data into your application using Clerk's OAuth capabilities. With just a few lines of code, you can securely access user data from external services while maintaining a smooth user experience.

Whether you're building a scheduling application like BookMate or any other service that requires SSO integration, Clerk's token management and data access features can significantly reduce development complexity while maintaining security best practices.

---

# Streamline enterprise customer onboarding with SAML and Clerk
URL: https://clerk.com/blog/streamline-enterprise-onboarding-saml.md
Date: 2024-11-26
Category: Guides
Description: Learn how to automatically enroll new users into your SAML-enabled enterprise customers.

Enterprise customers can provide massive growth for your B2B applications, but they come with their own unique challenges.

Onboarding is one such challenge. With a potentially large user base that might consistently churn, quickly providing new users the access they need can be a huge win for your B2B applications. Combining [SAML](/glossary#security-assertion-markup-language-saml), a common enterprise single sign-on strategy, with verified domains can automate the entire process of onboarding users into your application, providing a delightful experience for those users as well as the enterprise's support department.

In this article, you’ll learn more about SAML and how verified domains can be configured to automatically enroll new enterprise users.

## What is SAML?

[Secure Assertion Markup Language](/docs/authentication/enterprise-connections/overview) (or SAML) is an [enterprise single sign-on standard](/docs/authentication/enterprise-connections/authentication-flows) enabling different systems to communicate securely.

It allows systems to share user details such as various attributes about the user, the groups they are members of, and supports [Just-in-Time (JIT) Provisioning](/docs/authentication/enterprise-connections/jit-provisioning). Admins can also easily map these attributes if they do not match between the two systems, making the system quite flexible.

When Clerk is configured with a SAML connection, users who log in using an email address associated with the connection will automatically be prompted to log in using the specified identity provider. If they log in using a social provider like Google or GitHub, Clerk will detect the email address through those flows as well and ask the user to authenticate with the enterprise connection.

SAML is commonly used in the enterprise space to streamline the onboarding experience for their users while reducing stress on IT and support.

## Automated enrollment with verified domains

Clerk [organizations](/docs/organizations/overview) provide a way for developers to empower their users to create and manage tenants within an application.

If one of your customers is a large corporation that uses SAML, you can further streamline the onboarding process for their new users by configuring a verified domain with an organization. With verified domains configured, users using an email address with that domain will automatically be invited to join an organization in your application with no further action from IT.

When combined with SAML connections, the users can simply sign up for your service using their work email, use the same credentials they use for other work apps, and immediately get access to the tenant within your application.

## SAML with verified domains in action

With a general understanding of how Clerk’s organizations and verified domains can help you and your SAML customers, let’s see how to configure it. This guide will cover how to configure SAML using Google Workspaces, but field names are relatively standard across any service that supports SAML.

Before following along, make sure you have the following:

- An active [Clerk account](https://dashboard.clerk.com/sign-up).
- A Google Workspace account.

If you want to follow along with this guide in video format, you can watch the video below:

### Configuring SAML

In the Clerk Dashboard sidebar, go to **User & authentication** > [**SSO Connections**](https://dashboard.clerk.com/last-active?path=user-authentication/sso-connections). Select **Add connection**, choose **For specific domains**, and then **Google Workspace**. Complete the form as follows:

- **Name**: An arbitrary name to identify the connection.
- **Domain**: The domain that will use this connection.

Click **Add connection** when you are done.

![The Create connection modal in the Clerk dashboard.](./image-0.png)

Next, access your Google Workspace Admin panel and navigate to **Apps** > [**Web and Mobile Apps**](https://admin.google.com/ac/apps/unified).

![The Google Web Console with Web and mobile apps highlighted.](./image-1.png)

Select **Add app**, then **Add custom SAML app**. Google will open a new view to walk you through configuring the app. You’ll be sharing some information between the Clerk Dashboard and Google.

![The Google Web Console with Add custom SAML app highlighted.](./image-2.png)

Populate the **App Name** with the name of your choice and select **Continue**.

![The Add custom SAML app view on step 1 with the name field populated.](./image-3.png)

Start by selecting **Download Metadata** which will download a file containing the configuration for the Google Workspaces App.

![The Add custom SAML app view on step 2 with Download Metadata highlighted.](./image-4.png)

Back in the Clerk Dashboard, locate the **Identity Provider Configuration** section of the Enterprise Connection you created earlier, select **Upload file**, and upload the file you downloaded from Google.

![The enterprise connection in the Clerk dashboard with the Identity Provider Configuration section highlighted.](./image-5.png)

Once the upload is finished, take note of the **ACS URL** and **Entity ID** values from the Clerk dashboard, you’ll need these for the next step.

![The enterprise connection in the Clerk dashboard with the ACS URL and Entity ID fields highlighted.](./image-6.png)

Back in the Google Admin Console, click **Continue** to move on to step 3 of the setup process if you have not already. Populate the **ACS URL** and **Entity ID** fields you obtained from the Clerk Dashboard.

![The Add custom SAML app view on Step 3 with the ACS URL and Entity ID fields populated.](./image-7.png)

Click **Continue** to move on to step 4. Now you’ll configure attribute mapping which essentially tells Google Workspaces which of its user attributes maps to the attributes in Clerk. Select **Add Mapping** > **Basic Information** > **Primary Email**. In the **App attributes** field, enter “*mail*” as the value.

![The Add custom SAML app view with the Primary email to mail attribute mapping highlighted](./image-8.png)

Select **Finish** to complete this part of the process. You should now be viewing the details screen of the app that was just created. The last step in the Google Admin Console is to enable this app for Workspace users. In the **User access** section, select **View details**.

![The Google Web Console showing User access highlighted in the app that was created.](./image-9.png)

Next, toggle **ON for everyone** and select **Save**.

![The Google Web Console showing the ON for everyone option highlighted in the Service status section.](./image-10.png)

This wraps up the work required in the Google Admin Console. The last step for configuring SAML is to enable the connection in Clerk.

Back in the Clerk dashboard, simply toggle the switch next to Enable connection and then select Save in the bubble that will appear from the bottom of the screen.

![The enterprise connection in the Clerk dashboard with the Enable connection toggle highlighted.](./image-11.png)

If you are stepping through this guide, you should now be able to authenticate using this SAML connection.

### Using verified domains within organizations

To enable automatic enrollment within your application, start by enabling organizations within the Clerk dashboard if you have not already done so. This can be done under **Organization Settings**. Toggle on the following settings then click **Save** at the bottom of the screen:

- **Unlimited membership**
- **Enable verified domains**
- **Automatic invitation**

![The Clerk dashboard Organization Settings with the Unlimited membership and Enable verified domains options highlighted.](./image-12.png)

With these settings enabled, organization admins can use the Organization configuration view provided by Clerk to configure a verified domain for their enterprise to add their domain and automatically invite new users. Admins can access these settings by using the dropdown provided by `<OrganizationSwitcher/>` and selecting the cog icon next to their organization.

![The OrganizationSwitcher dropdown with the cog icon highlighted.](./image-13.png)

This will open the Organization configuration modal. Verified domains can be added in the General view:

![The Organization configuration modal, in the General tab, with the Verified domains section highlighted.](./image-14.png)

Once a domain is added, admins can now specify their preferred enrollment settings:

![The Organization configuration modal with Automatic invitations highlighted.](./image-15.png)

## Conclusion

SAML is a common single sign-on strategy used by enterprises all over the world. Your application can be configured to streamline onboarding for your enterprise customers using SAML by enabling verified domains for the organizations belonging to those customers. The result is a simplified experience for your customers, their support teams, and their users!

---

# Clerk launches EASIE SSO and eliminates SSO fees
URL: https://clerk.com/blog/clerk-launches-easio-sso-and-drops-all-sso-fees.md
Date: 2024-11-20
Category: Engineering
Description: EASIE SSO brings Clerk’s signature simplicity to a notoriously agonizing corner of authentication.

Since we started Clerk, it’s been clear that enterprise SSO is a tremendous disappointment to users, developers, and IT admins alike. Despite being crucial to enterprise security, and despite the high fees usually associated with the feature, there has been a severe underinvestment in the experience for all stakeholders.

That’s why we’re absolutely delighted to share **EASIE SSO** with the world today. It’s enterprise SSO with a healthy dose of Clerk’s signature simplicity, baked right into our dashboard and `<SignIn/>` component. We're leveraging Google and Microsoft OIDC for their ease of setup, then adding a thin layer of enterprise SSO capabilities on top, including:

- Enrollment based on a user's email domain
- Mandatory sign-in through the enterprise's choice of Google or Microsoft OIDC
- Just-in-time provisioning during sign-in
- Automatic deprovisioning and session revocation

Plus, **we’re eliminating usage-based SSO connection fees** (previously $50/mo each) to make enterprise SSO more accessible than ever, including SAML SSO connections!

EASIE SSO can be enabled through the **SSO Connections** page of your dashboard: just enter the enterprise's email domain and choose if they should sign in with Google or Microsoft… Clerk will handle the rest!

## Why EASIE SSO?

SSO is vital for enterprise security, but existing solutions are complex and frustrating for all parties. We've met countless developers who try to mimic enterprise SSO behavior by forcing authentication through Google or Microsoft and then verifying the email domain. This is easier than “real SSO,” but often carries security oversights:

- Verifying the email domain alone doesn't confirm that the user signed in through the correct tenant, and can be exploited through a [tenant](https://trufflesecurity.com/blog/google-oauth-is-broken-sort-of) [swap](https://www.youtube.com/watch?v=ceeA3FmKxtM) attack
- When a user is deactivated through the Google or Microsoft admin console, their sessions are not revoked, so terminated teammates can continue accessing tools and services as long as they don’t sign out

**EASIE SSO** is a thin layer on top of OIDC that solves these security challenges, while retaining the simple setup of verifying users based on email domain. There is no need to manage per-enterprise credentials like SAML metadata files or one-off OIDC secrets.

The technical details and future plans for EASIE SSO are maintained on [easie.dev](https://easie.dev) as an open specification, and we encourage others to implement the strategy even if they are not using Clerk.

## The future of enterprise SSO

It’s clear that enterprise SSO has been sitting at a crossroads. While there is unanimous agreement of its benefits, the challenges associated have limited its adoption to only the most security-conscious and cost-insensitive enterprise software buyers.

Clerk believes in a future where the benefits of enterprise SSO are realized far more frequently, among both the largest and smallest businesses. We’re confident this future can be achieved, and we expect EASIE SSO and the elimination of SSO connection fees to be a leap forward in the right direction.

If you are also interested in creating this future, we would love to hear from you! To connect with the EASIE team, please email [easie@clerk.dev](mailto:easie@clerk.dev).

---

# How to secure Liveblocks Rooms with Clerk in Next.js
URL: https://clerk.com/blog/secure-liveblocks-rooms-clerk-nextjs.md
Date: 2024-11-19
Category: Guides
Description: Learn how to use Clerk user data to secure access to rooms in Liveblocks.

Real-time apps make life seamless, but without proper security, they can expose users’ most sensitive information in an instant.

Liveblocks is a platform that enables developers to build collaborative platforms faster with real-time APIs.  Liveblocks takes a component-driven approach to development, where wrapping areas of your application in one of the provided components will automatically add real-time interaction for your users. When coupled with Clerk, you can not only secure your Liveblocks-enabled application but also integrate user information for easy traceability.

In this article, you’ll learn how to configure Clerk and Liveblocks to ensure that only authorized users can access Liveblocks rooms.

## What is Liveblocks?

[Liveblocks](https://liveblocks.io) is a platform that enables developers to easily integrate collaborative features and custom real-time functionality within their applications.

Using one of their [pre-built components](https://liveblocks.io/docs/products/comments/default-components), you can easily add functionality like comments, mentions, notifications, live text editors, and multiplayer canvas collaboration. One of the core entities of Liveblocks is the Room, which is essentially an isolated, virtual space where users can collaborate. Each project contains multiple rooms for users to enter to collaborate with others currently active in that room.

When developing a solution with Liveblocks, [Rooms](https://liveblocks.io/docs/concepts/how-liveblocks-works#Rooms) will often be a one-to-one mapping with an entity in your application.

## How room security works

Rooms also offer a security boundary that can be configured to only allow access to users who should have access.

By default, everyone has access to a room based on the public key that’s embedded into the application. You can also request a security token whenever the room is being accessed. This is done using a specially crafted API endpoint that is called during the initialization process. Using this API endpoint for security provides an opportunity to check what the [current user](/docs/references/nextjs/current-user) should have access to.

Using a Next.js route, you can use Clerk to check the currently logged-in user and perform any necessary security checks to make sure the user has access to what they are requesting before issuing a security token.

## The Liveblocks authorization route

As mentioned in the previous section, you’ll need to create an API route in Next.js to use with Liveblocks.

For context, this article is built around the concept of a team-based task manager, where multiple people can share task lists using [organizations](/docs/organizations/overview) in Clerk. The following code snippet defines the route needed to perform the necessary checks, including:

- Check that the request is being made by an authenticated user.
- Check the database to ensure the task exists.
- Verify that the user is permitted to work with that task based on their ID or the active [organization ID](/docs/references/backend/organization/get-organization).
- Create the Liveblocks session token and include the necessary user details to display the user within Liveblocks components.

```ts {{ filename: 'src/api/liveblocks-auth/route.ts' }}
import { getOneTask } from '@/app/actions'
import { useSession } from '@clerk/nextjs'
import { auth, currentUser } from '@clerk/nextjs/server'
import { Liveblocks } from '@liveblocks/node'

export async function POST(req: Request) {
  // Use Clerk to get the session claims for the current user.
  // Return a 401 response if the claims are not present (the user is not logged in)
  const { sessionClaims } = auth()
  if (!sessionClaims) {
    return new Response('Not authorized', { status: 401 })
  }

  // Use Clerk to get more details about the current user.
  const user = await currentUser()
  if (!user) {
    return new Response('Not authorized', { status: 401 })
  }

  // Parse the task ID from the request and query it from the database
  const { room } = await req.json()
  const [task] = await getOneTask(+room)
  if (!task) {
    return new Response('Not authorized', { status: 401 })
  }

  // If the task was not created by the user or within the organization,
  // send back a 401
  if (task.owner_id !== user.id && task.owner_id !== sessionClaims?.org_id) {
    return new Response('Not authorized', { status: 401 })
  }

  // All security checks passed so create the session and include the name and
  // avatar of the user, which will be shown within Liveblocks components
  const liveblocks = new Liveblocks({
    secret: process.env.LIVEBLOCKS_SECRET_KEY as string,
  })
  const session = liveblocks.prepareSession(user.id, {
    userInfo: {
      name: user.fullName,
      avatar: user.imageUrl,
    },
  })
  session.allow(room, session.FULL_ACCESS)
  const { body, status } = await session.authorize()

  // Return the response
  return new Response(body, { status })
}
```

## Configure `LiveblocksProvider` to use the authorization route

With the auth API route configured, the next step is to tell Liveblocks to use it.

Luckily this is a relatively straightforward process. If you’ve followed the Liveblocks quickstart for Next.js, your `LiveblocksProvider` probably looks similar to this:

```tsx
function ChatRoom({ taskId, children }: { taskId: number; children: ReactNode }) {
  return (
    <LiveblocksProvider publicApiKey={'pk_dev_06dAgs...'}>
      <RoomProvider id={`${taskId}`}>
        <ClientSideSuspense fallback={<div>Loading…</div>}>{children}</ClientSideSuspense>
      </RoomProvider>
    </LiveblocksProvider>
  )
}
```

The only change we need to perform is to replace the `publicApiKey` prop with the `authEndpoint` prop and set the value of that prop to the API route you want to use:

```tsx
function ChatRoom({ taskId, children }: { taskId: number; children: ReactNode }) {
  return (
    <LiveblocksProvider authEndpoint="/api/liveblocks-auth">
      <RoomProvider id={`${taskId}`}>
        <ClientSideSuspense fallback={<div>Loading…</div>}>{children}</ClientSideSuspense>
      </RoomProvider>
    </LiveblocksProvider>
  )
}
```

And that’s it! From now on, Liveblocks will use the configured API route to create the security tokens used to access Liveblock room data.

## See it in action

The following animation demonstrates what to expect when this integration is properly implemented. Note how commenting on specific tasks also displays the user’s name and avatar, which is populated from the Clerk data when the security token is created:

The above demo is from an open-source project, which you can explore in more detail [on GitHub](https://github.com/bmorrisondev/team-todo-demo/tree/liveblocks-comments).

## Conclusion

After reading this article you now understand the Liveblocks authorization process and how Clerk user data can be used to verify the user has access to the room. You also understand how Clerk user data can enhance the Liveblocks session, leading to a seamless experience for your users.

---

# Securing Node.js Express APIs with Clerk and React
URL: https://clerk.com/blog/securing-node-express-apis-clerk-react.md
Date: 2024-09-26
Category: Guides
Description: In this guide, we will cover how to use Clerk with Express to authenticate API requests using ClerkExpressWithAuth() and ClerkExpressRequireAuth() middleware.

When calling multiple external APIs from an application using Clerk for authentication, you need to ensure that each API request is properly authenticated and secured.
Implementing authentication in Express or Node.js is crucial for ensuring your API's integrity, security, and reliable operation. This reassures you that your APIs are protected and functioning as intended.

In this tutorial, we'll explore how to use Clerk with Express to authenticate API requests using `ClerkExpressWithAuth()` and `ClerkExpressRequireAuth()` middleware, and build a secure and robust backend for your React application.

> This guide focuses on API security. For frontend React authentication, [learn more about our React support](/react-authentication).

![Diagram showing how multiple APIs can be authenticated with Clerk](./diagram.png)

## Prerequisites

To following along with this article, make sure you have the following:

- Create a [Clerk account](https://dashboard.clerk.com/sign-up).
- Create a Clerk application.
- [Node.js and npm](https://nodejs.org/en) installed on your computer.
- [Postman](https://postman.com) installed on your computer.

## Securing API Endpoints

To secure multiple external APIs with your application and Clerk, you can use Clerk's authentication middleware to protect your API endpoints. Clerk provides middleware that can be used to authenticate requests to your API endpoints. This middleware can be applied to routes that need authentication, ensuring that only authenticated users can access these endpoints.

For Express applications, Clerk offers two middleware options:

- `ClerkExpressWithAuth()`: This middleware attaches the authenticated user's session to the request object, allowing you to access user information in your route handlers.
- `ClerkExpressRequireAuth()`: This middleware ensures that only authenticated requests can access the protected routes and will throw an error if the request is not authenticated.

Let's explore how both middlewares can be used with Express.

## Create the API with Express

Start by opening an empty directory on your computer and initializing a new Node project with the following command:

```bash
npm init -y
```

Now open the `package.json` file and update it to add the `"type": "module"` setting as shown below:

```json {{ filename: 'package.json', ins: [6] }}
{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

Next, we need to install the following packages:

- `express` is the web framework for Node.js we're going to use. Express is a great option for running Node servers because it's fast and minimal.
- `cors` allows us to easily call the endpoint from a client
- `dotenv` is used to read environmental variables in Node.
- `@clerk/clerk-sdk-node` is the Node.js SDK for the Clerk user management platform.

These can be installed by running the following command in the your terminal:

```bash
npm install express cors dotenv @clerk/clerk-sdk-node
```

Once installation completes, find your  `CLERK_PUBLISHABLE_KEY` & `CLERK_SECRET_KEY` in the [Clerk dashboard](https://dashboard.clerk.com/) on the Quickstart screen if this is a new application, or in the **Configure** tab in the sidebar under **API keys**.

Because you are building these routes on the backend, it is safe to include the `CLERK_SECRET_KEY` in your environment variables.

To learn how to set environemt variables for your project, check out the [How to set environment variables in Node.js](/blog/how-to-set-environment-variables-in-nodejs) article.

Finally, create a file named `server.js` and add the following code to it. Note that both Clerk middleware functions are being used on different routes to test the behavior of each. Below those routes is an error-handling middleware to address any errors that are thrown. If an error occurs in any middleware function that is run before the `ClerkExpressRequireAuth` middleware, this function will be called.

```ts {{ filename: 'server.js' }}
import 'dotenv/config' // To read CLERK_SECRET_KEY and CLERK_PUBLISHABLE_KEY
import express from 'express'
import { ClerkExpressRequireAuth, ClerkExpressWithAuth } from '@clerk/clerk-sdk-node'
import cors from 'cors'

const port = process.env.PORT || 3000

const app = express()
app.use(cors())

// Use the strict middleware that throws when unauthenticated
app.get('/protected-auth-required', ClerkExpressRequireAuth(), (req, res) => {
  res.json(req.auth)
})

// Use the lax middleware that returns an empty auth object when unauthenticated
app.get('/protected-auth-optional', ClerkExpressWithAuth(), (req, res) => {
  res.json(req.auth)
})

// Error handling middleware function
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(401).send('Unauthenticated!')
})

// Route not utilizing any authentication
app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

Let's start the API server by running the following command in the terminal:

```bash
node server.js
```

You should now see that message from in your `app.listen(…)` terminal:

```
Example app listening at http://localhost:3000
```

## Testing the route with Postman

Postman is a tool that's used to dispatch requests to various endpoints and analyze the results. With Postman open, create a new request by selecting **File** > **New**. In the modal that appears, select **HTTP** as the request type.

In the URL field, type in `http://localhost:3000/protected-auth-required` and click Send. The results will be displayed in the Response tab like so:

![The response window from Postman showing a 401 status and an "Unauthenticated!" string in the response body.](./postman.png)

While accessing the `/protected-auth-required` route without authentication will return a 401 response as defined in the error handling middleware, accessing the `/protected-auth-optional` route will return an empty JSON object instead. Feel free to create a new request in Postman and send the request.

This is the expected response:

```json
{
  "sessionClaims": null,
  "sessionId": null,
  "session": null,
  "userId": null,
  "user": null,
  "actor": null,
  "orgId": null,
  "orgRole": null,
  "orgSlug": null,
  "organization": null,
  "claims": null
}
```

## Testing authentication with React

Let's create a React app so we can add Clerk to it and test the API endpoints after being authenticated. Open an empty directory in your terminal and run the following command to initialize a new React app:

```bash
npm create vite@latest
```

You'll be guided through a series of questions, use the following answers for each:

- Ok to proceed? (y) **y**
- Project name: **app**
- Select a framework: **React**
- Select a variant: **TypeScript**

Next, run the following command to switch directories, install the dependencies, and launch the project:

```bash
cd app
npm install
npm run dev
```

You should now be able to open your browser to `http://localhost:5173/` to access the React app.

### Set up Clerk

Next, follow the [Clerk React Quickstart Guide](/docs/quickstarts/react) to add Clerk to the app you just created. Once done, replace the code in `src/App.jsx` to render the `<SignInButton>` if the user is not signed in, or the `<UserButton>` if they are:

```tsx {{ filename: 'src/App.tsx' }}
import './App.css'
import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'

function App() {
  return (
    <>
      <p>Hello World!</p>
      <SignedOut>
        <SignInButton />
      </SignedOut>
      <SignedIn>
        <UserButton />
      </SignedIn>
    </>
  )
}

export default App
```

Now click the **Sign-in button** and test that you can sign-in using your preferred method. After fully authenticating, the Sign-in button will be replaced with your avatar, which is the `<UserButton>` component.

### Accessing the API via React

Next, we'll update `App.tsx` again with the following changes. This code will add the `useAuth` hook provided by the Clerk React SDK, which contains the `getToken` function used to obtain the JWT for the currently authenticated user.

That token will be used in the `Authorization` header of the `fetch` requests to our API, each of which can be called by clicking the appropriate button that is rendered in the browser. Finally, we're storing the responses of each call in a `data` state and simply displaying that as a string in the browser.

```tsx {{ filename: 'src/App.tsx', ins: [3, 4, [7, 30], [38, 42]], del: [2] }}
import './App.css'
import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'
import { SignInButton, SignedIn, SignedOut, UserButton, useAuth } from '@clerk/clerk-react'
import { useState } from 'react'

function App() {
  const { getToken } = useAuth()
  const [data, setData] = useState({})

  async function callProtectedAuthRequired() {
    const token = await getToken()
    const res = await fetch('http://localhost:3000/protected-auth-required', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
    const json = await res.json()
    setData(json)
  }

  async function callProtectedAuthOptional() {
    const token = await getToken()
    const res = await fetch('http://localhost:3000/protected-auth-optional', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
    const json = await res.json()
    setData(json)
  }

  return (
    <>
      <p>Hello World!</p>
      <SignedOut>
        <SignInButton />
      </SignedOut>
      <SignedIn>
        <UserButton />
        <button onClick={callProtectedAuthRequired}>Call /protected-auth-required</button>
        <button onClick={callProtectedAuthOptional}>Call /protected-auth-optional</button>
        <h1>Data from API:</h1>
        <p>{JSON.stringify(data, null, 2)}</p>
      </SignedIn>
    </>
  )
}

export default App
```

In the browser, click each of the newly rendered buttons to display a payload similar to the following JSON on the page:

```json
{
  "sessionClaims": {
    "azp": "http://localhost:5173",
    "exp": 1720558905,
    "iat": 1720558845,
    "iss": "https://boss-squid-85.clerk.accounts.dev",
    "nbf": 1720558835,
    "sid": "sess_2j1ZjdrAVrALRRTkn8ZFXHDQ7Qy",
    "sub": "user_2iQUovqEkfcRDScQrXvAIfGQgUn"
  },
  "sessionId": "sess_2j1ZjdrAVrALRRTkn8ZFXHDQ7Qy",
  "userId": "user_2iQUovqEkfcRDScQrXvAIfGQgUn",
  "claims": {
    "azp": "http://localhost:5173",
    "exp": 1720558905,
    "iat": 1720558845,
    "iss": "https://boss-squid-85.clerk.accounts.dev",
    "nbf": 1720558835,
    "sid": "sess_2j1ZjdrAVrALRRTkn8ZFXHDQ7Qy",
    "sub": "user_2iQUovqEkfcRDScQrXvAIfGQgUn"
  }
}
```

### Conclusion

Authentication should be swift and efficient to ensure it gets done, rather than sitting in your backlog for months or leaving your endpoints vulnerable to attackers. By using Clerk Middlewares, In this case, `ClerkExpressWithAuth()` or `ClerkExpressRequireAuth()`, you can secure any endpoint and integrate authentication with Express without the complexity of building it from scratch.

---

# Combining the benefits of session tokens and JWTs
URL: https://clerk.com/blog/combining-the-benefits-of-session-tokens-and-jwts.md
Date: 2024-09-11
Category: Engineering
Description: Learn about how leveraging the benefits of both session token authentication and JWTs results in the best of both approaches.

Neither session tokens nor JWTs alone are sufficient as an optimal authentication strategy.

Session token authentication has been in use for decades and is still widely implemented in modern applications, but can become a bottleneck when scaling an application. JWT authentication enables fast request validation, making it suitable for scaling distributed applications, yet struggles with session invalidation. While both approaches have their tradeoffs, the two methods can be combined to amplify their benefits and cancel out their drawbacks.

Let's explore these two authentication strategies, and discuss how rethinking the approach to JWTs can result in the best of both worlds.

## Session token authentication

Session token authentication functions by tracking the state of a signed-in user on the server using the concept of a “session”.

When a user signs into a system, the server will check their provided credentials against a database to validate the information. If correct, the server will create a session with a unique identifier along with information about the session such as the user's ID, the creation time, expiration time, session status, etc. These details will be tracked in persistent storage, such as a database, and the session identifier will be sent back to the client to be used with future requests.

The client will often store these session identifiers in browser cookies, which are small pieces of information that are automatically sent with every request that matches the domain for which they were created. When a web server receives a request that contains a cookie with a session identifier, the server will look up the session among the list of tracked sessions, ensure its validity, and tie the request to the user associated with the session.

This allows the server to identify *who* is making the request.

Because sessions are stored on the server, they can be easily invalidated as needed, resulting in subsequent requests being denied.

While traditionally not an issue with most small to moderately-sized applications, the latency associated with checking sessions on each request can become a problem as the application grows. Geographically distributed services are especially impacted based on the additional latency between parts of the system, more so than when utilizing a single database.

## JSON Web Token (JWT) authentication

JWT authentication is an alternative to session tokens that largely solves the latency issue by embedding session details directly into the object sent back to the client, instead of only an identifier.

### What are JWTs?

A JWT itself is an encoded version of a JavaScript object with a cryptographic signature attached to it to prevent tampering.

JWTs are composed of three parts: the header, the payload, and the signature. The **header** contains general information about the token type and the algorithm used for the signature. The **payload** is simply a JavaScript object that contains all of the information you need to transmit. The **signature** is the result of combining the header, payload, and a secret (typically the private key of an [asymmetrical key pair](https://www.cloudflare.com/learning/ssl/what-is-asymmetric-encryption/)) being [hashed](https://www.geeksforgeeks.org/what-is-hashing/) using the algorithm specified in the header.

Each of these three sections is base64 encoded and joined with a period (.) to obtain the final result.

![Combining The Benefits Of Session Tokens And Jwts guide illustration](./jwt.png)

Any modification to the JWT will make the signature invalid, allowing recipients of a valid JWT to trust the authenticity of the data it contains.

### How are JWTs used in authentication?

When a user signs into a system using JWT authentication, the server still needs to validate the credentials as with session token authentication. Instead of a session being created and tracked on the server, much of the information typically associated with a session (user ID, creation time, expiration time, etc.) is added to the payload of a JWT and sent back to the client.

When a request is received by the server, it will verify the signature of the JWT instead of querying a database for session information. If the signature is valid, the server can trust the details embedded into the JWT such as the user ID and expiration date.

JWT authentication solves the latency issues in distributed applications by removing the overhead associated with querying a database on each request, however, the system that creates the JWT has little control over it once it leaves the server. If a JWT is obtained by an unauthorized party after it's been created, that JWT can be used to impersonate the party for whom it was created with no way to invalidate it since the server trusts the combination of the payload and signature.

This is especially problematic considering most implementations of JWT authentication set the expiration value of JWTs as they would sessions. This results in JWTs created with long lifetimes, sometimes days or even weeks.

Ultimately, JWT authentication provides faster request validation with the tradeoff of being able to easily revoke JWTs. This shifts part of the trust associated with the authentication system away from the server since you need to trust that the client will not allow the JWT to leak.

## Using a hybrid approach for optimal results

So the big question now is how to get the speed of JWT authentication while still enabling the revocation ability of session token authentication.

The first step is to create JWTs with extremely short lifetimes that will be used to authenticate requests. While leaked JWTs can still be used for impersonation, the window of opportunity to utilize the JWT is so short that it is extremely unlikely that any meaningful action can be taken by attackers.

This change means that JWTs used for authentication will need to be created more frequently to keep the user signed in. At first glance, this may seem like a downside, but it creates an opportunity for the system to decide whether or not it *should* create a new JWT for that user.

To make that decision, the system creates sessions upon user sign-in that will be managed and tracked on the server. This part of the hybrid model follows the same approach as with session token authentication, but instead of using sessions to validate each request, the system will only use them to mint new JWTs prior to their expiration.

The state of the session (valid, expired, revoked, etc.) would be used to determine if the new JWT should be created or not. If a session is no longer valid, the request to renew the JWT will be denied, which allows the server to retain control over the user's ability to access the service.

### Implementing the hybrid model

Using this model, the client would receive a session identifier (as defined with session token authentication) as well as a JWT upon sign-in.

As requests are made to the server, the JWT is used to authenticate the request and grant the user access to that resource.

Before the JWT's short expiration window elapses, the session identifier is used to renew the authentication JWT before it expires to maintain access to the system. This renewal process would occur as a background task, outside of the typical interaction between the application and the server.

Clerk SDKs perform this operation in the browser by setting an interval that executes every minute to request a new JWT. This operation is performed in the background without any user interaction.

If a session is expired or invalidated on the server, the authentication system will no longer create a JWT, effectively signing the user out of the system.

By decoupling the session lifetime from the JWT used to authenticate requests and instead tracking it elsewhere, you can have a hybrid approach to authentication that combines the best of both JWT authentication and session token authentication.

## Conclusion

Both JWT and session token authentication have their tradeoffs. While it is common to debate between using one over the other, this assumes that the benefits of each are exclusive to their respective method.

By instead combining the perks of each approach, we can provide fast responses from JWTs while enabling developers and users to invalidate sessions.

---

# Build a task manager with Next.js, Supabase, and Clerk
URL: https://clerk.com/blog/nextjs-supabase-clerk.md
Date: 2024-09-06
Category: Guides
Description: Learn how to integrate Clerk with Supabase by building a task manager.

[Supabase](https://supabase.com/) is an open-source backend-as-a-service platform that provides Postgres databases, authentication, instant APIs, realtime data subscriptions, and more to help developers quickly build scalable applications.

[Integrating Supabase with Clerk](/blog/how-clerk-integrates-nextjs-supabase) gives you the benefits of using a Supabase database while leveraging [Clerk's Next.js authentication](/nextjs-authentication), prebuilt components, and webhooks.

To get the most out of Supabase with Clerk, you must implement custom [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) (RLS) policies. RLS works by validating database queries according to the restrictions defined in the RLS policies applied to the table. This guide will show you how to create RLS policies that restrict access to data based on the user's Clerk ID. This way, users can only access data that belongs to them. To set this up, you will:

- Create a function in Supabase to parse the Clerk user ID from the authentication token.
- Create a `user_id` column that defaults to the Clerk user's ID when new records are created.
- Create policies to restrict what data can be read, inserted, updated, and deleted.
- Use the Clerk Supabase integration helper in your code to authenticate with Supabase and execute queries.

This guide will have you create a new table in your [Supabase project](https://supabase.com/dashboard/projects), but once you've learned the concepts and the process, you can apply them to any existing table.

The source code for the final version can be found [here](https://github.com/clerk/clerk-supabase-nextjs).

> \[!WARNING]
> This guide is now outdated. For the latest information, see [our Supabase integration guide in the docs](/docs/integrations/databases/supabase).

## Project setup

### Clone the Clerk Next.js quickstart

To get started, clone the [Clerk Next.js quickstart](https://github.com/clerk/clerk-nextjs-app-quickstart) and install the dependencies:

```sh
git clone https://github.com/clerk/clerk-nextjs-app-quickstart
cd clerk-nextjs-app-quickstart
npm install
```

If you're wondering how this project was created, check out the [Next.js quickstart](https://github.com/clerk/clerk-nextjs-app-quickstart).

### Set up a Clerk project

If you do not have a Clerk account, [create one](https://clerk.com/sign-up) before proceeding. If you already have an account, [create a new project](https://dashboard.clerk.com/apps/new) for this guide.

Once you create an application, you'll be presented with the quickstarts. For this guide, follow the Next.js quickstart and only complete step 2, which instructs you to create the `.env.local` file and populate it with the necessary environment variables. The remaining steps are already completed as part of the quickstart repository that you cloned in the previous step.

Run your project with the following command:

```sh
npm run dev
```

Visit your app's homepage at [`http://localhost:3000`](http://localhost:3000). Sign up to create your first user and test everything works as expected.

### Set up a Supabase project

If you do not have a Supabase account, [create one](https://supabase.com/dashboard/sign-in) before proceeding. If you already have an account, [create a new project in the Supabase dashboard](https://supabase.com/dashboard/projects).

## Create a SQL query that checks the user's Clerk ID

Now that you've set up your project, it's time to get to work.

Create a function named `requesting_user_id()` that will parse the Clerk user ID from the authentication token. This function will be used to set the default value of `user_id` in a table and in the RLS policies to ensure the user can only access their data.

1. In the sidebar of your [Supabase dashboard](https://supabase.com/dashboard/projects), navigate to **SQL Editor**. This is where you will run all your SQL queries for the rest of this guide. Paste the following into the editor:
   ```sql
   CREATE OR REPLACE FUNCTION requesting_user_id()
   RETURNS TEXT AS $$
       SELECT NULLIF(
           current_setting('request.jwt.claims', true)::json->>'sub',
           ''
       )::text;
   $$ LANGUAGE SQL STABLE;
   ```
2. To execute the query and create the `requesting_user_id()` function, select **Run**. The results will be displayed in the **Results** tab, and should say "Success. No rows returned". Throughout this guide, keep an eye on this tab when running queries as it will display any errors that occur.

## Create a table and enable RLS on it

Next, you'll create a `"tasks"` table and enable RLS on that table. The `"tasks"` table will also contain a `user_id` column that will use the `requesting_user_id()` function you just created as its default value. **This column will be used in the RLS policies to only return or modify records scoped to the user's account.**

To create the `"tasks"` table and enable RLS on it, you'll run the following two queries. The first query creates the table and the second query enables RLS on the table.

Paste the following in the **SQL Editor** and select **Run**:

```sql
-- Create a "tasks" table
create table tasks(
  id serial primary key,
  name text not null,
  user_id text not null default requesting_user_id()
);

-- Enable RLS on the table
alter table "tasks" enable row level security;
```

If you want to see the table you just created, in the sidebar, select **Table Editor** and select the `"tasks"` table. You should see three empty columns: `id`, `name`, and `user_id`.

## Create ID-based RLS policies

Now, you need to create RLS policies that permit users to read, insert, update, and delete content associated with their user IDs only.

Paste the following in the **SQL Editor** and select **Run**:

```sql
-- This policy will enforce that only tasks where the `user_id` matches the Clerk user ID are returned.
CREATE POLICY "Select tasks policy" ON "public"."tasks" AS PERMISSIVE FOR
SELECT
  TO authenticated USING (requesting_user_id () = user_id);

-- This policy will enforce the user_id field on INSERT statements matches the Clerk user ID.
CREATE POLICY "Insert tasks policy" ON "public"."tasks" AS PERMISSIVE FOR INSERT TO authenticated
WITH
  CHECK (requesting_user_id () = user_id);

-- This policy will enforce that only tasks where the `user_id` matches the Clerk user ID can be updated.
CREATE POLICY "Update tasks policy" ON "public"."tasks" AS PERMISSIVE
FOR UPDATE
  TO authenticated USING (requesting_user_id () = user_id);

-- This policy will enforce that only tasks where the `user_id` matches the Clerk user ID can be deleted.
CREATE POLICY "Delete tasks policy" ON "public"."tasks" AS PERMISSIVE FOR DELETE TO authenticated USING (requesting_user_id () = user_id);
```

## Get your Supabase JWT secret key

Now that your table is set up and ready to be populated with data, it's time to set up your Clerk application and get Supabase integrated.

Get your Supabase JWT secret key:

1. In the sidebar, navigate to **Project Settings > API**.
2. Under the **JWT Settings** section, save the value in the **JWT Secret** field somewhere secure. This value will be used in the next step.

## Create a Supabase JWT template

When sending requests to Supabase, Supabase needs to verify that the user is who they say they are. Because you're using Clerk to authenticate your users, you need Clerk to tell Supabase who the user is. This is where JWTs come in. For each authenticated user, Clerk issues JWTs that contain information about the user, like their ID. You're going to create a custom template for a JWT that Supabase can use.

To create a JWT template for Supabase:

1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=jwt-templates).
2. In the navigation sidebar, select **JWT Templates**.
3. Select the **New template** button, then select **Supabase** from the list of options.
4. Configure your template:
   - The value of the **Name** field will be required when using the template in your code. For this tutorial, name it `supabase`.
   - **Signing algorithm** will be `HS256` by default. This algorithm is required to use JWTs with Supabase. [Learn more in their docs](https://supabase.com/docs/guides/resources/glossary#jwt-signing-secret).
   - Under **Signing key**, add the value of your Supabase **JWT secret key** from [the previous step](#get-your-supabase-jwt-secret-key).
   - You can leave all other fields at their default settings or customize them to your needs. See the [JWT template guide](/docs/backend-requests/making/jwt-templates#creating-a-template) to learn more about these settings.
   - Select **Save** from the notification bubble to complete setup.

## Install the Supabase client library

Add the Supabase client library to your project by running the following command in your terminal:

```bash {{ filename: 'terminal' }}
npm i @supabase/supabase-js
```

## Set up your environment variables

Add the Supabase project URL and key to your `.env.local` file.

1. In the sidebar of the [Supabase dashboard](https://supabase.com/dashboard/projects), select **Settings** > **API**.
2. Add the **Project URL** to your `.env.local` file as `SUPABASE_URL`.
3. In the **Project API keys** section, add the value beside `anon` `public` to your `.env.local` file as `SUPABASE_KEY`.

> \[!IMPORTANT]
> If you are using Next.js, the `NEXT_PUBLIC_` prefix is required for environment variables that are used in the client-side code.

## Fetch Supabase data in your code

Let's get to coding!

You want to load a list of tasks for the user and allow the user to add new tasks, mark tasks as complete, and delete tasks. You can do this either on the client-side or server-side.

### Client-side rendering (CSR)

The following example demonstrates how to fetch data from Supabase in a client-side rendered page.

The `createClerkSupabaseClient()` function uses [Supabase's `createClient()` method](https://supabase.com/docs/reference/javascript/initializing) to initialize a new Supabase client, but modifies it to inject the Clerk token you [created with the Supabase JWT template](#create-a-supabase-jwt-template) into the request headers sent to Supabase. The `requesting_user_id()` function that was created in the Supabase dashboard will parse the user ID from the Clerk token and use it when querying data from the `tasks` table.

Paste the following code into the `app/page.tsx` file:

```tsx {{ filename: 'app/page.tsx' }}
'use client'
import { useEffect, useState } from 'react'
import { useSession, useUser } from '@clerk/nextjs'
import { createClient } from '@supabase/supabase-js'

export default function Home() {
  const [tasks, setTasks] = useState<any[]>([])
  const [loading, setLoading] = useState(true)
  const [name, setName] = useState('')
  // The `useUser()` hook will be used to ensure that Clerk has loaded data about the logged in user
  const { user } = useUser()
  // The `useSession()` hook will be used to get the Clerk session object
  const { session } = useSession()

  // Create a custom supabase client that injects the Clerk Supabase token into the request headers
  function createClerkSupabaseClient() {
    return createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_KEY!,
      {
        global: {
          // Get the custom Supabase token from Clerk
          fetch: async (url, options = {}) => {
            const clerkToken = await session?.getToken({
              template: 'supabase',
            })

            // Insert the Clerk Supabase token into the headers
            const headers = new Headers(options?.headers)
            headers.set('Authorization', `Bearer ${clerkToken}`)

            // Now call the default fetch
            return fetch(url, {
              ...options,
              headers,
            })
          },
        },
      },
    )
  }

  // Create a `client` object for accessing Supabase data using the Clerk token
  const client = createClerkSupabaseClient()

  // This `useEffect` will wait for the User object to be loaded before requesting
  // the tasks for the signed in user
  useEffect(() => {
    if (!user) return

    async function loadTasks() {
      setLoading(true)
      const { data, error } = await client.from('tasks').select()
      if (!error) setTasks(data)
      setLoading(false)
    }

    loadTasks()
  }, [user])

  // Add a task into the "tasks" database
  async function createTask(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    await client.from('tasks').insert({
      name,
    })
    window.location.reload()
  }

  // Update a task when its completed
  async function onCheckClicked(taskId: number, isDone: boolean) {
    await client
      .from('tasks')
      .update({
        is_done: isDone,
      })
      .eq('id', taskId)
    window.location.reload()
  }

  // Delete a task from the database
  async function deleteTask(taskId: number) {
    await client.from('tasks').delete().eq('id', taskId)
    window.location.reload()
  }

  return (
    <div>
      <h1>Tasks</h1>

      {loading && <p>Loading...</p>}

      {!loading &&
        tasks.length > 0 &&
        tasks.map((task: any) => (
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
            <input
              type="checkbox"
              checked={task.is_done}
              onChange={(e) => onCheckClicked(task.id, e.target.checked)}
            />
            <p>{task.name}</p>
            <button onClick={() => deleteTask(task.id)}>Delete</button>
          </div>
        ))}

      {!loading && tasks.length === 0 && <p>No tasks found</p>}

      <form onSubmit={createTask}>
        <input
          autoFocus
          type="text"
          name="name"
          placeholder="Enter new task"
          onChange={(e) => setName(e.target.value)}
          value={name}
        />
        <button type="submit">Add</button>
      </form>
    </div>
  )
}
```

## Server-side rendering (SSR)

The following example demonstrates how to fetch data from Supabase in a server-side rendered page. It requires creating multiple files as you will use [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) to handle adding, deleting, and updating tasks.

The `createClerkSupabaseClientSsr()` function uses [Supabase's `createClient()` method](https://supabase.com/docs/reference/javascript/initializing) to initialize a new Supabase client, but modifies it to inject the Clerk token you [created with the Supabase JWT template](#create-a-supabase-jwt-template) into the request headers sent to Supabase. The `requesting_user_id()` function that was created in the Supabase dashboard will parse the user ID from the Clerk token and use it when querying data from the `tasks` table.

It is stored in a separate file so that it can be reused in multiple places, such as in both your `page.tsx` and your Server Action file.

Create the `src/app/ssr/client.ts` file and paste the following code into it:

```ts {{ filename: 'src/app/ssr/client.ts' }}
import { auth } from '@clerk/nextjs/server'
import { createClient } from '@supabase/supabase-js'

export function createClerkSupabaseClientSsr() {
  // The `useAuth()` hook is used to access the `getToken()` method
  const { getToken } = auth()

  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_KEY!,
    {
      global: {
        // Get the custom Supabase token from Clerk
        fetch: async (url, options = {}) => {
          const clerkToken = await getToken({
            template: 'supabase',
          })

          // Insert the Clerk Supabase token into the headers
          const headers = new Headers(options?.headers)
          headers.set('Authorization', `Bearer ${clerkToken}`)

          // Now call the default fetch
          return fetch(url, {
            ...options,
            headers,
          })
        },
      },
    },
  )
}
```

Create the `src/app/ssr/page.tsx` file and paste the following code into it:

```tsx {{ filename: 'src/app/ssr/page.tsx' }}
import AddTaskForm from './AddTaskForm'
import { createClerkSupabaseClientSsr } from './client'
import TaskRow from './TaskRow'

export default async function Home() {
  // Use the custom Supabase client you created
  const client = createClerkSupabaseClientSsr()

  // Query the 'tasks' table to render the list of tasks
  const { data, error } = await client.from('tasks').select()
  if (error) {
    throw error
  }
  const tasks = data

  return (
    <div>
      <h1>Tasks</h1>

      <div>
        {tasks?.map((task: any) => (
          <TaskRow key={task.id} task={task} />
        ))}
      </div>

      <AddTaskForm />
    </div>
  )
}
```

Create a `src/app/ssr/actions.ts` file which will include [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) for adding, deleting, and updating tasks. Paste the following code into that file:

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

import { createClerkSupabaseClientSsr } from './client'

const client = createClerkSupabaseClientSsr()

export async function addTask(name: string) {
  try {
    const response = await client.from('tasks').insert({
      name,
    })
    console.log('Task successfully added!', response)
  } catch (error: any) {
    console.error('Error adding task:', error.message)
    throw new Error('Failed to add task')
  }
}

export async function deleteTask(taskId: number) {
  try {
    const response = await client.from('tasks').delete().eq('id', taskId)
    console.log('Task successfully deleted!', response)
  } catch (error: any) {
    console.error('Error deleting task:', error.message)
    throw new Error('Failed to delete task')
  }
}

export async function setTaskState(taskId: number, isDone: boolean) {
  try {
    const response = await await client
      .from('tasks')
      .update({
        is_done: isDone,
      })
      .eq('id', taskId)
    console.log('Task successfully updated!', response)
  } catch (error: any) {
    console.error('Error updating task:', error.message)
    throw new Error('Failed to update task')
  }
}
```

Your form for adding tasks will use the `addTask()` Server Action that you created in the previous file. The form is in a separate file than your `page.tsx` file because it must be a client component. Create the `src/app/ssr/AddTaskForm.tsx` file and paste the following code into it:

```ts {{ filename: 'src/app/ssr/AddTaskForm.tsx' }}
'use client'
import React, { useState } from 'react'
import { addTask } from './actions'
import { useRouter } from 'next/navigation'

function AddTaskForm() {
  const [taskName, setTaskName] = useState('')
  const router = useRouter()

  async function onSubmit() {
    await addTask(taskName)
    setTaskName('')
    router.refresh()
  }

  return (
    <form action={onSubmit}>
      <input
        autoFocus
        type="text"
        name="name"
        placeholder="Enter new task"
        onChange={(e) => setTaskName(e.target.value)}
        value={taskName}
      />
      <button type="submit">Add</button>
    </form>
  )
}
export default AddTaskForm
```

Create the `src/app/ssr/TaskRow.tsx` file which will display the tasks in your list. It's a separate file from your `page.tsx` because it uses the `useRouter()` hook, so it must be a client component. Paste the following code into that file:

```ts {{ filename: 'src/app/ssr/TaskRow.tsx' }}
// This must be a separate file because useRouter() can't be used in server components
'use client'

import { useRouter } from 'next/navigation'
import { deleteTask, setTaskState } from './actions'

export default function TaskRow({ task }: { task: any }) {
  const router = useRouter()

  async function onCheckClicked(taskId: number, isDone: boolean) {
    // Update a task when its completed
    await setTaskState(taskId, isDone)
    router.refresh()
  }

  async function onDeleteClicked(taskId: number) {
    // Delete a task from the database
    await deleteTask(taskId)
    router.refresh()
  }

  return (
    <div key={task.id}>
      <p>{task.name}</p>
      <input
        type="checkbox"
        checked={task.is_done}
        onChange={(e) => onCheckClicked(task.id, e.target.checked)}
      />
      <button onClick={() => onDeleteClicked(task.id)}>Delete</button>
    </div>
  )
}
```

### Test your integration

Now it's time to test your code.

Run your project with the following command:

```sh
npm run dev
```

Sign in and test viewing, creating, updating, and deleting tasks. Sign out and sign in as a different user, and repeat.

If you have the same tasks across multiple accounts, double-check that RLS is enabled, or that the RLS policies were properly created. Check the table in the Supabase dashboard. You should see all the tasks between both users but with differing values in the `user_id` column.

---

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

---

# What are passkeys and how do they work?
URL: https://clerk.com/blog/what-are-passkeys.md
Date: 2024-04-22
Category: Engineering
Description: Passkeys allow for a simple, yet extremely secure, sign-in experience. Learn more about what they are and how they work in this article.

Passkeys are a big step towards a passwordless future.

Allowing users the ability to sign in with their device or biometric data, passkeys provide a streamlined and convenient way of logging into supported services. And while convenient, they are also very secure thanks to the underlying tech that makes it possible: public-key cryptography. Interested in learning more about passkeys and how they work?

In this article, we’ll discuss what passkeys are, as well as some of the benefits they provide.

## What are passkeys?

Passkeys are a method of signing into websites and services using cryptographically generated keys which are often stored on physical devices such as hardware tokens and modern smartphones.

When a passkey is created for a user account, the device that is being registered for the passkey will generate two keys: a public and a private key. The public key is sent to the service where the passkey is being registered, and the private key is stored securely on the device.

These keys are then used to securely sign into the service where the passkey is registered.

When the user tries to sign in using a passkey, the client will request a challenge from the server. The client then signs this challenge with its private key and sends it to the server. Once received, the server checks that the signature is valid using the public key it has.

Provided the signature is valid, the user is then signed into the service without having to enter any information manually.

![A sign in user diagram with passkeys.](./passkeys-signin-diagram.png)

1. User device requests a challenge from the server.
2. Server sends a challenge back, which goes to the authenticator
3. The authenticator checks with the user that they are trying to sign in.
4. On confirmation, the authenticator signs the challenge using the private key and sends it to the server
5. The server validates the signature with the user’s public key
6. If valid, the server completes the request and the user is signed in

The use of passkeys creates a passwordless experience that is both convenient and secure for the user signing into the service.

## How does public key crypto work?

To understand the magic behind passkeys, it's worth understanding how public-key cryptography works.

Public-key cryptography works by using a pair of keys in order to securely store, sign, or transmit data. The private key can be used to encrypt or decrypt data, as well as generate a signature based on the data. The private key should always be stored securely since anyone who has the private key can also sign and decrypt data.

The public key can be used only to encrypt data, or to validate a signature from the private key. The public key can be distributed without risk as its primary utility is to secure data before sending it to a destination that has the private key.

Because this system uses two different keys, it is also referred to as asymmetric cryptography.

## A practical use of public-key cryptography

While passkeys are relatively new, public-key cryptography has been around since the 1970s and has a wide number of applications and use cases. One such application that demonstrates these concepts well is end-to-end encrypted messaging.

Let’s say Ava and Brent use a secure chat app to communicate regularly. They each have a set of public and private keys. When they first connected, Ava would have sent her public key to Brent, and Brent would have sent his key to Ava.

![A diagram showing how public keys are exchanged between Ava and Brent.](./chat-key-exchange.png)

When Ava sends a message to Brent, her device encrypts the message using Brent's public key and the encrypted data is transmitted over the internet to Brent. When Brent receives the message, his device would use his private key to decrypt it before displaying it.

![A diagram showing how messages are encrypted and decrypted between Ava and Brent.](./chat-encrypted-message.png)

Now let’s say Charlotte tries to intercept the messages Ava is sending to Brent. Because the messages are encrypted and Charlotte does not have Brent's private key, the data being received is unreadable.

![A diagram showing how Charlotte is unable to read the encrypted messages.](./chat-mitm.png)

Even though this example uses a chat application to demonstrate how public and private keys work together, the same can be applied to the login process for passkeys.

![A diagram showing a public key being sent to a server and a private key being stored on a device.](./client-server-key-exchange.png)

## How are passkeys more secure than a username and password?

Passkeys are more secure than using a traditional username and password for a few reasons.

### Convenience

There is often an indirect relationship between how secure something is and how convenient it is. Passkeys however have the benefit of being convenient for users to take advantage of by providing a very streamlined experience to signing in to a service.

People tend to gravitate towards solutions that are easier to use. Combining the high security of public key crypto with the simplicity of just signing in by tapping a button, users of passkeys get the best of both worlds.

### Password reuse

Reusing passwords across services is a bad security practice. If one password gets compromised, whether it's your fault or theirs, any service where that password is also used should now be considered compromised as well. This can be a nightmare if you use the same password across dozens of services!

With passkeys, each service automatically gets a unique key pair. This means that if one service does lose control of its secrets, any other accounts you have that use passkeys are still secure, eliminating the work needed to cycle your passwords in the event of a breach.

### Credential strength

One strategy for hacking an account is by repeatedly guessing a password until the correct one is discovered. This is known as brute-forcing, and password length is often the biggest determining factor on how realistic it is to brute-force a password. The longer the password, the less chance it can be guessed.

A 16-character/byte password is often considered very secure, but passkeys are approximately 100-1400 bytes [according to the Electronic Frontier Foundation](https://www.eff.org/deeplinks/2023/10/what-passkey), which means they are nearly impossible to determine with this approach.

### Built-in MFA

Multifactor authentication (or MFA) is the use of multiple methods to verify that the user attempting to sign in is who they claim to be. Common factors used are something you know, something you have, or something you are.

For instance, if you sign into a service with a username and password, those are considered a single factor since you know both of those pieces of data (and one is often public). Adding an authenticator app or hardware token adds an additional factor since it utilizes something you have.

With Passkeys, MFA is built in. The device (along with the public/private key pair it generates) is one factor, and since biometric data is often used to unlock the device, that's a second factor.

### Eliminates phishing attempts

Phishing is where an attacker creates a look-alike website and attempts to trick users into entering their credentials. For example, a user might receive an email that appears to be from Amazon stating they are going to place an order that was never approved and telling the user they can manage it using a link embedded in the email. Instead of this link going to "amazon.com", it may go to "anazon.com" or another similar domain.

Since the domain looks close enough to the real one, the user may click on it and enter their credentials, which are then stolen by the attacker.

Since the passkeys generated on a device are specific to the domain for which they were created, this removes phishing as an attack vector. If an attacker does mimic a website and tries to get the user to log in, the device will understand that the domain is incorrect and will not offer the option to sign in. This also includes domain names that contain non-standard characters that appear to look the same.

## Clerk supports passkeys

Passkeys are an officially supported authentication method for services secured with Clerk.

You can enable passkeys just like you would any other login method for your users.

![The dashboard showing the passkey option in the authentication settings.](./dashboard-passkeys.png)

Once enabled, your users can add passkeys through their User Profile.

![The User Profile showing the option to add a passkey.](./add-passkey.png)

When signing in, they’ll be presented with the option to use a passkey.

![The sign in modal showing the option to sign in with a passkey.](./signin-passkeys.png)

For more information on using passkeys with Clerk, check out [the passkeys overview in our docs](/docs/authentication/configuration/sign-up-sign-in-options#passkeys).

---

# Comparing Authentication in React.js vs. Next.js
URL: https://clerk.com/blog/comparing-authentication-react-nextjs.md
Date: 2024-03-15
Category: Company
Description: We compare authentication in React.js and Next.js, emphasizing the ease of securing user data with Clerk.

Authentication in React and Next.js is a critical topic. This tutorial will compare authentication in [React.js](/react-authentication), known for dynamic interfaces, and [Next.js](/nextjs-authentication), a framework that optimizes React for production. We'll cover the importance of authentication for securing user data and access to protected components, and dive into the practical differences between using React and Next.js for this purpose.

Additionally, we'll discuss how Clerk can simplify the authentication process with framework-specific solutions for both [React](/react-authentication) and [Next.js](/nextjs-authentication). For practical experience, we've provided a [code repository on GitHub](https://github.com/theodesp/react-vs-nextjs-auth) with examples to deepen your understanding of authentication in React and Next.js.

Let's get started.

## Core Differences between React and Next.js

Let's delve into the real differences between React and Next.js when it comes to authentication and how they affect our decision-making process.

### Server-side rendering vs client-side rendering

React.js is a library for creating user interfaces on web and native platforms, as described in its official documentation. It's primarily a client-side tool for developing UI applications, where UI refers to the interface on the user's device or browser for interacting with websites or applications.

React.js doesn't have built-in server-side rendering capabilities, limiting its function to client-side rendering. However, future updates, such as React Server Component, aim to improve server-side support, though this involves complex nuances.

On the other hand, Next.js is a React framework that enhances React.js with additional features and capabilities. Next.js is a full-stack framework with features including:

- Server-side Rendering (SSR): Where pages are generated on the server and sent to the client.
- React Server Component support (RSC)
- Routing: A Built-in routing system.
- Static Export (SSG): Pre-rendering pages at build time to serve static HTML.
- Automatic Build Size Optimization: Optimizes build sizes automatically.
- Incremental Static Regeneration: Re-rendering static pages on-demand without rebuilding the entire site.

Even when utilizing Next.js, which facilitates server-side rendering with React, developers encounter restrictions on certain authentication operations. We'll expand deeper into these limitations as we examine the actual code examples.

### React Routing capabilities

React.js does not handle page routing for you. It’s up to the developer to choose how to navigate through pages or redirects in a concise manner. That's why developers opt-in to use a third party tool like [React-router](https://reactrouter.com/en/main) or [TanStack Router](https://tanstack.com/router/latest) to satisfy their needs.

Next.js on the other hand has routing and it’s fixed or built-in with no option to override it at least on the server side. As a matter of fact, Next.js currently supports two routing methods:

**Pages** creates routes within the pages folder.

**App Router** organizes routes within the app folder.

Although both routing options can be used together, they fundamentally work differently so they are not compatible with each other without certain modifications.

## Example: How to Implement Authentication in React

We now take a closer look on how to Implement Authentication in React from scratch. You can work your way through this section of the tutorial with the code examples as located in the react-auth folder.

### Overview of client-side authentication

Focusing on React as a client-side library, our overview will center on creating authentication components like login and logout forms and managing the visibility of sensitive information.

Client-side authentication primarily involves showing or hiding content based on a user's authentication status. Unauthenticated users are redirected to a [login page](/blog/building-a-react-login-page-template) to enter credentials for accessing protected routes. This common approach in applications is standard and typically trouble-free.

However, it involves intricate technicalities that are not apparent to the end user. Developers must handle authentication securely and efficiently on the client side, including in React applications. We will outline key considerations for client-side authentication before diving into coding specifics.

### Considerations for client-side authentication

When adopting authentication solutions for your client-side app, the first step is defining criteria for identifying users. Additionally, you need to consider the following:

**Security Concerns**

To ensure security, it's essential to manage the generation, storage, and retrieval of Personally Identifiable Information (PII), session identifiers, or tokens that could be misused for user impersonation. Given that sensitive information is handled client-side, it's critical to mitigate potential security risks effectively.

**State Management**

If you store session keys in the user's device or browser, you must establish how to manage them in the application's state. This involves determining the appropriate data structure for storing authentication tokens or session information, implementing mechanisms to synchronize state changes across components, and handling authentication-related events such as login and logout actions.

In many cases, React should be used as a thin client aiming to retain minimal data. The responsibility for managing session revalidation or token updates, in accordance with the security protocols of the organization, is typically delegated to the server, which instructs the client accordingly.

Considering the above parameters, let's explore in practice how to implement these considerations in React and how easy it is to miss important details. The code for the whole tutorial section is located in the `react-auth` folder of the repo.

### Example of setting up authentication in React

Let’s show now how to set up authentication in React. We first initiate a new React Project using [Vite](https://vitejs.dev) since `create-react-app` is deprecated:

```bash
$ npm init @vitejs/app react-auth --template react-ts
$ cd react-auth
```

#### Step 1: Setup the main App Router page

We'll use React Router to manage navigation within our application. If you haven't already installed React Router, you can do so using npm:

```bash
$ npm install react-router-dom
```

Next, let's define the main application router in our `App.tsx` file:

```tsx {{ title: 'App.tsx' }}
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import Login from './Login'
import Profile from './Profile'
import AuthProvider from './providers/AuthProvider'
import Protected from './components/Protected'

function App() {
  return (
    <div className="App">
      <Router>
        <AuthProvider>
          <Routes>
            <Route path="/login" element={<Login />} />
            <Route element={<Protected />}>
              <Route path="/profile" element={<Profile />} />
            </Route>
          </Routes>
        </AuthProvider>
      </Router>
    </div>
  )
}
export default App
```

Inside the `<AuthProvider />` component, we define routes for the login page (`/login`) and the user profile page (`/profile`). The `<Protected/>` component ensures that the `/profile` route is only accessible to authenticated users. If a user tries to access the profile page without authentication, they will be redirected to the login page.

Since we haven’t defined what the `AuthProvider`, `Protected` or `Login` components do yet let's proceed to explore the client side components next.

#### Step 2: Define the Login Component

Let's examine the structure and style of the login page component, which includes a form for users to input their credentials for authentication.:

```tsx {{ title: 'Login.tsx' }}
import React, { useState } from 'react'
import { useAuth } from './hooks/useAuth'
import './Login.css'
const Login: React.FC = () => {
  const [credentials, setCredentials] = useState({ username: '', password: '' })
  const auth = useAuth()
  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (formValid(credentials)) {
      await auth.loginAction(credentials)
    }
  }

  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setCredentials((prev) => ({
      ...prev,
      [name]: value,
    }))
  }

  const formValid = (state: typeof credentials) => {
    return state.username && state.password
  }

  return (
    <div className="login-container">
      <form onSubmit={handleLogin} className="login-form">
        <input
          type="text"
          placeholder="Username"
          name="username"
          value={credentials.username}
          onChange={handleInput}
        />
        <input
          type="password"
          placeholder="Password"
          name="password"
          value={credentials.password}
          onChange={handleInput}
        />
        <button className="login-button" type="submit">
          Login
        </button>
      </form>
    </div>
  )
}
export default Login
```

In the Login Page, we use  the `useState` hook to manage the state of the input fields (credentials). The `handleLogin` function is called when the form is submitted, and it triggers the login action by calling the `loginAction` function from the `useAuth` hook. The `handleInput` function updates the state as the user types in the input fields.

#### Step 3: Define the AuthProvider and Custom Hooks

Now that we have our login page component ready, let's implement the authentication logic using the `AuthProvider` and custom hooks. The `AuthProvider` component will manage the authentication state and provide authentication-related functions to its child components using React context:

```tsx {{ title: 'AuthProvider.tsx' }}
import React, { createContext, PropsWithChildren, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import useToken from '../hooks/useToken'
import appConfig from '../app.config'

interface UserData {
  username: string
}

export interface Credentials {
  username: string
  password: string
}

interface LoginData {
  token: string
  user: UserData
}

interface AuthContextType {
  token: string | null
  user: UserData | null
  loginAction: (data: Credentials) => void
  logoutAction: () => void
}

export const AuthContext = createContext<AuthContextType | undefined>(undefined)

const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const [user, setUser] = useState<UserData | null>(null)
  const { token, setToken } = useToken()
  const navigate = useNavigate()

  const loginAction = async (credentials: Credentials) => {
    try {
      // Mocking authentication request
      const response = await fetch(`${appConfig.AUTH_API_URL}/login`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(credentials),
      })
      if (!response.ok) {
        throw new Error('Invalid credentials')
      }
      const data: LoginData = await response.json()
      setUser(data.user)
      setToken(data.token)
      navigate('/profile')
    } catch (error) {
      console.error('Login error:', error)
    }
  }

  const logoutAction = () => {
    setUser(null)
    setToken(null)
    navigate('/login')
  }

  const authContextValue: AuthContextType = {
    token,
    user,
    loginAction,
    logoutAction,
  }

  return <AuthContext.Provider value={authContextValue}>{children}</AuthContext.Provider>
}
export default AuthProvider
```

Here the `loginAction` function handles user authentication by sending a POST request to the server with the user's credentials. Upon successful authentication, it updates the user state with the authenticated user data and stores the authentication token in local storage. It then navigates the user to the profile page.

The `logoutAction` function clears the user state and removes the authentication token from local storage, effectively logging out the user and redirecting them to the login page.

The `useAuth` and `useToken` hooks are  shown next:

```tsx {{ title: 'useAuth.ts' }}
import { useContext } from 'react'
import { AuthContext } from '../providers/AuthProvider'

export const useAuth = () => {
  const authContext = useContext(AuthContext)
  if (!authContext) {
    throw new Error(
      'useAuth hook was called outside of context, make sure your app is wrapped with AuthProvider',
    )
  }
  return authContext
}
```

The `useAuth` hook allows components to access the authentication context provided by the AuthProvider.

Next, let's define the `useToken` hook:

```tsx {{ title: 'useToken.ts' }}
import { useState } from 'react'

const ACCESS_TOKEN_KEY = 'access_token'
export default function useToken() {
  const getToken = () => {
    const token = localStorage.getItem(ACCESS_TOKEN_KEY)
    return token
  }

  const [token, setToken] = useState<string | null>(getToken())

  const saveToken = (data: string | null) => {
    if (!data) {
      localStorage.removeItem(ACCESS_TOKEN_KEY)
    } else {
      localStorage.setItem(ACCESS_TOKEN_KEY, data)
      setToken(data)
    }
  }

  return {
    token,
    setToken: saveToken,
  }
}
```

The `useToken` hook provides a simple interface for managing token storage and retrieval using local storage.

Tokens facilitate authenticated server requests, but relying solely on token authentication has limitations and security risks. Simple token systems often lack secure methods to refresh or revoke tokens, exposing applications to unauthorized access if tokens are compromised. Moreover, storing tokens in local storage makes them vulnerable to cross-site scripting (XSS) attacks, where malicious scripts could steal tokens, risking security breaches.

Serving this API should be done over HTTPS to ensure there are no Man In The Middle attacks (MitM) that could compromise the token value.

Setting those arguments aside, the last component is the `Protected` component that ensures certain routes are only accessible to authenticated users. This component will act as a guard, preventing unauthorized access to protected routes by redirecting unauthenticated users to the login page:

```tsx {{ title: 'Protected.tsx' }}
import React from 'react'
import { Navigate, Outlet } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'

const Protected: React.FC = () => {
  const { token } = useAuth()

  // If the user is not authenticated, redirect to the login page
  if (!token) {
    return <Navigate to="/login" />
  }

  // If the user is authenticated, render the child routes
  return <Outlet />
}
export default Protected
```

The `Protected` component utilizes the `useAuth` hook to tap into the authentication context and obtain the authentication token. If authentication is confirmed (indicated by the presence of the token), the Protected component displays the child routes. This setup permits the rendering of nested routes within protected areas for authenticated users.

#### Step 4: Implementing authentication endpoints on the server

Nevertheless you still need to have a server to handle client-side authentication with React. In our case, since we are building things from scratch we will have to consider the simplest option.

Here is what the server code looks like:

```tsx {{ title: 'auth-server.js' }}
import express from 'express'
import bodyParser from 'body-parser'
const app = express()
const PORT = 5000
// Middleware to parse JSON request bodies
app.use(bodyParser.json())

// Mock user data
const users = [
  { username: 'theo', password: 'password1', user: 'Theo' },
  { username: 'alex123', password: 'password2', user: 'Alex' },
]
// Authentication endpoint: POST /auth/login
app.post('/auth/login', (req, res) => {
  const { username, password } = req.body
  const user = users.find((u) => u.username === username && u.password === password)
  if (!user) {
    return res.status(401).json({ message: 'Invalid username or password' })
  }
  // Generate authentication token
  const token = generateToken(user)
  res.json({ token, user: user.user })
})
function generateToken(user) {
  return `generated-token-for-${user.username}`
}

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`)
})
```

This server sets up a route : `/auth/login` for user authentication. It uses a simple array of user objects as a mock user database and a fake token generator for generating an access token.

Once you have our server set up, we can proceed in testing the whole authentication workflow.

#### Step 5: Running the Development Environment

To locally test our authentication setup, we must run the Vite development server for the React application alongside the mock auth server. This can be done by inserting a custom script into our `package.json` file.

First, let's install the required dependencies:

```bash
$ npm install --save-dev concurrently
```

Next, let's update our `package.json` file with a script that starts both the Vite development server and the mock authentication server:

```json {{ title: 'package.json', prettier: false }}
"scripts": {
  "start": "concurrently \"vite\" \"node auth-server.js\""
}
```

Running `npm start` will simultaneously initiate the Vite development server and the authentication server, enabling local testing of our authentication setup as if it were on a live server.

That required significant effort! We've merely begun with a basic setup that doesn't cover user sessions or storing user information in a database—both crucial for production-level authentication.

Next, we'll explore how to enhance our approach with a Next.js implementation.

## Example: How to Implement Authentication in Next.js

In this part, we'll delve into how to incorporate authentication in a Next.js application using NextAuth.js. This package simplifies adding authentication to Next.js projects by providing ready-made support for well-known providers such as Google, GitHub, and Facebook, reducing the manual configuration often seen in React authentication.

Next.js's API routes feature further streamlines this process, allowing us to manage authentication logic directly within our project without a separate server. This enhances our solution's maintainability. The complete code for this tutorial can be found in the `nextjs-auth` folder of the repository.

#### Step 1: Install NextAuth.js

First, let's install Next with NextAuth.js and its dependencies:

```bash
$ npx create-next-app@latest nextjs-auth
$ cd nextjs-auth
$ npm install next-auth@beta @auth/core
```

#### Step 2: Configure NextAuth.js

Next, create a new file named `auth.ts` somewhere in your application. This file will handle authentication requests and configurations. In this tutorial, we are using GitHub as the authentication provider so you need to make sure you register a [new GitHub App](https://github.com/settings/apps/new) to get the provider credentials. Also make sure to set up the correct callback URL. Here is mine for reference:

![NextAuth GitHub configuration](./github.png)

*Setting up a new GitHub App*

```tsx {{ title: 'lib/auth.ts' }}
import NextAuth from 'next-auth'
import Github from '@auth/core/providers/github'
export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
  unstable_update,
} = NextAuth({
  debug: process.env.NODE_ENV === 'development',
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: 'jwt',
  },
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID || '',
      clientSecret: process.env.GITHUB_SECRET || '',
    }),
  ],
})
```

Then export the auth handlers to the  `app/api/auth/[...nextauth]/route.ts` as well. This will handle all the API requests for the `next-auth` package and any configured OAuth callbacks.

```tsx
export { GET, POST } from '@/lib/auth'
```

#### Step 3: Use the provided auth functions to enforce authentication

The `auth` function effectively fetches the current user session. If the user is unauthenticated, it returns null, allowing us to check and redirect as necessary.

Below is an example of implementing protected routes using this approach:

```tsx {{ title: 'pages/api/protected.ts' }}
import { auth } from '@/lib/auth'
export default auth((req) => {
  if (req.auth) {
    return Response.json({ data: 'Success' })
  }
  return Response.json({ message: 'Not authenticated' }, { status: 401 })
})
```

And here is how to protect pages:

```tsx {{ title: 'app/profile/page.tsx' }}
import { redirect } from 'next/navigation'
import { authOptions } from '@/lib/auth'
export default async function Page() {
  const session = await auth()
  if (!session) {
    redirect('/login')
  }
  return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```

#### Step 4: Setup the Login page

To log in using GitHub or any other configured provider, utilize the `signIn` function exported from `auth.ts`.

```tsx {{ title: '(auth)/login/page.tsx' }}
import { Login } from '@/components/Login'
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'

export default async function LoginPage() {
  const session = await auth()
  if (!session?.user) return <Login provider="github" />
  redirect('/profile')
}
```

The `Login` component interacts with server actions as follows:

```tsx {{ title: 'components/Login.tsx' }}
import { signIn } from '@/lib/auth'
export function Login({ provider, ...props }: { provider?: string }) {
  return (
    <form
      action={async () => {
        'use server'
        await signIn(provider)
      }}
    >
      <button {...props}>Login</button>
    </form>
  )
}
```

#### Step 5: Testing the login with GitHub flow

The final step involves testing the entire authentication process with GitHub. Start by navigating to the `/login` page to view the main `Login` button. Upon clicking the `Login` Button, you'll be redirected to GitHub, where you can complete the authentication login process.

Integrating [authentication with Next.js](/nextjs-authentication) and the NextAuth package marked a substantial improvement over the React example. Yet, configuring it revealed certain complexities.

Primarily, the [NextAuth documentation](https://next-auth.js.org/getting-started/introduction) focuses on authentication setup for the pages router, necessitating a visit to the beta documentation for `app` folder insights, leading to discrepancies in examples.

Moreover, not configuring the `secret` and `session` strategy for the GitHub provider in the NextAuth configuration led to difficult-to-diagnose 500 errors. This indicates that developers must still dedicate significant effort to achieve a smooth authentication process with Next.js. The final part of this tutorial will explore whether a more streamlined approach exists.

## How do React and Next.js Authentication differ from each other?

This guide highlights that both React and Next.js offer flexibility in authentication options without enforcing a specific approach, leaving the decision to developers. However, implementing client-side authentication with React still requires a server.

Below is a table illustrating the key differences between authentication in React and Next.js:

| Aspect                   | React.js      | Next.js                                                                                 |
| ------------------------ | ------------- | --------------------------------------------------------------------------------------- |
| Authentication Libraries | External Only | External Only                                                                           |
| Session Management       | No            | [Yes](https://clerk.com/blog/complete-guide-session-management-nextjs)                  |
| Server Side Code         | No            | Yes                                                                                     |
| Middleware support       | No            | [Yes](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) |

While Next.js offers somewhat better support for authentication compared to React, leveraging external solutions for authentication and authorization remains essential. These solutions must be compatible with Next.js's newest features, like the App router, for smooth integration and to fully utilize the framework's capabilities.

Considering the effort involved, would you prefer a more straightforward approach? Explore how Clerk simplifies authentication challenges for both React and Next.js.

## Simplifying Authentication with Clerk

This part of the tutorial is brief, thanks to Clerk's streamlined solution for handling authentication in React and Next.js, app-router support included.

You just need to follow these four simple steps to set it up!

### Clerk with React

Clerk offers a dedicated library for this purpose, and you can easily follow the steps outlined in their [quickstart guide](https://clerk.com/docs/quickstarts/react) to get started.

### Clerk with Next.js

**Step 1: Install Clerk**

```bash
npm install @clerk/nextjs
```

**Step 2: Setup Clerk Secret Keys**

```env {{ title: '.env.local' }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=<CLERK_PUBLISHABLE_KEY>
CLERK_SECRET_KEY=<CLERK_SECRET_KEY>
```

**Step 3: Setup the ClerkProvider on the root component**

Add the `<ClerkProvider/>` on the root layout component:

```tsx {{ title: 'app/layout.tsx' }}
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

Then configure the `authMiddleware` to protect routes:

```tsx {{ title: 'middleware.ts' }}
import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware({
  publicRoutes: ['/'],
})
export const config = {
  matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api)(.*)'],
}
```

**Step 4: Utilize the relevant Authentication components where you see fit**

Here is a full example of using `<UserButton />` which allows users to manage their account information and log out:

```tsx {{ title: 'app/page.tsx' }}
import { SignedIn, SignedOut, SignInButton, UserButton } from '@clerk/nextjs'
function Header() {
  return (
    <header
      style={{
        display: 'flex',
        justifyContent: 'space-between',
        padding: '10px',
      }}
    >
      <h1>My App</h1>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
      </SignedIn>
      <SignedOut>
        <SignInButton />
      </SignedOut>
    </header>
  )
}
export default function Home() {
  return (
    <main>
      <Header />
    </main>
  )
}
```

We didn't encounter any difficulties implementing essential user authentication features like login and logout with Clerk. Clerk simplifies the management of authentication state and sessions, making it straightforward and efficient.

This concise tutorial demonstrated Clerk's ability to effortlessly manage authentication in both React.js and Next.js, positioning it as the ideal solution for authentication requirements.

If Clerk's features have caught your attention, we recommend trying it as your primary authentication solution. For additional tutorials and resources, check out our [docs](https://clerk.com/docs). Sign up for the [free plan](https://clerk.com/pricing) to explore everything Clerk provides.

---

# How to Add an Onboarding Flow for your Application with Clerk
URL: https://clerk.com/blog/add-onboarding-flow-for-your-application-with-clerk.md
Date: 2024-01-30
Category: Engineering
Description: Leverage Clerk’s customizable session tokens, publicMetadata and Next’s Middleware to create a robust onboarding experience within a few lines of code.

As part of your onboarding flow, you may want to collect extra information from your user and use it to drive your application state. Let’s walk through a quick example using Next.js and TypeScript to show you how simple implementing an onboarding flow can be.

In this guide, you will learn how to:

1. Add custom claims to your session token
2. Configure your middleware to read session data
3. Update with the user’s onboarding state

To see a working example, check out our [sample demonstration app here](https://sample-onboarding-app.clerkpreview.com).

The examples below have been pared down to the bare minimum to enable you to easily customize them to your needs, you
can build them with the [Clerk + Next Quickstart](/docs/quickstarts/nextjs) using @clerk/nextjs 6.0.2 and Next
15.0.2.

Let’s get started!

![Add Onboarding Flow For Your Application With Clerk guide illustration](./3e1f0959942a439402674187ee45ef6c592e5e38-2400x1392.png)

## Add Custom Claims to Your Session Token

Session tokens are JWTs that are generated by Clerk on behalf of your instance, and contain claims that allow you to store data about a user’s session. With Clerk, when a session token exists for a user, it indicates that the user is authenticated, and the associated claims can be retrieved at any time. \[[Learn More](/docs/backend-requests/making/custom-session-token)]

First, navigate to [Sessions](https://dashboard.clerk.com/last-active?path=sessions) in your Clerk Dashboard and click the ‘Edit’ button. In the modal that opens, there will be a window where you can augment your session token with custom claims.

In there, add the following and hit save:

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

If you haven’t already, we can make the public metadata type information accessible to our application by adding the following to `src/types/globals.d.ts`:

```typescript {{ filename: 'src/types/globals.d.ts' }}
export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      onboardingComplete?: boolean
      applicationName?: string
      applicationType?: string
    }
    firstName?: string
  }
}
```

We have just added custom data to our session token in the Clerk Dashboard and made those claims accessible to our app. Next, we’ll use `clerkMiddleware` to redirect the user based on `onboardingComplete` status.

## Configure your Next.js middleware to read session data

Clerk's `clerkMiddleware()` allows you to configure access to your routes with fine grained control. You can also retrieve claims directly from the session and redirect your user accordingly. \[[Learn More](/docs/references/nextjs/clerk-middleware#usage)]

Add the code sample below to your `src/middleware.ts` file:

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

const isPublicRoute = createRouteMatcher(['/', '/onboarding'])

export default clerkMiddleware(async (auth, req) => {
  const { userId, sessionClaims, redirectToSignIn } = await auth()

  // If the user isn't signed in and the route is private, redirect to sign-in
  if (!userId && !isPublicRoute(req)) {
    return redirectToSignIn({ returnBackUrl: req.url })
  }

  // Catch users who do not have `onboardingComplete: true` in their publicMetadata
  // Redirect them to the /onboading route to complete onboarding
  if (
    userId &&
    !sessionClaims?.metadata?.onboardingComplete &&
    req.nextUrl.pathname !== '/onboarding'
  ) {
    const onboardingUrl = new URL('/onboarding', req.url)
    return NextResponse.redirect(onboardingUrl)
  }

  // If the user is logged in and the route is protected, let them view.
  if (userId && !isPublicRoute(req)) {
    return NextResponse.next()
  }
})

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)(.*)',
  ],
}
```

Next, create a `layout.tsx` file in `src/app/onboarding` and add the following code to the file. This logic could go in the Middleware, but by adding to the `layout.tsx` to the route the logic remains in one place. This file can also be expanded to handle multiple steps, if multiple steps are required for an onboarding flow.

```tsx {{ filename: 'src/app/onboarding/layout.tsx' }}
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  // Check if a user has completed onboarding
  // If yes, redirect them to /dashboard
  if ((await auth()).sessionClaims?.metadata?.onboardingComplete === true) {
    redirect('/dashboard')
  }

  return <>{children}</>
}
```

Now that we have the logic for where to direct the user, we’ll need a way to track their onboarding status and note it on their session, let’s dig into that now!

## Update publicMetadata based on onboarding state

Updating a user's `publicMetadata` as they progress through the flow will allow us to recognize when they have successfully completed their onboarding and, per the logic above, are now able to access the application. \[[Learn More](/docs/users/metadata)]

To do this you need:

- A method in your backend to securely update the user `publicMetadata`
- A process in your frontend with logic to collect and submit all the information for onboarding. In this guide you’ll use an example form.

### Add userUpdate method to your backend

First, add a method in your backend, that will be called on form submission and update the user’s `publicMetadata` accordingly. The example below uses the `clerkClient` wrapper to interact with the Backend API.

Under `src/app/onboarding/_actions.ts` add the following code snippet:

```typescript {{ filename: 'src/app/onboarding/_actions.ts' }}
'use server'

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

export const completeOnboarding = async (formData: FormData) => {
  const client = await clerkClient()
  const { userId } = await auth()

  if (!userId) {
    return { message: 'No Logged In User' }
  }

  try {
    await client.users.updateUser(userId, {
      publicMetadata: {
        onboardingComplete: true,
        applicationName: formData.get('applicationName'),
        applicationType: formData.get('applicationType'),
      },
    })
    return { message: 'User metadata Updated' }
  } catch (e) {
    console.log('error', e)
    return { message: 'Error Updating User Metadata' }
  }
}
```

Now that we have a method to securely update our user’s `publicMetadata` we can call this server action from a client side form.

### Add a form to your frontend

With the backend updateUser method in place, we’ll add a basic page that contains a form to complete the onboarding process.

This example form that will capture an application name (applicationName) and application type of either B2C or B2B (applicationType). This is a very loose example — you can use this step to capture information from the user, sync user data to your database, have the user sign up to a course or subscription, or more.

To implement this logic, insert the following into your `src/app/onboarding/page.tsx`:

```tsx {{ filename: 'src/app/onboarding/page.tsx' }}
'use client'

import * as React from 'react'
import { useUser } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import { completeOnboarding } from './_actions'

export default function OnboardingComponent() {
  const { user } = useUser()
  const router = useRouter()

  const handleSubmit = async (formData: FormData) => {
    await completeOnboarding(formData)
    await user?.reload()
    router.push('/dashboard')
  }
  return (
    <div className="px-8 py-12 sm:py-16 md:px-20">
      <div className="mx-auto max-w-sm overflow-hidden rounded-lg bg-white shadow-lg">
        <div className="p-8">
          <h3 className="text-xl font-semibold text-gray-900">Welcome!</h3>
        </div>
        <form action={handleSubmit}>
          <div className="space-y-4 px-8 pb-8">
            <div>
              <label className="block text-sm font-semibold text-gray-700">
                {' '}
                Application Name{' '}
              </label>
              <p className="text-xs text-gray-500">Enter the name of your application.</p>
              <input
                type="text"
                name="applicationName"
                className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-semibold text-gray-700">Application Type</label>
              <p className="text-xs text-gray-500">Describe the type of your application.</p>
              <input
                type="text"
                name="applicationType"
                className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
              />
            </div>
          </div>
          <div className="bg-gray-50 px-8 py-4">
            <button
              type="submit"
              className="w-full rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
            >
              Submit
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}
```

## Wrap Up

Your onboarding flow is now complete! 🎉 New users who haven’t yet onboarded will now land on your `/onboarding` page and, once they have completed onboarding, will be sent through to the dashboard. By using Clerk, which already handles user authentication, we were able to simplify the process of creating a custom user onboarding flow as well.

---

# Create Your Own Custom User Menu with Radix - Part 2
URL: https://clerk.com/blog/create-custom-user-menu-radix-pt-2.md
Date: 2024-01-29
Category: Guides
Description: Extend your Radix powered custom User Menu to turn it into a Sign In or User Profile component

Code samples are from @clerk/nextjs 4.29.5, @radix-ui/react-dropdown-menu 2.0.6, class-variance-authority 0.7.0 and
@heroicons/react 2.1.1

Welcome to the second part of building a custom user menu! [In the first part,](/blog/create-custom-user-menu-radix) we built a replacement for Clerk’s `<UserButton /> `using the Radix Dropdown primitive and some of Clerk’s hooks. Now we’ll be upgrading our user button with sign-in functionality when the user is not logged in as well as improve the behavior of the component in several ways.

## Refactoring the component

The first step is to refactor the component so it's ready to build upon. Take the contents of the `return` and create a new component in the file above the exported component. Call the new component `<UserButtonAndMenu />` and paste the copied code. We will need to add in the destructures for the user method from the `useUser()` hook, `signOut()`, and `openUserProfile()` from the `userClerk()` hook, and the router method from the `userRouter()` hook.

The first step is to refactor the component so it's ready to build upon.

- Create a new component in the file called `<UserButtonAndMenu />`
- Move the JSX from the original `<UserButton />` into the `<UserButtonAndMenu />` component
- Move `user` method from `useUser()` hook to the new component
- Move `signOut()`, and `openUserProfile()` from `userClerk()` hook to the new component
- Move `router` method from `userRouter()` hook to the new component\\

In the `<UserButton />` component, we’re going to leave the check for `isLoaded` and add a `user.id` check using `if ( !user.id ) {}` and return the `<SignInButton />` component from Clerk if there is no user.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useUser, useClerk, SignInButton } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import Link from 'next/link'

// Create a new UserButtonandMenu component and move the old return into this
const UserButtonAndMenu = () => {
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()
  const { user } = useUser()

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Render a button using the image and email from `user` */}
        <button className="flex flex-row rounded-xl border border-gray-200 bg-white px-4 py-3 text-black drop-shadow-md">
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
            className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-6 py-4 text-black drop-shadow-2xl">
          <DropdownMenu.Label />
          <DropdownMenu.Group className="py-3">
            <DropdownMenu.Item asChild>
              {/* Create a button with an onClick to open the User Profile modal */}
              <button onClick={() => openUserProfile()} className="pb-3">
                Profile
              </button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild>
              {/* Create a fictional link to /subscriptions */}
              <Link href="/subscriptions" passHref className="py-3">
                Subscription
              </Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator className="my-1 h-px bg-gray-500" />
          <DropdownMenu.Item asChild>
            {/* Create a Sign Out button -- signOut() takes a call back where the user is redirected */}
            <button onClick={() => signOut(() => router.push('/'))} className="py-3">
              Sign Out{' '}
            </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}

// Refactor to show the default <SignInButton /> if the user is logged out
// Show the UserButtonAndMenu if the user is logged in
export const UserButton = () => {
  const { isLoaded, user } = useUser()

  if (!isLoaded) return null

  if (!user?.id) return <SignInButton />

  return <UserButtonAndMenu />
}
```

## Improving the component

If you saved your work and tested it, you will see that the refactoring has already accomplished the base goal — it is now both a Sign-In button and a User Button/User Menu. We can still improve the component and provide a better user experience, so let’s do a few more refactors.

The first step is moving the button to its component. We’ll also add in a `forwardRef` to plan for the later improvements.

Tip: You might have your own custom button already created or available from a library you are using. If so then you
can use that in place of this one.

```tsx
// Add import
import * as React from 'react'

// Create a new <Button /> component using the same classes
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ children, className, ...props }, ref) => {
    return (
      <button ref={ref} className={className} {...props}>
        {children}
      </button>
    )
  },
)
```

The second step is to refactor part of the `<UserButtonAndMenu /`> component. We want to take advantage of the new `<Button />`

```tsx
const UserButtonAndMenu = () => {
  const { user } = useUser()
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Swap <button /> to the new <Button /> */}
        <Button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl!}
            width={30}
            height={30}
            className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </Button>
      </DropdownMenu.Trigger>
    </DropdownMenu.Root>

    // rest of the component remains the same
  )
}
```

The third step is to refactor the top-level `<UserButton /> `component to also use the new `<Button />` component. This button will use `openSignIn() `from `useClerk()` to programmatically open the sign-in modal. This results in a custom button and removal of the Clerk `<SignInButton />`

```tsx
// Update @clerk/nextjs imports
import { useUser, useClerk } from '@clerk/nextjs'

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  // Bring in openSignIn
  const { openSignIn } = useClerk()

  if (!isLoaded || !user?.id) {
    /* Use the new <Button /> component for the sign-in button */
    return <Button onClick={() => openSignIn()}>Sign In</Button>
  }

  return <UserButtonAndMenu />
}
```

You can see that we left the user profile image inside of `<UserMenuAndButton />` and passed it to the `<Button />` as a child. Depending on your need you could hoist the image handling into the `<Button />` — that’s totally up to the needs of what you’re building.

## Refining the component

Everything is working nicely at this point, and the structure is in a great place to build on. That said, we can add a few refinements to elevate the user experience. Let’s start by installing an icon package and the class-variance-authority package.

This is using `@heroicons/react` as it's a simple, one-stop icon package. Use other icons that might suit your design
or needs better.

```bash
pnpm install @heroicons/react class-variance-authority
```

With that installed, let’s do another refactor of the Button. We’ll use `class-variance-authority` to expand on what the button can do. This is a very structured approach and provides TypeScript support. We will set up a `variant` and a `size`, use the resulting `primary` and `regular` for the main button, and `menu` and `small` for the buttons in the dropdown menu.

```tsx
// Add imports
import { VariantProps, cva } from 'class-variance-authority'

// Configure the styles for the Button and its variants and sizes
const button = cva(['flex', 'flex-row', 'items-center', 'rounded-xl'], {
  variants: {
    variant: {
      primary: [
        'border',
        'border-gray-200',
        'bg-white',
        'text-black',
        'drop-shadow-md',
        'hover:bg-stone-100',
        'hover:text-stone-800',
      ],
      menu: ['bg-transparent', 'text-gray-800/70', 'hover:text-gray-900'],
    },
    size: {
      regular: ['px-4', 'py-3'],
      small: ['py-3', 'py-2'],
    },
  },
  defaultVariants: {
    variant: 'primary',
    size: 'regular',
  },
})

// Extend the default Button types with props created by create-variance-authority
interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof button> {}

// Create a new <Button /> component using the same classes
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant, size, children, className, ...props }, ref) => {
    return (
      <button
        ref={ref}
        className="flex flex-row rounded-xl border border-gray-200 bg-white px-4 py-3 text-black drop-shadow-md"
        {...props}
      >
        {children}
      </button>
    )
  },
)
```

This `<Button />` component can be used anywhere in your application — if you don’t have a `<Button />` you’ve built
already or one from a library then you can move this out of the `<UserButton />` and instead use it application-wide.

Now that our `<Button />` has reached its final form, let’s import some icons.

```typescript
// Add import
import {
  ArrowRightCircleIcon,
  ArrowRightEndOnRectangleIcon,
  CurrencyDollarIcon,
  UserIcon,
} from '@heroicons/react/24/solid'
```

Let’s modify the button that serves as the trigger for the User Menu. We’ll use Clerk’s `hasImage` value from the `user` return of `useUser()`. Using them will let us display the `UserIcon` we just imported when the user hasn’t set a profile image, but use their image when they have. We will also will move the logic we have for the label for the button up.

```tsx
const UserButtonAndMenu = () => {
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()
  const { user } = useUser()
  // Use the firstname if there is on, otherwise provide a label */
  const label = user?.firstName ? user.firstName : 'Profile'

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <Button>
          {/* Render a button using the image and email from `user` */}
          {user?.hasImage ? (
            <Image
              alt={label}
              src={user?.imageUrl}
              width={30}
              height={30}
              className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
            />
          ) : (
            <>
              {/* Display the icon is there is no profile image */}
              <UserIcon className="mr-2 h-6 w-auto" />
            </>
          )}
          {label}
        </Button>
      </DropdownMenu.Trigger>
    </DropdownMenu.Root>

    // Rest of the component here
  )
}
```

We can use the `ArrowRightCircleIcon` to add a little flare to the Sign-In button.

```tsx
export const UserButton = () => {
  const { isLoaded, user } = useUser()
  const { openSignIn } = useClerk()

  if (!isLoaded) return null

  if (!user?.id) {
    return (
      <Button onClick={() => openSignIn()}>
        Sign In
        {/* Add an icon to the Sign-in button */}
        <ArrowRightCircleIcon className="ml-2 h-6 w-auto" />
      </Button>
    )
  }

  return <UserButtonAndMenu />
}
```

Lastly, we will use `UserIcon`, `ArrowRightEndOnTectangleIcon`, and `CurrencyDollarIcon` to add icons to the drop-down menu. At the same time we will add the variant and size to the buttons so they are using the new configuration.

```tsx
<DropdownMenu.Portal>
  <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-2 py-2 text-black drop-shadow-2xl">
    <DropdownMenu.Label />
    <DropdownMenu.Group className="py-1">
      <DropdownMenu.Item asChild>
        <Button onClick={() => openUserProfile()} className="pb-3" variant="menu" size="small">
          <UserIcon className="mr-2 h-6 w-auto" />
          Profile
        </Button>
      </DropdownMenu.Item>
      <DropdownMenu.Item asChild>
        <Link href="/subscriptions" passHref>
          <Button className="py-2" variant="menu" size="small">
            <CurrencyDollarIcon className="mr-2 h-6 w-auto" />
            Subscription
          </Button>
        </Link>
      </DropdownMenu.Item>
    </DropdownMenu.Group>
    <DropdownMenu.Separator className="my-1 h-px bg-gray-200" />
    <DropdownMenu.Item asChild>
      <Button
        onClick={() => signOut(() => router.push('/'))}
        className="py-3"
        variant="menu"
        size="small"
      >
        <ArrowRightEndOnRectangleIcon className="mr-2 h-5 w-auto" /> Sign Out
      </Button>
    </DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu.Portal>
```

## Finishing Touches

We can add a few finishing touches to the component to flesh it out some more with a few smaller tweaks and improvements:

- add an `accent` variant, and then use that for the user button.
- give `menu` variant buttons unique styling.
- add `className="min-w-[192px]"` to the sign-in and user button to help give them a more consistent width.
- add `ArrowPathIcon` to the icon import create a button for `!isLoaded`, and give it a loading/spanning state.
- use the `outline-none` class to remove the focus ring from the menu items

```tsx
'use client'

import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useUser, useClerk } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import Link from 'next/link'
import {
  ArrowPathIcon,
  ArrowRightCircleIcon,
  ArrowRightEndOnRectangleIcon,
  CurrencyDollarIcon,
  UserIcon,
} from '@heroicons/react/24/solid'
import { VariantProps, cva } from 'class-variance-authority'

const button = cva(['flex', 'flex-row', 'items-center', 'rounded-xl'], {
  variants: {
    variant: {
      primary: [
        'border',
        'border-gray-200',
        'bg-white',
        'text-black',
        'drop-shadow-md',
        'hover:bg-stone-100',
        'hover:text-stone-800',
        'justify-center',
      ],
      accent: [
        'border',
        'border-stone-950',
        'bg-stone-800/70',
        'hover:bg-stone-950',
        'text-stone-200',
        'justify-center',
      ],
      menu: [
        'w-full',
        'justify-start',
        'bg-transparent',
        'hover:bg-stone-800/70',
        'text-gray-800/70',
        'hover:text-stone-100',
        'px-4',
        'rounded-sm',
      ],
    },
    size: {
      regular: ['px-4', 'py-3'],
      small: ['py-3', 'py-2'],
    },
  },
  defaultVariants: {
    variant: 'primary',
    size: 'regular',
  },
})

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof button> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant, size, children, className, ...props }, ref) => {
    return (
      <button ref={ref} className={button({ variant, size, className })} {...props}>
        {children}
      </button>
    )
  },
)

// Create a new UserButtonandMenu component and move the old return into this
const UserButtonAndMenu = () => {
  const { user } = useUser()
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()
  const label = user?.firstName ? user.firstName : 'Profile'

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild className="outline-none">
        <Button variant="accent" className="min-w-[192px]">
          {user?.hasImage ? (
            <Image
              alt={label ? label : 'Profile image'}
              src={user?.imageUrl}
              width={30}
              height={30}
              className="mr-2 rounded-full border border-stone-950 drop-shadow-sm"
            />
          ) : (
            <UserIcon className="mr-2 h-6 w-auto" />
          )}
          {label}
        </Button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-2 py-2 text-black drop-shadow-2xl">
          <DropdownMenu.Label />
          <DropdownMenu.Group className="py-1">
            <DropdownMenu.Item asChild className="outline-none">
              <Button
                onClick={() => openUserProfile()}
                className="pb-3"
                variant="menu"
                size="small"
              >
                <UserIcon className="mr-2 h-6 w-auto" />
                Profile
              </Button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild className="outline-none">
              <Link href="/subscriptions" passHref>
                <Button className="py-2" variant="menu" size="small">
                  <CurrencyDollarIcon className="mr-2 h-6 w-auto" />
                  Subscription
                </Button>
              </Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator className="my-1 h-px bg-gray-200" />
          <DropdownMenu.Item asChild className="outline-none">
            <Button
              onClick={() => signOut(() => router.push('/'))}
              className="py-3"
              variant="menu"
              size="small"
            >
              <ArrowRightEndOnRectangleIcon className="mr-2 h-5 w-auto" /> Sign Out
            </Button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  const { openSignIn } = useClerk()

  if (!isLoaded)
    return (
      <Button onClick={() => openSignIn()} className="w-48">
        <ArrowPathIcon className="ml-2 h-6 w-auto animate-spin" />
      </Button>
    )

  if (!user?.id)
    return (
      <Button onClick={() => openSignIn()} className="w-48">
        Sign In
        <ArrowRightCircleIcon className="ml-2 h-6 w-auto" />
      </Button>
    )

  return <UserButtonAndMenu />
}
```

Example repository: [https://github.com/royanger/clerk-custom-user-menu](https://github.com/royanger/clerk-custom-user-menu)

## Explore the Powerful Customization Options Clerk Offers!

With this component you have the building blocks to build out your own user button and menu. Add the new entries to the dropdown that you need for your application, and use the tools provided by Radix and `cva` to design and style your component so it matches your application's design language!

Take a look at our [Custom Flows](/docs/custom-flows/overview) documentation to explore more ways to customize your application using the many hooks and methods Clerks provides. The ability to add the pieces you need from Clerk to fully custom and unique UI provides flexibility to projects.

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless user management starts here!

---

# Introducing Webhook Workflows with Inngest & Svix
URL: https://clerk.com/blog/clerk-inngest-svix-webhooks.md
Date: 2024-01-24
Category: Company
Description: We are excited to announce that Clerk has teamed up with Inngest and Svix to integrate with external systems reliably.

In this post we will dive into how the collaboration between Clerk, [Inngest](https://www.inngest.com), and [Svix](https://www.svix.com) enhances your authentication workflows using Clerk’s new Inngest webhook transformation template.

## Building with Clerk Webhooks

Clerk webhooks (powered by Svix) allow you to receive event notifications from Clerk. When a `user.created` event is triggered, you may want to:

- Sync user data to your database.
- Kick off an account provisioning workflow.
- Start a trial in Stripe.
- Send a welcome email or start a drip campaign.
- Add the user to your product newsletter in Mailchimp.

Today, with Clerk’s new Inngest webhook transformation template, you can easily use Clerk webhook events to trigger Inngest functions.

Inngest is a reliability layer for your application. Using it comes with a few key benefits such as [managing concurrency](https://www.inngest.com/docs/guides/concurrency), [automatic retries](https://www.inngest.com/docs/functions/retries), [parallel execution](https://www.inngest.com/docs/guides/fan-out-jobs), [complex workflows](https://www.inngest.com/docs/reference/functions/step-run), or [task scheduling](https://www.inngest.com/docs/reference/functions/step-sleep). This approach eliminates concerns about operating and scaling webhooks or error handling.

## See it in Action

The code below features the `inngest.createFunction` method, which inserts a new user into the database. It will be triggered whenever a `clerk/user.created` event occurs.

```javascript
const syncUser = inngest.createFunction(
  { id: 'sync-user-from-clerk' },
  { event: 'clerk/user.created' }, // ← This is the function's triggering event
  async ({ event }) => {
    const user = event.data // The event payload's data will be the Clerk User json object
    const { id, first_name, last_name } = user
    const email = user.email_addresses.find((e) => e.id === user.primary_email_address_id).email
    await database.users.insert({ id, email, first_name, last_name })
  },
)
```

You can also have multiple functions react to the same event. The code below uses `step.sleep` to send a welcome email, then wait for three days, then follow up with a message offering a free trial:

```javascript
const sendOnboardingEmails = inngest.createFunction(
  { id: 'onboarding-emails' },
  { event: 'clerk/user.created' },
  async ({ event, step }) => {
    // ← step is available in the handler's arguments
    const { user } = event.data
    const { first_name } = user
    const email = user.email_addresses.find((e) => e.id === user.primary_email_address_id).email

    await step.run('welcome-email', async () => {
      await emails.sendWelcomeEmail({ email, first_name })
    })

    // wait 3 days before second email
    await step.sleep('wait-3-days', '3 days')

    await step.run('trial-offer-email', async () => {
      await emails.sendTrialOfferEmail({ email, first_name })
    })
  },
)
```

To learn how to integrate Inngest into your workflows, check out [Clerk's official integration guide](/docs/integrations/webhooks/inngest) or refer to [Inngest documentation](https://www.inngest.com/docs).

## **Onward and Upward**

As we continue to relentlessly improve the Developer Experience of our products, we are excited to see what developers build using the Inngest integration.

---

# Clerk raises $30M Series B from CRV and Stripe
URL: https://clerk.com/blog/series-b.md
Date: 2024-01-18
Category: Company
Description: New funding will accelerate expansion beyond authentication, into authorization

We’re excited to share that Clerk has raised $30M in Series B funding led by [CRV](https://www.crv.com), with participation from [Stripe](https://stripe.com), and existing investors [Andreessen Horowitz](https://a16z.com) and [Madrona](https://www.madrona.com). We will use this capital to expand our service beyond *authentication*, which identifies who a user is, and into *authorization*, which determines the permissions a user has.

To build our authorization service, we are partnering with Stripe to tightly integrate Stripe Billing. Together, we will create the easiest way to subscribe users to plans and grant access to features based on the plan that was purchased — all with no webhooks required.

> “The `<SignIn/>` and `<UserProfile/>` components from Clerk have already set a new standard in authentication, and proven that components are the new APIs. We’re thrilled to support Clerk as they apply their solutions to the authorization market.” - Reid Christian, General Partner at CRV

Our Series B was led by Reid Christian at CRV, who we’ve been trading notes with for years. Reid and CRV are no strangers to modern developer tools, having invested in Vercel in 2015, during the earliest days of Next.js. We’re thrilled to be working with him for this next phase of Clerk and beyond.

Of course, none of this would be possible without our foundations in authentication, which we’ve been bolstering since 2019. Thanks to the dedication of our team, Clerk has experienced tremendous growth since our [Series A](/blog/series-a) last year, and is now managing over 16 million end-users! And, we’re facilitating over one new sign-up per second across verticals like SaaS, fintech, e-commerce, and more.

Our mission is to simplify the way you keep your users and their data safe, without forcing you to sacrifice high-quality user experience. We’ve been stalwart in our commitment to that mission for authentication, and you can be confident we will maintain our principles as we expand into authorization.

If you want to get in touch, find us on X at [@clerk](https://x.com/clerk). On behalf of the entire Clerk team, thank you so much for your continued support. We can’t wait to continue this journey with all of you, and to show you what we’re building next!

---

# Clerk in 2023: A Year in Review
URL: https://clerk.com/blog/clerk-2023-year-review.md
Date: 2024-01-04
Category: Company
Description: Wow! We had an incredible year in 2023, here is a list of everything we shipped...

This was a exciting year for Clerk, packed with new features and a growing community around Clerk’s user authentication platform. Let's rewind and celebrate some of the milestones that marked our journey!

## January

![Clerk 2023 Year Review tutorial illustration](./95c27737706e26d7bb2a24cd81b1dee3046269d8-2400x1260.png)

### Enhanced DX & Security

As part of Clerk’s mission to revolutionize DX, the focus was on removing roadblocks and streamlining workflows. To enhance security, Clerk implemented several key improvements, including eliminating third-party cookies, which can be used for malicious acts such as cross site scripting. Additionally, Clerk improved API key management for stronger access control; all of this means developers can build more secure applications with ease, using Clerk.

### Upgraded Dashboard & Documentation

Clerk made it easier for developers to find answers and manage their Clerk integrations with a more intuitive interface and comprehensive documentation.

### Gatsby SDK & Community RedwoodJS SDK

Expanded Clerk's compatibility with popular web development frameworks, making it even easier for developers to integrate user authentication into their projects. The major thing to highlight here is Clerk’s growing community and passion for enabling Clerk to work with the tools they love! Learn more in this [changelog post](/changelog/2023-01-27#gatsby-v-5).

### The Popular Starter Template t3-turbo-and-clerk Powered by Clerk

[This open-source template](https://github.com/clerk/t3-turbo-and-clerk) showcases the power of Clerk for building web and mobile apps with robust authentication.

## February

### Improved Next.js Middleware Integration

Clerk’s [new Middleware](/changelog/2023-02-10#next-js-middleware-protection-strategy) allowed for seamless integration with the popular Next.js framework, allowing developers to easily [add user authentication to your Next.js applications](/nextjs-authentication).

### Fastify & RedwoodJS v4 Integrations

Further broadened Clerk's reach by offering official integrations with [Fastify](/docs/quickstarts/fastify#use-clerk-with-fastify) and [RedwoodJS](/docs/quickstarts/redwood#use-clerk-with-redwood-js) frameworks, empowering developers to choose the tools they love.

### Documentation Overhaul

Revamped Clerk's documentation with better organization, search functionality, performance, and authoring experience, making it easier for developers of all levels to learn and use Clerk.

## March

![Clerk 2023 Year Review tutorial illustration](./2d1af91c70bf05d829a3d890a0db78bc4c95b8fd-2400x1260.png)

### $15M Series A Led by Madrona & Clerk.com Domain!

This significant investment and domain move solidified Clerk's position as a leading authentication provider and fueled Clerk’s continued growth. Read more about our Series A in this [announcement post](/blog/series-a).

### SDK Performance Boost through Lazy Loading

Optimized performance by ensuring Clerk's pre-built components only load when needed, preventing initial render time delays and ensuring a smooth user experience.

### Dedicated Chrome Extensions SDK

Opened up possibilities for secure user authentication in the booming Chrome extension market. Check out the [starter repo](https://github.com/clerk/clerk-chrome-extension-starter) on GitHub.

### T3 Stack Tutorial by Theo Browne Showcases Clerk, Vercel & PlanetScale

The [T3 Stack Tutorial](https://www.youtube.com/watch?v=YkOSUVzOAA4\&t) highlighted the seamless integration of Clerk with other popular tools like Vercel and PlanetScale, demonstrating how developers can build powerful and scalable applications with a unified tech stack.

## April

### Clerk Becomes an Identity Provider

Clerk IdP enables large enterprise companies juggling Authentication between all their vendors to create a nexus through Clerk for Single Sign-On functionality.

### Improved Phone & Email Input, Customizable Hosted Pages through Dashboard

Enhanced the user experience with more intuitive input fields and the ability to personalize the look and feel of login and signup pages, boosting brand consistency.

### Full Expo 48 Support

Made Clerk a perfect fit for mobile developers using Expo, a popular framework for building cross-platform apps, and streamlined the creation of custom login flows within Expo apps. Learn more in [this changelog entry](/changelog/2023-04-07#expo-48-support) or explore our [Expo authentication](/expo-authentication) solution.

## May

![Clerk 2023 Year Review tutorial illustration](./50919943d3ffac47193796b588967026f710ae57-2400x1260.png)

### Next.js 13.4 & App Router Support

Clerk swiftly followed the release of Next.js 13.4 and App Router stable by [offering full support](/blog/nextjs-13-4) on May 5th. This made Clerk one of the first development tools and the first authentication provider to fully leverage the power of Next.js, React Server Components, and Edge middleware.

### Enhanced Password Security & UX

May saw a [significant upgrade to password functionalities](/blog/a-new-password-experience) in Clerk applications. Setting and resetting passwords became smooth and user-friendly, while breach detection and complexity tests ensured stronger security.

Developers gained complete control over password security through the Dashboard, allowing them to tailor password policies and requirements to their specific needs.

### Stepping into the Public Spotlight

Clerk's presence in the community exploded in May. We transitioned from sporadic YouTube mentions to sponsoring and presenting at major conferences.

CEO Colin Sidoti's [keynote at Reactathon](https://www.youtube.com/watch?v=enUuBY3HXh4), advocating for the power of well-designed components, and VP of Engineering Sokratis Vidros's talk at CityJS on [building for JavaScript edge runtimes](https://www.youtube.com/watch?v=_ypxvJ0oU-Y), solidified Clerk's position as a thought leader in the industry.

### "How We Roll" Launch

May also marked the launch of the ["How We Roll" blog series](/blog/how-we-roll-passwords), offering a behind-the-scenes look at Clerk's technology and how it delivers a seamless user experience.

### UX Tweaks & Optimization

Recognizing the power of well-crafted UI components, Clerk focused on subtle but impactful UX improvements. Highly customizable user avatars with hover effects and subsequent image size optimizations are prime examples of this dedication to user experience.

## June

### SAML

In June, [SAML support](/docs/authentication/enterprise-connections/overview) was released to public beta, allowing companies to leverage Clerk to seamlessly integrate their internal tools and frontends with their SAML services. Just a few projects leveraging Clerk’s SAML offering include, [Airflip](https://www.airflip.com) a modern tool for procurement teams, [Lawhive](https://lawhive.co.uk) a powerful platform for finding litigators in the U.K., and [Casa](https://clibrain.com) a LLM specialized in usage in Spanish.

### Bot Detection

Clerk’s ownership of the authentication flows allows [enhanced bot detection](/changelog/2023-07-07#enhanced-bot-detection-for-ui-components). Clerk’s hosted pages were retrofitted with layers of bot detection capabilities in June. Clerk also enabled self-service user delete, an important user privacy feature that every app collecting user data should implement.

## July

### Bot Detection in the JavaScript SDK

Clerk’s bot detection capabilities were quickly brought to the JavaScript SDK, including the pre-built components and hooks, ensuring that you can be much more confident about your users not being bots. This capability is a must-have for applications providing free trial or usage credits, user generated content, or social interactions, and comes with Clerk out-of-the-box with no additional configuration required.

### API Improvements for Sorting Users & Organizations

The ownership of user management also comes with a responsibility - user data being stored with Clerk should not become a bottleneck when developing applications, and developers should be able to query the data however they want. July saw improvements to the APIs for querying users and organizations with [advanced filtering and sorting capabilities](/changelog/2023-07-21#filtering-and-sorting-for-users-organizations-members), and better customization of the session token with custom user data.

## August

### Detection for Disposable Emails & Subaddressing

The defenses put in place to fight the bots got even better in August, when Clerk added [detection for disposable emails and subaddressing](/docs/authentication/configuration/restrictions#block-email-subaddresses). These capabilities make Clerk especially useful to AI products for which bots can be very costly.

### Reverse Proxy Support

Another key infrastructure upgrade quickly followed - support for [proxying the requests to Clerk’s Frontend API](/docs/advanced-usage/using-proxies#proxying-the-clerk-frontend-api) through a reverse proxy server!

The reverse proxy support unlocked an entire realm of capabilities, since your application can now be deployed on any domain (preview, staging, tenant subdomains etc) and have full access to authentication, as well as synchronization of signed in sessions.

### “How We Roll” Conclusion

The blog series “How We Roll” closed out with its [10th chapter](/blog/how-we-roll-roundup), rounding up prior chapters and summarizing how Clerk maximizes developer experience, application security, and user experience. The community made some amazing contributions to Clerk’s integrations ecosystem through a Vue, Elysia, and Rust integration, fully open source like all of Clerk’s integrations.

##

## September

![Clerk 2023 Year Review tutorial illustration](./df028ebcab1340139c60bf84ae8aa24b3125b9f7-2400x1260.png)

### Account Portal

September saw the release of Clerk’s [Account Portal](/docs/account-portal/getting-started#getting-started-with-the-account-portal), which replaced hosted pages as the fastest way to authenticate any application, by eliminating the need for the developer to build any authentication related UI at all. The Account Portal was a fresh redesign of the entire user experience, along with better control over the look and feel.

### Custom Domain Registration

Clerk’s B2B offerings continue to get crucial upgrades, like custom domain registration for organizations, allowing users to automatically join organization based on their work email instead of requiring an invite from the admins.

### Form Pre-Fill Ability

The pre-built components for sign in and sign up also received the [ability to pre-fill](/docs/account-portal/direct-links#prefill-sign-in-and-sign-up-fields) the form, making them a lot more flexible and elevating the user experience.

## October

![Clerk 2023 Year Review tutorial illustration](./34c514b138fe70e4ac08798abd3e0f84f691e262-2400x1379.png)

### Changelog & Roadmap

This is getting to be a lot to keep track of, right? We agree, and in October we addressed this through the new changelog and roadmap.

The new [changelog page](/changelog) on [clerk.com](https://clerk.com) provides a one stop hub for all the important releases and announcements, while the [roadmap](https://feedback.clerk.com/roadmap) provides a lot of insight into the ongoing efforts of our product teams and provide any feedback for a new feature or improvements.

### Custom Pages

We also want to make sure as developers scale their products and require user management capabilities that Clerk doesn’t provide, they don’t have to opt out of the pre built components and build completely new UI. [Custom pages](/changelog/2023-10-26) in user and organization profile take their extensibility to the next level.

## November

![Clerk 2023 Year Review tutorial illustration](./913027a390d70b194c9f1bce6fde26240efb031e-2400x1260.png)

### Pricing Overhaul

We want the experience of Clerk to be available to everyone, including developers who don’t have paying users. We solidified our dedication towards the affordability of Clerk with a massive pricing overhaul, which came with 10,000 free MAUs for everyone, with the user’s first day free, and paid add-ons for the advanced authentication, administration, and B2B features. Read our [announcement post](/blog/new-pricing-plans) for further details.

### Hono Adapter

Applications built using Hono, a powerful and lightweight web server known for its compatibility with edge runtimes, also got access to Clerk’s authentication features with an [official adapter](https://github.com/honojs/middleware/tree/main/packages/clerk-auth)!

## December

### Account Lockout

[Account Lockout](/changelog/2023-12-01) is a Clerk feature that enables you to protect your users from brute-force attacks on static credentials such as passwords or backup codes. When enabled it tracks all the attempts and locks down the account after 100, the default attempts, it’s locked for an hour!

### Custom Roles & Permissions

One of our most sought after features of the year, custom roles and permissions, dropped in December, just in time for the holidays. Companies were able to simplify and improve their UX around roles, permissions and access provided to their end users.

As a part of this addition, we've added helper functions and components – `has()`, `protect()` and `<Protect />`. To learn more, read our [announcement post](/blog/introducing-authorization).

### FAPI Reference Docs

Our [Frontend API documentation](/docs/reference/frontend-api) is back and better than ever!

### Fetch User By Activity

Another drop was a new endpoint that allows insight into when a user was last active, this is great for all kinds of UX or admin dashboard implementations! Check out the [changelog entry](/changelog/2023-12-07) to learn more.

## That's A Wrap!

Let's reach new heights in 2024 with Clerk, don't stop the auth conversation! Dive into the nitty-gritty with our technical wizards on [Discord](https://clerk.com/discord). Share your ideas, get expert advice, and join the Clerk community building the future of User Management. Plus, stay in the know with [@clerk](https://x.com/clerk) on X for all the latest releases and sneak peeks. Your seamless journey starts here!

---

# Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk
URL: https://clerk.com/blog/build-movie-emoji-quiz-with-remix-fauna-and-clerk.md
Date: 2024-01-02
Category: Guides
Description: Test the emoji game of all the movie buffs you know by building a Movie Emoji Quiz app with Remix, Fauna, and Clerk.

> \[!WARNING]
> Fauna’s service [ended on May 30, 2025](https://web.archive.org/web/20250319171743/https://fauna.com/blog/the-future-of-fauna), but they’ve [open-sourced their codebase](https://faunadb.org).

## Project setup

A fun challenge coming out of the modern smartphone world is to identify a movie based only on a sequence of emoji. Sometimes the emoji “spell out” the words in the title, while other times they identify key plot themes. Test the emoji game of all the movie buffs you know by building a Movie Emoji Quiz app.

In this tutorial, we will build the app using the Remix full-stack web framework, a Fauna database, and Clerk for authentication and user management. Remix is a relatively new, open-source framework for React that has been gaining traction. Fauna is a developer-friendly, cloud-based database platform that offers modern features, such as real-time streaming and GraphQL. Clerk, an authentication provider built for the modern web, has a first-party [Remix authentication package](/solutions/remix-authentication) and integrates with Fauna through its JWT templates feature.

A brief web search didn't come up with any existing Remix and Fauna tutorials. And then I found this tweet from Ryan Florence ([@ryanflorence](https://twitter.com/ryanflorence)), the co-founder of Remix:

> I really want to build some demos with FaunaDB (I love the direction they're going) and AWS DynamoDB (I mean, come on, it's solid) as well. — Ryan Florence (@ryanflorence) [April 6, 2021](https://twitter.com/ryanflorence/status/1379289891226316801?ref_src=twsrc%5Etfw)

That sold me on moving forward in building with this stack.

If you would like to skip ahead, you can see the [completed
codebase](https://github.com/clerkinc/remix-fauna-tutorial) here.

### Assumptions

This tutorial makes the following assumptions...

- Basic command line usage
- Node.js (≥ v14) installed with `npm` (≥ v7)
- Experience with React components and hooks
- [Clerk](https://dashboard.clerk.com/sign-in) and [Fauna](https://dashboard.fauna.com/accounts/login) accounts already set up (if you haven’t done so, do it now\... we’ll wait)

### Set up a Clerk application

The first step is to create a new application from the Clerk dashboard. We’ll name this application **Movie Emoji Quiz** and leave the default **Password** authentication strategy selected. We’re also going to choose **Google** and **Twitter** as the social login providers, but feel free to select whichever ones you and your friends use.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./0a352efb7eacb52a6221f618c1fcd705126aae31-567x882.png)

Click the **Add application** button and your Clerk development instance will be created.

### Create Remix project

The next step is to create the Remix project. Run the following command in your terminal:

```bash
npx create-remix movie-emoji-quiz
```

It may prompt you to install the create-remix package. Then respond with the following:

```text
? What type of app do you want to create? Just the basics
? Where do you want to deploy? Remix App Server
? Do you want me to run `npm install`? Yes
```

It will then ask “TypeScript or JavaScript?”, we’re going with **JavaScript** on this one, but that’s up to your personal preference.

As the instructions state, change into the app directory with `cd movie-emoji-quiz/`

Now install the two dependencies we need for this application:

```bash
npm install @clerk/remix faunadb
```

Next, touch `.env` to create an environment variables file and replace `<YOUR_FRONTEND_API>` and `<YOUR_CLERK_API_KEY> `with their respective values from your Clerk instance, which you can get from the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page.

```yaml {{ title: '.env' }}
CLERK_FRONTEND_API=<YOUR_FRONTEND_API>
CLERK_API_KEY=<YOUR_CLERK_API_KEY>
```

Once those environment variables are set, spin up the Remix dev server:

```bash
npm run dev
```

### Set up Clerk authentication

To share authentication state with Remix routes, we need to make three modifications to the `app/root.jsx` file:

1. Export `rootAuthLoader` as `loader`
2. Export `ClerkCatchBoundary` as `CatchBoundary`
3. Wrap the default export with `ClerkApp`

```jsx {{ title: 'app/root.jsx' }}
import { rootAuthLoader } from '@clerk/remix/ssr.server'
import { ClerkApp, ClerkCatchBoundary } from '@clerk/remix'

export const loader = (args) => rootAuthLoader(args) /* 1 */
export const CatchBoundary = ClerkCatchBoundary() /* 2 */

function App() {
  return <html lang="en">{/*...*/}</html>
}

export default ClerkApp(App) /* 3 */
```

Clerk supports Remix SSR out-of-the-box.

### Set up Fauna database

From the Fauna dashboard, create a new database. We named ours **emoji-movie-quiz** and chose the **Classic** Region Group.

We only need to create one Collection for this application. You can do so either in the Dashboard UI or in the interactive query shell.

To do it in the shell, type the following:

```javascript
CreateCollection({ name: 'challenge' })
```

Then press the **Run query** button. If it was successful, you should see similar output.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./e653aadad75fe1c67e048c30db7c942f332dd09f-972x932.png)

We’re going to seed the database with a few examples to get started. If you’ve never used the Fauna Query Language (FQL) before, the syntax may look a little funny.

```javascript
Map(
  [
    { emoji: '🔕🐑🐑🐑', title: 'Silence of the Lambs' },
    { emoji: '💍💍💍💍⚰️', title: 'Four Weddings and a Funeral' },
    { emoji: '🏝🏐', title: 'Castaway' },
    { emoji: '👽📞🏡', title: 'E.T.' },
    { emoji: '👂👀👃👅✋6️⃣', title: 'The Sixth Sense' },
  ],
  Lambda('data', Create(Collection('challenge'), { data: Var('data') })),
)
```

This will map over the examples array and create a new document with challenge data for each item in the Challenge collection.

To learn more about FQL, check out the [Fauna docs](https://docs.fauna.com/fauna/current) and this handy [cheat
sheet](https://docs.fauna.com/fauna/current/api/fql/cheat_sheet).

You can navigate to the **Collections** tab to validate that the data is all set. You can even make edits directly from the user interface if you prefer.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./3ff4a1aba4c5fe26181d70d9257ded3cdccfc59e-2674x1620.png)

While we’re here, let’s navigate over to the **Functions** tab and write a couple FQL functions we will need later.

Click the **New Function** button, name it `getChallenges`, and then paste the following function body:

```javascript
Query(
  Lambda(
    [],
    Map(
      Paginate(Documents(Collection('challenge'))),
      Lambda(
        'challenge',
        Let(
          {
            challengeRef: Get(Var('challenge')),
            data: Select('data', Var('challengeRef')),
            refId: Select(['ref', 'id'], Var('challengeRef')),
          },
          Merge(Var('data'), { id: Var('refId') }),
        ),
      ),
    ),
  ),
)
```

This function will get all the challenges and include the unique document ID inside each data object.

You can test out the functionality in the Shell by running:

```javascript
Call('getChallenges')
```

The second function we’re going to define is called `getChallengeById` and will take the unique document ID parameter and return the respective challenge data or `null` if it doesn’t exist.

```javascript
Query(
  Lambda(
    'id',
    Let(
      {
        challengeRef: Ref(Collection('challenge'), Var('id')),
        exists: Exists(Var('challengeRef')),
        challenge: If(Var('exists'), Get(Var('challengeRef')), null),
      },
      Select('data', Var('challenge'), null),
    ),
  ),
)
```

That’s all of the custom functions we’ll need here.

### Authentication integration

Although Fauna offers built-in identity and basic password authentication, it requires that you manage the user data yourself and does not provide features like prebuilt UI components, `<UserProfile />` access, and other auth strategies such as OAuth social login and [magic links](/blog/magic-links). Clerk provides these features and more without the hassle of managing your own user and identity service.

The Clerk integration with Fauna enables you to authenticate queries to your Fauna database using a JSON Web Token (JWT) created with a JWT template.

From your Clerk dashboard, navigate to the [JWT Templates](https://dashboard.clerk.com/last-active?path=jwt-templates) screen. Click the **New template** button and choose the **Fauna** template.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./46108295ebfce65e7c5446b2abe9e80b8959d5df-1386x1246.png)

Take note of the default template name of fauna (as this will come up later). You can leave the default settings and optionally add your own custom claims using convenient shortcodes.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./7e8a7f7f9821b7cb54e49094faf4e62c84a34e41-1519x1096.png)

While keeping this tab open, go back to the Fauna dashboard and navigate to the **Security** page.

Click on the **Roles** tab and then create a **New Custom Role**. Name this role `user` and give it **Read** and **Create** access to the `challenge` collection as well as **Call** permission for both `getChallenges` and `getChallengeById` functions.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./1c3605a3b7bb4cbf0b12803f1a9004a3db0834f5-1519x1039.png)

If everything looks correct, click **Save** to create the user role.

Next, click on the **Providers** tab and click on the **New Access Provider** button.

Enter `Clerk` as the name to identify this access provider.

We’re going to play a little pattycake back-and-forth between Fauna and Clerk, but bear with me and we’ll get through it together.

1. **Fauna**: Copy the **Audience** URL and go back to the Clerk JWT template tab.
2. **Clerk**: Paste the Audience URL as the value for the `aud` claim.
3. **Clerk**: Copy the **Issuer** URL.
4. **Fauna**: Paste into Issuer field.
5. **Clerk**: Copy the **JWKS Endpoint** URL.
6. **Fauna**: Paste into the JWKS endpoint field.
7. **Fauna**: Select the **user** role for access.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./7cafa54652ac1e960b91ec6faac5cf40746b526b-786x695.png)

After those fields have been set, you can save both the Clerk JWT template and Fauna access provider. Whew! That was fun.

### Add Clerk auth to Remix

Now we can get back into building our application. Open the Remix project in your code editor of choice.

In Remix, `app/root.jsx` wraps your entire application in both server and browser contexts.

Clerk requires a few modifications to this file so the authentication state can be shared with your Remix routes. First, add these imports to `app/root.jsx`:

```jsx
import { rootAuthLoader } from '@clerk/remix/ssr.server'
import { ClerkApp, ClerkCatchBoundary } from '@clerk/remix'
```

Second, export `rootAuthLoader` as `loader` taking in the args parameter.

```javascript
export const loader = (args) => rootAuthLoader(args)
```

Next, we need to export the `ClerkCatchBoundary` as `CatchBoundary` to handle expired authentication tokens. If you want your own custom boundary, you can pass it in as the first argument.

```javascript
export const CatchBoundary = ClerkCatchBoundary()
```

And finally, we need to wrap the default export with the `ClerkApp` higher-order component.

```jsx
export default ClerkApp(function App() {
  return <html lang="en">{/*[...]*/}</html>
})
```

That’s all that’s needed to install and configure Clerk for authentication. The next step will include adding sign in functionality.

### Add SignIn to index route

We’re going to use the Clerk hosted `<SignIn />` component to render the sign in form.

Update `app/routes/index.jsx` with the following:

```jsx {{ title: 'app/routes/index.jsx' }}
import { SignIn } from '@clerk/remix'
import styles from '~/styles/index.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

export default function Index() {
  return (
    <div>
      <main>
        <div className="content">
          <SignIn />
        </div>
      </main>
    </div>
  )
}
```

We’re using Remix’s [route styles](https://remix.run/docs/en/v1/guides/styling#route-styles) functionality to dynamically add a stylesheet to this route.

Create a `styles` directory inside of the `app` folder and save the following as `index.css`:

app/styles/index.css

```css
@font-face {
  font-family: 'color-emoji';
  src:
    local('Apple Color Emoji'), local('Segoe UI Emoji'), local('Segoe UI Symbol'),
    local('Noto Color Emoji');
}

:root {
  --font-body:
    'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif,
    color-emoji;
  --color-error: #c10500;
  --color-success: #15750b;
}

body {
  margin: 0;
  font-family: var(--font-body);
}

header {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  background-color: #fd890f;
  box-shadow: 0 2px 2px 1px rgba(0, 0, 0, 0.2);
}

.actions {
  display: flex;
  align-items: center;
}

.actions > a {
  margin: 0 20px;
  color: #fff;
  font-size: 14px;
  text-decoration: none;
}

.actions > a:hover,
.actions > a:focus {
  color: rgba(255, 255, 255, 0.8);
}

.logo {
  color: #fff;
  font-size: 22px;
  font-weight: 600;
}

main {
  display: flex;
  height: calc(100vh - 56px);
}

aside {
  width: 280px;
  height: 100%;
  overflow-y: scroll;
  flex-shrink: 0;
  background-color: #f5f5f4;
  border-right: 1px solid #d8d8d4;
}

aside h2 {
  margin: 20px 20px 10px;
  font-size: 18px;
}

aside ul {
  list-style: none;
  padding: 0;
}

aside li {
  font-size: 28px;
}

aside li:not(:last-child) {
  border-bottom: 1px solid #d8d8d4;
}

aside li > a {
  display: block;
  padding: 8px 20px;
  text-decoration: none;
}

aside li > a:hover,
aside li > a:focus,
aside li > a.active {
  background-color: #ececea;
}

.content {
  width: 100%;
  height: calc(100vh - 40px);
  padding: 40px 20px 0;
  background-color: #e5e6e4;
  text-align: center;
}

.content h1 {
  margin-bottom: 8px;
}

@font-face {
  font-family: 'color-emoji';
  src:
    local('Apple Color Emoji'), local('Segoe UI Emoji'), local('Segoe UI Symbol'),
    local('Noto Color Emoji');
}

:root {
  --font-body:
    'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif,
    color-emoji;
  --color-error: #c10500;
  --color-success: #15750b;
}

body {
  margin: 0;
  font-family: var(--font-body);
}

header {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  background-color: #fd890f;
  box-shadow: 0 2px 2px 1px rgba(0, 0, 0, 0.2);
}

.actions {
  display: flex;
  align-items: center;
}

.actions > a {
  margin: 0 20px;
  color: #fff;
  font-size: 14px;
  text-decoration: none;
}

.actions > a:hover,
.actions > a:focus {
  color: rgba(255, 255, 255, 0.8);
}

.logo {
  color: #fff;
  font-size: 22px;
  font-weight: 600;
}

main {
  display: flex;
  height: calc(100vh - 56px);
}

aside {
  width: 280px;
  height: 100%;
  overflow-y: scroll;
  flex-shrink: 0;
  background-color: #f5f5f4;
  border-right: 1px solid #d8d8d4;
}

aside h2 {
  margin: 20px 20px 10px;
  font-size: 18px;
}

aside ul {
  list-style: none;
  padding: 0;
}

aside li {
  font-size: 28px;
}

aside li:not(:last-child) {
  border-bottom: 1px solid #d8d8d4;
}

aside li > a {
  display: block;
  padding: 8px 20px;
  text-decoration: none;
}

aside li > a:hover,
aside li > a:focus,
aside li > a.active {
  background-color: #ececea;
}

.content {
  width: 100%;
  padding: 40px 20px 0;
  background-color: #e5e6e4;
  text-align: center;
}

.content h1 {
  margin-bottom: 8px;
}

.emoji {
  display: block;
  font-size: 80px;
  line-height: 1.2;
  margin: 20px auto 10px;
}

.error > p {
  color: var(--color-error);
  font-size: 18px;
}
```

Your app should now look something like:

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./dc28c45c9b45ff09ce76b659a41a7841ddf9e1a2-2258x1618.png)

If you see a blank screen, you may be already signed in. We will handle that case momentarily.

Inside of the `app` directory, create a folder called `components` and a file called `header.jsx`.

Drop in the following code:

```jsx {{ title: 'app/components/header.jsx' }}
import { SignedIn, UserButton } from '@clerk/remix'

export default function Header() {
  return (
    <header>
      <span className="logo">Movie Emoji Quiz</span>
      <SignedIn>
        <UserButton />
      </SignedIn>
    </header>
  )
}
```

Here we’re making use of the `<SignedIn>` control flow component and the `<UserButton />` which will allow us to edit our profile and sign out.

Go back to `app/routes/index.jsx` and import the `<Header />` component and add it just above the `<main>` element:

```jsx {{ title: 'app/routes/index.jsx' }}
import Header from '../components/header'

export default function Index() {
  return (
    <div>
      <Header />
      <main>
        <div className="content">
          <SignIn />
        </div>
      </main>
    </div>
  )
}
```

If you sign in, you should now see your avatar. Click on it to see the user profile menu.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./4b528fa3e84cf5949c2047dfa5bfba9e13a0186f-1988x1488.png)

You can now sign in, sign out, and manage your account. Clerk makes it super easy with their hosted components.

### Authenticate with the Fauna client

In order to start fetching data from our Fauna database, we need to set up the Fauna client.

Create a `utils` folder inside of `app` and a file named `db.server.js`.

The `.server` naming convention is a hint to the compiler to ignore this file in the browser bundle.

Add the following code:

```javascript {{ title: 'app/utils/db.server.js' }}
import { getAuth } from '@clerk/remix/ssr.server'
import faunadb from 'faunadb'

export const getClient = async (request) => {
  const { userId, getToken } = await getAuth(request)

  if (!userId) {
    return null
  }

  const secret = await getToken({ template: 'fauna' })

  return new faunadb.Client({ secret })
}

export const q = faunadb.query
```

Here we are using the `getAuth` function from Clerk to check if we have a `userId` (e.g. if the user is signed in) and to get access to the `getToken` function, which when called with our Fauna JWT template name (it was simply “fauna” if you remember), will be passed to the Fauna client as the authentication secret.

If the user is signed in and we get access to the Fauna JWT template, we should then be able to make queries against our Fauna database.

We are also exporting `q` here, which is a convention when using `faunadb.query`. This way all our database helper functions are kept in the same place.

## Display movie challenges

Now it’s time to display some of these challenges.

First let’s create a new route at `/challenges`. We do this by creating a file `app/routes/challenges.jsx` and populating it with the following:

```jsx {{ title: 'app/routes/challenges.jsx' }}
import { Outlet } from '@remix-run/react'
import Header from '../components/header'
import styles from '~/styles/index.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

export default function Challenges() {
  return (
    <div>
      <Header />
      <main>
        <div className="content">
          <Outlet />
        </div>
      </main>
    </div>
  )
}
```

This should look the same as the index route, with the exception being `<SignIn />` is replaced with the Remix `<Outlet />` component.

Next, import the database utils we previous created and export a `loader` function with the below code:

```javascript
import { getClient, q } from '../utils/db.server'

export const loader = async ({ request }) => {
  const client = await getClient(request)

  if (!client) {
    return null
  }

  const response = await client.query(q.Call('getChallenges'))

  // Check your terminal for response data
  console.log(response)

  return json(response)
}
```

You can see we’re using the Fauna client to perform a FQL query to call the `getChallenges` function we created. If all went well, check your terminal window (not the browser console) and you should see the response data.

Create a new file `app/components/sidebar.jsx` that will loop over this data and create links to each challenge based on its ID.

```jsx {{ title: 'app/components/sidebar.jsx' }}
import { NavLink } from '@remix-run/react'

export default function Sidebar({ data }) {
  return (
    <aside>
      <h2>Guess these movies...</h2>
      <ul>
        {data?.map((movie) => (
          <li key={movie.id}>
            <NavLink to={`/challenges/${movie.id}`}>{movie.emoji}</NavLink>
          </li>
        ))}
      </ul>
    </aside>
  )
}
```

Import the `<Sidebar />` component in the index route directly under the `main` element.

To access the data from the loader, we will use the aptly named `useLoaderData` hook from Remix and pass the `data` property to the `Sidebar` component:

```jsx
export default function Challenges() {
  const { data } = useLoaderData()

  return (
    <div>
      <Header />
      <main>
        <Sidebar data={data} />
        <div className="content">
          <Outlet />
        </div>
      </main>
    </div>
  )
}
```

If you visit [http://localhost:3000/challenges](http://localhost:3000/challenges), you should now see the emoji challenges rendered as links in the sidebar:

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./22b986b5e553c075663f766a040df434cfde6167-2192x1488.png)

If you click on the links, they will cause an error because we haven’t created those individual challenge routes. Let’s do that now.

### Create challenge route

Let’s create a new stylesheet that will hold the challenge styles at `app/styles/challenge.css`.

app/styles/index.css

```css
.emoji {
  display: block;
  font-size: 80px;
  line-height: 1.2;
  margin: 20px auto 10px;
}

.author {
  padding-bottom: 10px;
  color: #8a8a8a;
  font-size: 14px;
}

form {
  display: flex;
  flex-flow: column wrap;
  align-items: center;
  justify-content: center;
  margin-top: 20px;
}

label {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 20px;
}

input[type='text'] {
  min-width: 280px;
  padding: 8px;
  border: 1px solid #ccc;
  font-family: var(--font-body);
  font-size: 16px;
}

.submit-btn {
  appearance: none;
  margin-top: 40px;
  padding: 12px 24px;
  background-color: #7180ac;
  border: 1px solid #6575a4;
  border-radius: 4px;
  cursor: pointer;
  color: #fff;
  font-family: var(--font-body);
  font-size: 14px;
}

.submit-btn:hover,
.submit-btn:focus {
  background-color: #6575a4;
}

.form-field {
  display: flex;
  align-items: baseline;
  margin-top: 20px;
  text-align: left;
}

.form-field label {
  display: block;
  min-width: 50px;
  margin: 0 16px 0 0;
}

.form-validation-error {
  color: var(--color-error);
  font-size: 14px;
  margin-top: 4px;
  margin-bottom: 0;
}

.message {
  margin: 8px 0 0;
  font-size: 16px;
}

.message--correct {
  color: var(--color-success);
}

.message--incorrect {
  color: var(--color-error);
}

.reveal {
  position: relative;
}

.reveal-btn {
  position: relative;
  z-index: 2;
  appearance: none;
  width: 300px;
  margin: 24px auto;
  padding: 16px 24px;
  background: #f5f5f4;
  border: 1px solid #d8d8d4;
  transition: opacity 1.5s ease;
  cursor: help;
  font-family: var(--font-body);
  font-size: 14px;
}

.reveal-btn:hover {
  opacity: 0;
}

.reveal-text {
  position: absolute;
  top: 40px;
  left: 0;
  width: 100%;
  font-size: 14px;
}
```

Next, create a file inside a folder at the path `app/routes/challenges/$id.jsx`

The `$` prefix is important here as it creates a [dynamic segment](https://remix.run/docs/en/v1/guides/routing#dynamic-segments).

Add the following code:

```jsx {{ title: 'app/routes/challenges/$id.jsx' }}
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { getClient, q } from '../../utils/db.server'
import styles from '~/styles/challenge.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

export const loader = async ({ params, request }) => {
  const client = await getClient(request)

  if (isNaN(params.id)) {
    throw new Response('Challenge not found', {
      status: 404,
    })
  }

  const challenge = await client.query(q.Call('getChallengeById', params.id))

  if (!challenge) {
    throw new Response('Challenge not found', {
      status: 404,
    })
  }

  return json(challenge)
}

export default function Challenge() {
  const { emoji } = useLoaderData()

  return (
    <div>
      <span className="emoji">{emoji}</span>
    </div>
  )
}
```

This code follows a similar pattern to what we did in the sidebar with the `loader` and the `useLoaderData` hook. The difference here is we’re calling the `getChallengeById` FQL function and passing it the `id` parameter, which comes from the `$id` dynamic route.

We are also using a Remix convention of throwing `Response` objects for error scenarios (e.g. invalid ID, challenge not found).

If you click on the links in the sidebar, you should now see each emoji challenge rendered in its full glory (a large type size).

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./91eb7782b4592ebde7e96f1c0023f4fea6e576d5-2090x1572.png)

### Challenge form

Now it’s time to add the form to submit guesses for the emoji challenges. Form handling is one area where Remix really shines.

Add the following form markup below the emoji:

```jsx
<form method="post" autoComplete="off">
  <label htmlFor="guess">What movie is this?</label>
  <input id="guess" type="text" name="guess" placeholder="Enter movie title..." required />
  <button className="submit-btn">Submit guess</button>
</form>
```

This is pretty standard JSX form markup. Nothing too fancy going on here.

Let’s import our DB utils as well as the `json` Response helper from Remix.

```javascript
import { json } from '@remix-run/node'
import { getClient, q } from '~/utils/db.server'
```

Then export the following action function:

```javascript
export const action = async ({ params, request }) => {
  const form = await request.formData()
  const guess = form.get('guess')
  const client = await getClient(request)
  const challenge = await client.query(q.Call('getChallengeById', params.id))
  const isCorrect = guess.toLowerCase() === challenge.title.toLowerCase()

  return json({
    guessed: isCorrect ? 'correct' : 'incorrect',
    message: isCorrect ? 'Correct! ✅' : 'Incorrect! ❌',
    answer: challenge.title,
  })
}
```

Here we’re using native [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) methods to read the guess input and comparing it against the challenge title we get by calling our FQL `getChallengeById` function with the challenge ID from the route params.

Based on whether the guess matches the title (case insensitive), we return an appropriate JSON response. We can access the action data using the `useActionData` hook (also aptly named).

```javascript
import { useActionData, useLoaderData } from '@remix-run/react'
```

Now we can update the UI to display the correct message based on the guess submitted.

```jsx
export default function Challenge() {
  const { emoji } = useLoaderData()
  const data = useActionData()

  return (
    <div>
      <span className="emoji">{emoji}</span>
      <form method="post" autoComplete="off">
        <label htmlFor="guess">What movie is this?</label>
        <input id="guess" type="text" name="guess" placeholder="Enter movie title..." required />
        {data?.guessed ? (
          <p className={`message message--${data.guessed}`}>{data.message}</p>
        ) : null}
        <button className="submit-btn">Submit guess</button>
        {data?.guessed === 'incorrect' ? (
          <div className="reveal">
            <button className="reveal-btn" type="button">
              Reveal answer
            </button>
            <span className="reveal-text">{data?.answer}</span>
          </div>
        ) : null}
      </form>
    </div>
  )
}
```

If an incorrect guess is submit, we provide the user a way to reveal the answer.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./125715f68cfbb3512476f88b2e28ef96d0154475-2090x1572.png)

The form should now work to submit correct and incorrect guesses.

### Handling transitions

Because the form is shared between different challenge routes, if you enter text in one input and go to another challenge, you will see the input value is not being cleared.

We can fix this by using the `useTransition` hook, putting a ref on the form element, and then resetting the form when the transition is a normal page load.

```jsx
export default function Challenge() {
  const { emoji } = useLoaderData()
  const data = useActionData()
  const transition = useTransition()
  const ref = useRef()

  useEffect(() => {
    if (transition.type == 'normalLoad') {
      // Reset form on route change
      ref.current && ref.current.reset()
    }
  }, [transition])

  return (
    <div>
      <span className="emoji">{emoji}</span>
      <form ref={ref} method="post" autoComplete="off">
        <label htmlFor="guess">What movie is this?</label>
        <input id="guess" type="text" name="guess" placeholder="Enter movie title..." required />
        {data?.guessed ? (
          <p className={`message message--${data.guessed}`}>{data.message}</p>
        ) : null}
        <button className="submit-btn">Submit guess</button>
        {data?.guessed === 'incorrect' ? (
          <div className="reveal">
            <button className="reveal-btn" type="button">
              Reveal answer
            </button>
            <span className="reveal-text">{data?.answer}</span>
          </div>
        ) : null}
      </form>
    </div>
  )
}
```

You will need to add the necessary hook imports from React and Remix.

```javascript
import { useEffect, useRef } from 'react'
import { useActionData, useLoaderData, useTransition } from '@remix-run/react'
```

After that is added, the form should clear when choosing a different challenge.

## Submit new challenges

This app is not much so much fun if users can’t submit their own movie emoji challenges. So that’s the functionality we’re going to add now.

As with most things in Remix, the first thing we need to do is create a new route.

Create `app/routes/challenges/new.jsx` with the following:

```jsx {{ title: 'app/routes/challenges/new.jsx' }}
import { getAuth } from '@clerk/remix/ssr.server'
import { json, redirect } from '@remix-run/node'
import { Form, useActionData } from '@remix-run/react'
import { getClient, q } from '~/utils/db.server'
import styles from '~/styles/challenge.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

const badRequest = (data) => json(data, { status: 400 })

const validateEmoji = (emoji) =>
  !emoji.trim() || /\p{L}|\p{N}(?!\uFE0F)|\p{Z}/gu.test(emoji)
    ? 'Please enter only emoji'
    : undefined

const validateTitle = (title) =>
  title && title.length > 1 ? undefined : 'Please enter a movie title'

export const action = async ({ request }) => {
  const form = await request.formData()
  const emoji = form.get('emoji')
  const title = form.get('title')

  if (typeof emoji !== 'string' || typeof title !== 'string') {
    return badRequest({
      formError: 'Form not submitted correctly.',
    })
  }

  const fieldErrors = {
    emoji: validateEmoji(emoji),
    title: validateTitle(title),
  }

  if (Object.values(fieldErrors).some(Boolean)) {
    return badRequest({
      fieldErrors,
      fieldValues: {
        emoji,
        title,
      },
    })
  }

  const { userId } = await getAuth(request)
  const client = await getClient(request)
  const data = {
    emoji,
    title,
    userId,
  }

  const response = await client.query(q.Create('challenge', { data }))

  return redirect(`/challenges/${response.ref.value.id}`)
}

export default function NewRoute() {
  const actionData = useActionData()

  return (
    <div>
      <h1>Create new challenge</h1>
      <Form method="post" autoComplete="off">
        <div className="form-field">
          <label htmlFor="emoji">Emoji</label>
          <input id="emoji" type="text" name="emoji" />
        </div>
        {actionData?.fieldErrors?.emoji ? (
          <p className="form-validation-error" role="alert" id="name-error">
            {actionData.fieldErrors.emoji}
          </p>
        ) : null}
        <div className="form-field">
          <label htmlFor="title">Movie</label>
          <input id="title" type="text" name="title" />
        </div>
        {actionData?.fieldErrors?.title ? (
          <p className="form-validation-error" role="alert" id="name-error">
            {actionData.fieldErrors.title}
          </p>
        ) : null}
        {actionData?.formError ? (
          <p className="form-validation-error" role="alert">
            {actionData.formError}
          </p>
        ) : null}
        <button className="submit-btn">Submit challenge</button>
      </Form>
    </div>
  )
}
```

This time we’re using the `Form` component provided by Remix, which helps with automatically serializing the values. There’s validation logic to check for valid emoji and titles. If the validation passes, we create a new challenge. We use the `getAuth` function from Clerk to send in the user ID along with the emoji and title. These values form the data for a new challenge document in Fauna. On a successful document creation, the user is redirected to the new challenge page.

Let’s add a link to the header so we can get to this page. It’s wrapped with `<div className="actions">` to provide the necessary styling.

```jsx {{ title: 'app/components/header.jsx' }}
import { SignedIn, UserButton } from '@clerk/remix'
import { Link } from '@remix-run/react'

export default function Header() {
  return (
    <header>
      <span className="logo">Movie Emoji Quiz</span>
      <SignedIn>
        <div className="actions">
          <Link to="/challenges/new">Submit challenge</Link>
          <UserButton />
        </div>
      </SignedIn>
    </header>
  )
}
```

Clicking the link should take you to the page where you can create a new challenge. You should see validation errors if you don’t fill out the form properly.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./96ba5c89aa2784720ee94206c12557645c742168-2482x1550.png)

Once submitted, you will be redirected to the new challenge page. Because the action created a new mutation, Remix will automatically update the data in the sidebar. Neat!

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./0a87785674c2ec965c2c6b54bcf92e8a8e98f967-2482x1550.png)

You now have a working Movie Emoji Quiz app. If you’re ready to share it with your family and friends, Remix makes deployment very easy and has support for various [deployment targets](https://remix.run/docs/en/v1/guides/deployment). Using the [Vercel CLI,](https://vercel.com/cli) all you need to deploy your Remix app is run:

```bash
npm i -g vercel
vercel
```

And your app will be live within minutes!

## Next steps

To take this app even further, you can do the following:

- Add [catch boundaries](https://remix.run/docs/en/v1/guides/not-found) and redirects to handle error cases and invalid challenge routes
- Create Remix routes for `/sign-in` and `/sign-out` to use [mounted Clerk components](/docs/references/javascript/clerk/sign-in#sign-in-component)
- Use the [Clerk User API](/docs/reference/backend-api) to query the username for each user user-submitted challenge so you can give them credit for their cleverness
- Keep track of correct guesses and display an indicator in the UI so the user knows which challenges they have yet to tackle

If you enjoyed this tutorial or have any questions, feel free to reach out to me ([**@devchampian**](https://x.com/devchampian)) on X, follow [**@clerk**](https://x.com/clerk), or join our [**Discord community**](https://clerk.com/discord) to connect with other developers. Happy coding!

---

# Ultimate Guide to Magic Link Authentication
URL: https://clerk.com/blog/magic-links.md
Date: 2023-12-20
Category: Guides
Description: In this post, we discuss the benefits of email magic links, show examples of how they work, and explain why they meet the requirements for secure, passwordless authentication.

Data breaches and password overload have made companies and their users wary of using a traditional username/password authentication system. Companies know that handling user passwords is both technically challenging ***and*** costly, as it requires stringent security measures, robust infrastructure for storage, and continuous monitoring to prevent unauthorized access. Users find it cumbersome to manage multiple complex passwords for various accounts, especially with the prevalence of methods like SSO, and often end up reusing passwords across different platforms, as a matter of convenience; this not only leaves the individual vulnerable, but also makes companies jobs of securing data that much harder, highlighting the critical requirements for improved security measures.

Luckily, email magic links have emerged as an elegant solution to secure user sessions in a [passwordless](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategy) context. Their rising popularity is underpinned by their dual advantages:

- An augmented security posture through the minimization of phishing opportunities and password theft.
- An optimized user experience devoid of the need to memorize and manage many login credentials.

## What are Magic Links?

Magic links are a token-based authentication (TBA) strategy that uses a unique, time-sensitive URL, which leverages a securely-generated token to serve as a credential for user authentication.

The links are sent directly to the user's registered email or phone number, providing a straightforward, secure authentication method. When a user clicks on a magic link, the embedded token is validated against the server to authenticate the user's identity. This process, by design, eliminates the traditional risks associated with password-based systems and simplifies the login experience for the user.

In this guide, we will show you why you should consider authentication with magic links and how they work at a high level, before going through [a Next.js App Router implementation](/blog/secure-authentication-nextjs-email-magic-links) to show how you can add magic links to your application.

## The Benefits of Magic Links

Magic links bolster the security architecture of authentication systems by adopting a token-based, stateless interaction model. Each link is cryptographically unique and typically accompanied by an expiration timestamp, making it resilient against replay attacks. Given their ephemeral nature, even if a magic link were to be intercepted or exposed, its short-lived validity constrains the window of opportunity for malicious exploitation.

But there are a few other benefits to magic links for companies and users:

- **Streamlined user experience**. Authentication with magic links eliminate the cognitive burden of remembering and managing many complex passwords, by providing a single-click authentication pathway. This frictionless access modality can enhance user engagement and satisfaction, reducing abandonment rates during the sign-in or sign-up processes.
- **Compliance and data protection**. By using magic links to minimize passwords, organizations can reduce the risk of breaches involving personal data and avoid the consequences of compromised credentials, which can include heavy fines and reputational damage.
- **Reduction in credential exposure**. With magic links, the threat of credential stuffing attacks—where compromised credentials are used to gain unauthorized access to multiple user accounts—is mitigated. Since magic links do not rely on reusable passwords, the standard vector of credential exposure is eliminated, enhancing overall system security.
- **Improved accessibility**. For users with disabilities or those less familiar with technology, magic links offer a more accessible authentication mode. The simplification of the login process—replacing typing and memory demands with a single action—can be particularly advantageous for individuals facing physical or cognitive challenges.

## How Magic Links Work

A magic link is structurally composed of two critical elements: the URL, which provides the link for the user's web interaction, and the embedded token, a cryptographically-generated string serving as the temporary credential.

In essence, a typical magic link may resemble the following structure:

`https://example.com/authenticate?token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`

In this example, the `token` query parameter carries the weight of authentication, substantiating the user's claim without revealing identity until verified by the server.

Here’s an example of how the process works:

![Example Magic Link Authentication Process](./387a0a12d7a188ea6c83f3b15c54bf37e63e6462-2400x1392.png)

### Token Validity

Magic links are designed to become invalid under certain conditions for security purposes:

- **Expiration**: The token embedded in the link has a short lifespan and is often configurable based on application security requirements.
- **Usage Limitation**: Once a magic link is used, it becomes invalid, preventing multiple uses, which could lead to unauthorized access.
- **Revocation**: The system can programmatically revoke a token, thus invalidating the magic link in response to specific triggers or anomalies.

### Token Generation

The lifecycle of a magic link commences with the generation of a unique token. This process employs cryptographic algorithms to ensure each token is a random, high-entropy string, making it virtually impossible to predict or reproduce through brute force or other cryptographic attacks. Typically, the token generation utilizes [HMAC](https://en.wikipedia.org/wiki/HMAC) or AES combined with a [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) to guarantee the robustness of the token against collision and preimage attacks.

Once generated, the token is stored on the server along with metadata that includes the user's identifier, the token's expiration time, and any other relevant session data. The magic link is then composed by appending the token to a predetermined URL structure, forming a complete, ready-to-use hyperlink.

This link is dispatched to the user's email address or phone number via SMTP or SMS protocols. The communication channel must be secure, leveraging TLS for email and similarly secure protocols for SMS to safeguard the link during transit.

### User Interaction and Verification

A user typically interacts with the magic link by clicking on it, which initiates a secure request to the service's endpoint. The service extracts the token from the URL and verifies it against the stored data. This verification process involves several checks:

- **Authenticity**: Validates that the token matches a recently generated and stored token.
- **Integrity**: Ensures the token has not been tampered with during transit.
- **Timeliness**: Confirms the token has not expired based on the predefined validity period.
- **Non-reuse**: Ensures the token has not been used before, adhering to the principle of one-time use.

The server considers the authentication request legitimate if the token passes these checks.

### Session Initialization

Post-verification, the server establishes a session for the user. This session is typically stateless, with a new session token or cookie generated to maintain the user's authenticated state in the application. This session token is separate from the magic link token. It has its own security considerations, such as being HttpOnly and Secure, to prevent access via client-side scripts and ensure transmission over HTTPS only.

## Building a Magic Link System

Let’s create our own email with magic link authentication. In this example, we’ll use [Next.js](https://nextjs.org) 13 with the App Router. We’ll also use [Supabase](https://supabase.com) for our backend database to store users and tokens.

First, let’s create a new Next.js project:

```sh
npx create-next-app@latest
```

Follow the prompts to select how you want to configure your app, but be sure to select “Yes” for “Would you like to
use App Router? (recommended)”.

Once you have created and configured your project, `cd` into the created directory and run `npm run dev` to start it. You’ll see just the default Next.js homepage when you load `localhost:3000`.

Before we start building out our project, we need to install a few dependencies that we’ll use. Install them using:

```sh
npm install nodemailer jsonwebtoken @supabase/supabase-js
```

What do these do?

- [nodemailer](https://www.npmjs.com/package/nodemailer): A library that allows for easy email sending.
- [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken): A library to implement JSON Web Tokens for secure data transmission.
- [@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js): The Supabase client library for Javascript.

With those installed, let’s open up the project in an IDE. In total, we’re going to have two pages, two API routes, and two helper libraries:

- `page.js` will be our homepage. It will have a simple email field, and will call our `requestMagicLink` API reroute.
- `requestMagicLink.js` will be the API route that will create our magic link, send it to the email address passed from `page.js`, and save the token on our Supabase database.
- `verify.js` will be the API route called when the user clicks on the magic link in their email. It will verify the token and redirect the user to the protected dashboard page.
- `dashboard/page.js` will be a simple mock “protected page” (that would require the user to be logged in to view it).
- `lib/database.js` will be a number of database helper functions to save, load, and delete Supabase data.
- `lib/supabaseClient.js` will set up our Supabase client.

### page.js

Let’s start with what the user will see, `page.js`:

```jsx
// app/page.js
'use client'
import { useState } from 'react'

export default function RequestMagicLink() {
  const [email, setEmail] = useState('')
  const [message, setMessage] = useState('')
  const [isLoading, setIsLoading] = useState(false)

  const handleSubmit = async (event) => {
    event.preventDefault()
    setIsLoading(true)
    setMessage('')

    try {
      const response = await fetch('/api/auth/requestMagicLink', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
      })

      const data = await response.json()

      if (response.ok) {
        setMessage('Magic link sent! Check your email to log in.')
      } else {
        setMessage(data.error || 'An error occurred. Please try again.')
      }
    } catch (error) {
      setMessage('An error occurred. Please try again.')
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
        required
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Sending...' : 'Send Magic Link'}
      </button>
      {message && <p>{message}</p>}
    </form>
  )
}
```

In the code example above, we are requesting the `RequestMagicLink` component, which is responsible for handling the functionality of requesting a magic link via an email address. This component provides a UI for users to request a magic link. Users enter their email and submit the form, triggering a request to the server. Feedback is given to the user through messages and button state changes during the process.

First, we set up the state variables for the page. In the Next.js App Router, you can only use `useState` in client-side components. As all components default to server-side, we have to use the `“use client”` directive at the top of the file to show this page has to be rendered on the client. We have three state variables:

- `email`: Stores the user's email address. It's updated every time the user types into the email input field.
- `message`: Used to display messages to the user, like confirmation or error messages.
- `isLoading`: Indicates whether the request is being processed. It's used to disable the submit button and change the button text while the request is in progress.

After that, we have the `handleSubmit` function. This is triggered when the form is submitted, and initially prevents the default form submission action with `event.preventDefault()`. It then sets `isLoading` to `true` to indicate the start of an asynchronous operation and clears any previous messages stored in `message`.

Then, comes the core part of the component. We make an async `POST` request to the `/api/auth/requestMagicLink` endpoint with the user's email in the request body. We then update the `message` state based on the success or failure of the request:

- If the request is successful (`response.ok` is true), it sets a success message.
- If the request fails, it displays an error message, either from the response or a generic error message.

We have some basic error catching, and then set `isLoading` to `false`.

The actual form presented to the user is basic, with just an email input field that updates the `email` state variable on change and a submit button that is disabled and changes its text based on the `isLoading` state. We have an `onSubmit` event handler linked to `handleSubmit` to send the form details and a paragraph that displays any messages stored in the `message` state.

This `page.js` file calls `requestMagicLink.js`; let’s dig into that, next.

### requestMagicLink.js

```javascript
// app/api/auth/requestMagicLink.js
import jwt from 'jsonwebtoken'
import nodemailer from 'nodemailer'
import { headers } from 'next/headers'

import { saveToken, getUserByEmail } from '../../../../lib/database'

export async function POST(req, res) {
  const { email } = await req.json()

  const user = await getUserByEmail(email)

  if (!user) {
    return Response.json({ message: 'User note found' })
  }

  // Create a magic link token
  const token = jwt.sign({ email }, process.env.JWT_SECRET, {
    expiresIn: '1h',
  })
  const headersList = headers()
  const host = headersList.get('host')
  const magicLink = `http://${host}/api/auth/verify?token=${token}`

  // Save token in your database with an expiration time
  await saveToken(user.id, token)

  // Set up email transporter and send the magic link
  const transporter = nodemailer.createTransport({
    host: '<email-host>',
    port: 587,
    secure: false, // upgrade later with STARTTLS
    auth: {
      user: '<email-username>',
      pass: '<email-password>',
    },
  })

  await transporter.sendMail({
    from: '<from-address>',
    to: email,
    subject: 'Your Magic Link',
    text: `Click here to log in: ${magicLink}`,
  })

  return Response.json({ message: 'Magic link sent!' })
}
```

This example code handles the API requests related to generating and sending the magic link for user authentication. Let's go through the major components and functions of the code.

The function takes `req` (request) and `res` (response) objects as parameters, and then extracts the `email` from the request's JSON body. We then use `getUserByEmail` from `lib/database.js` to search for a user in the database using the provided email. If the user doesn’t exist we send a JSON response indicating the user was not found.

The function then creates the token using `jsonwebtoken` to create a JWT with the user's email, signing it with a secret from environment variables and setting an expiration of 1 hour. With that token we can create our magic link. We retrieve the host from the request headers and construct a URL with the generated token as a query parameter.

The generated token is then saved in the database with an associated user ID using `saveToken`.

After that, we configure a `nodemailer` transporter with SMTP settings (host, port, security, and authentication credentials) and send an email to the user with the magic link authentication.

Finally, we send back a JSON response indicating that the magic link was sent.

Here, we using one of our helper libraries, `database.js`. Let’s go through that next.

### database.js

```javascript
// lib/database.js

import { supabase } from './supabaseClient'

export const saveToken = async (userId, token) => {
  const { data, error } = await supabase.from('magic_tokens').insert([
    {
      user_id: userId,
      token: token,
      expires_at: new Date(Date.now() + 3600000),
    }, // expires in 1 hour
  ])

  if (error) throw new Error(error.message)
  return data
}

export const getUserByEmail = async (email) => {
  const { data, error } = await supabase.from('users').select('*').eq('email', email).single()

  console.log(data)
  if (error) throw new Error(error.message)
  return data
}

export const getTokenData = async (token) => {
  const { data, error } = await supabase
    .from('magic_tokens')
    .select('*')
    .eq('token', token)
    .single()

  if (error) throw new Error(error.message)
  if (new Date(data.expires_at) < new Date()) {
    throw new Error('Token expired')
  }
  return data
}

export const deleteUserToken = async (token) => {
  const { data, error } = await supabase.from('magic_tokens').delete().match({ token: token })

  if (error) throw new Error(error.message)
  return data
}
```

This is a collection of utility functions designed to interact with Supabase. Each function is designed to interact with specific tables in a Supabase database, which each handle different aspects like token generation, user lookup, token validation, and cleanup. Let's break down each function:

### saveToken

- **Purpose**: To save a newly generated token in the database, fulfilling the requirements for secure token management.
- **Parameters**: Accepts `userId` (the user's ID) and `token` (the magic link token).
- **Functionality**:
  - Inserts a new record into the `magic_tokens` table in Supabase with the user's ID, the token, and an expiration time (set to 1 hour ahead of the current time).
  - If an error occurs during the database operation, it throws an error with the message received from Supabase.
  - Returns the data received from Supabase if the operation is successful.

### getUserByEmail

- **Purpose**: To fetch a user's data from the database using their email address.
- **Parameters**: Accepts `email`, which is the email address of the user.
- **Functionality**:
  - Queries the `users` table in Supabase for a record matching the provided email address.
  - Logs the email and the data received for debugging purposes.
  - If an error occurs, it throws an error with the message from Supabase.
  - Returns the user data if a matching record is found.

### getTokenData

- **Purpose**: To retrieve token data from the database.
- **Parameters**: Accepts `token`, the magic link token.
- **Functionality**:
  - Queries the `magic_tokens` table for a record with a token matching the provided one.
  - Checks if the token has expired by comparing the `expires_at` field with the current time. If the token is expired, it throws an error.
  - If an error occurs during the query, it throws an error with the message from Supabase.
  - Returns the token data if a matching and non-expired token is found.

### deleteUserToken

- **Purpose**: To delete a token from the database, complying with the requirements for token lifecycle management.
- **Parameters**: Accepts `token`, the magic link token to be deleted.
- **Functionality**:
  - Deletes the record from the `magic_tokens` table that matches the provided token.
  - If an error occurs during this operation, it throws an error with the message from Supabase.
  - Returns the data received from Supabase upon successful deletion.

This file, in turn, is calling on the other helper library `supabaseClient.js`.

### supabaseClient.js

```javascript
// lib/supabaseClient.js

import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.SUPABASE_URL
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```

This is a straightforward setup for initializing a client instance of Supabase in a Javascript application.

`export const supabase = createClient(supabaseUrl, supabaseAnonKey);`creates and exports an instance of the Supabase client, which is used to interact with your Supabase project. The `createClient` function takes the Supabase URL and the anonymous key as arguments and returns the initialized client.

`SUPABASE_URL` and `SUPABASE_ANON_KEY` (along with `JWT_SECRET`) are pulled from our environment variables file, `.env.local`.

### .env.local

```sh
SUPABASE_URL=<supabase-url>
SUPABASE_ANON_KEY=<supabase-anon-key>
JWT_SECRET=<jwt-secret>
```

You get `SUPABASE_URL` and `SUPABASE_ANON_KEY` from your Supabase dashboard.

You’ll see from above, we also need to set up two tables in Supabase. We need a `users` table with an `email` field, and a `magic_tokens` table with these fields:

- `user_id`, which comes from the users table.
- `token`, which is the generated token.
- `expires_at`, to add an expiry time to the token.

Let’s get back to the main code. If we fill out the email field on the homepage and click “Send Magic Link,” `requestMagicLink` will be called and an email sent to the entered email address. Clicking on that link calls the `verify.js` endpoint.

### verify.js

```javascript
// app/api/auth/verify.js
import jwt from 'jsonwebtoken'

import { getTokenData, deleteUserToken } from '../../../../lib/database'

export async function GET(req) {
  const token = req.url.split('=')[1]

  console.log(token) // Logs the token value
  const tokenData = await getTokenData(token)

  if (!tokenData) {
    return Response.json({ error: 'Invalid or expired token' })
  }

  const { email } = jwt.verify(token, process.env.JWT_SECRET)

  // Delete or invalidate the token
  await deleteUserToken(token)

  return Response.redirect('/dashboard') // Or wherever you want to redirect the user after login
}
```

This defines the API route for verifying the magic link token as part of an authentication process.

The code is designed to:

- Extract a token from the request URL.
- Verify the token's validity.
- Perform actions based on the token's validity (such as user redirection or error response).

The code retrieves the token from the URL query string by splitting the URL at the `=` character and taking the second part (`req.url.split("=")[1]`).

The `getTokenData` function is called to fetch the token data from the database. If no data is found (implying the token is invalid or expired), it returns a JSON response with an error message. Using `jwt.verify`, we validate the token against the secret key stored in `process.env.JWT_SECRET`. This also extracts the payload (`email`) from the token.

We then call `deleteUserToken` to remove the token from the database, ensuring it cannot be reused. Finally, it redirects the user to the `/dashboard` route upon successful token verification.

### dashboard/page.js

There isn’t much to `dashboard/page.js`:

```javascript
// app/dashboard/page.js
'use client'

export default function Dashboard() {
  return <h1>A verified page</h1>
}
```

In a production application, this is the page you would build out in your application.

## Final Thoughts

There is a lot to think about with email magic links. The above example doesn’t go into robust authentication checking with the user, nor does it add rate-limiting or have significant error handling. An unfortunate truth of authentication is that there is no silver bullet. While magic links take away the headache of managing user credentials, you still have to manage token generation, storage and expiry. Plus, you are still managing and storing user data.

Any good developer is going to take the time to understand, at least, the basic strategies leveraged by the tools they use to speed up their processes (good work understanding magic links!). With that being said, a simpler and more secure alternative to all the code above is to use [Clerk](/). We built Clerk to make it quick and easy to add advanced authentication techniques into your application. To learn more about magic links, visit our [documentation](/docs/custom-flows/magic-links) or this article on [implementing magic links with Next.js and Clerk](/blog/secure-authentication-nextjs-email-magic-links).

---

# Create Your Own Custom User Menu with Radix
URL: https://clerk.com/blog/create-custom-user-menu-radix.md
Date: 2023-12-14
Category: Guides
Description: Quickly and easily build a custom user menu for your application leveraging Clerk's hooks and methods and building on Radix primitives for a custom UI.

> \[!IMPORTANT]
> `<UserButton />` might be more flexible than you think! As of August 2024, you can now customize the component with [custom menu items](/docs/customization/user-button). Should you need even more customization, this post and [part 2](/blog/create-custom-user-menu-radix-pt-2) are still here to help you build the experience you desire from scratch.

Clerk’s components were created with you in mind. Components do most of the functional work for you, allowing you to get your auth flows working in minutes, and support customization to fit your app’s style. That said, sometimes a component like the [`<UserButton />`](/docs/components/user/user-button) doesn’t suit the needs of your application. The good news? Clerk provides hooks and functions that make building your custom UI components easy. Let’s take a quick look at how to create your custom user menu.

Code samples are from @clerk/nextjs 4.27.2 and @radix-ui/react-dropdown-menu 2.0.6

## Getting Started

The hardest part of building a custom user menu is often the dropdown menu itself. You need the button to trigger the menu opening, a way to close it, a way to track open/closed states, a way to handle ‘click off’ to close, logic to close if the user hits the `Esc` button, a way to handle keyboard input, and the list goes on. We’re going to save ourselves some time and use a great library while doing so. Radix provides world-class, accessible, unstyled [primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) that you can use to quickly and efficiently build your UI. Let’s start by installing the primitive we need — the dropdown menu.

```bash
pnpm install @radix-ui/react-dropdown-menu
```

Once the installation finishes, let’s create the scaffolding for our new component. The following will be the foundation of the menu. The trigger will hold the User button that will open the menu, and each item will hold one of the menu entries. Remember that Radix Primitives are unstyled and there is no content so this will be blank.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'

export const UserButton = () => {
  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger></DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content>
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item></DropdownMenu.Item>
            <DropdownMenu.Item></DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item></DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## Create the User Button

The first content we will add is the User button. This will show that the user is logged in, and will be the trigger to open the menu. For the sake of this post we will assume that you have marked email as required in the Clerk Dashboard, in the [User & Authentication → Email, Password, Username → Email](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) section to ensure every user has an email. You could change this to a first name or username easily enough. Just make sure that whatever option you choose is something that will exist for all users, for the sake of rendering. You could also conditionally render different information depending on what the user has provided — show the first name if available; if not, show the email.

To build the button we will leverage the [`useUser()`](/docs/references/react/use-user#use-user) hook. This gives us access to information about the user, such as profile image, email, name, and more. We will also make sure that Clerk and the user have loaded, and that there is valid user data, before rendering the User button.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
// Import useUser()
import { useUser } from '@clerk/nextjs'
// Import the Image element
import Image from 'next/image'

export const UserButton = () => {
  // Grab the `isLoaded` and `user` from useUser()
  const { isLoaded, user } = useUser()

  // Make sure that the useUser() hook has loaded
  if (!isLoaded) return null
  // Make sure there is valid user data
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Render a button using the image and email from `user` */}
        <button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content>
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item></DropdownMenu.Item>
            <DropdownMenu.Item></DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item></DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## Add the Sign-Out and Manage Account Buttons

With the User button in place, we can now add Sign Out and Manage Account buttons. The [`useClerk()`](/docs/references/react/use-clerk) hook provides the two methods we will need for this — the [`signOut()`](/docs/custom-flows/sign-out#custom-sign-out) method and the [`openUserProfile()`](/docs/references/javascript/clerk/user-profile#open-user-profile) method. The User Profile will open as a modal. You could, instead, mount the [`<UserProfile />`](/docs/components/user/user-profile) component to its own route, and then link it to the route. For the Sign Out button, we will also need to use the Next.js router to handle the redirect.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
// Import useClerk()
import { useUser, useClerk } from '@clerk/nextjs'
// Import the Next.js router
import { useRouter } from 'next/navigation'
import Image from 'next/image'

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  // Grab the signOut and openUserProfile methods
  const { signOut, openUserProfile } = useClerk()
  // Get access to the Next.js router
  const router = useRouter()

  if (!isLoaded) return null
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content>
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item asChild>
              {/* Create a button with an onClick to open the User Profile modal */}
              <button onClick={() => openUserProfile()}>Profile</button>
            </DropdownMenu.Item>
            <DropdownMenu.Item></DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item asChild>
            {/* Create a Sign Out button -- signOut() takes a call back where the user is redirected */}
            <button onClick={() => signOut(() => router.push('/'))}>Sign Out </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## Extending the Custom User Menu

The custom button has now replicated the behavior of the `<UserButton />`, though it is very much still unstyled. We’re going to do one more thing — add one more menu entry to mimic expanding the menu. This will use the [`<Link />`](https://nextjs.org/docs/app/api-reference/components/link) component from Next.js to link to a fictional `/subscriptions` route.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useUser, useClerk } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
// Import Link to add more buttons to the menu
import Link from 'next/link'

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()

  if (!isLoaded) return null
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="border border-gray-200 bg-white text-black drop-shadow-md">
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item asChild>
              <button onClick={() => openUserProfile()}>Profile</button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild>
              {/* Create a fictional link to /subscriptions */}
              <Link href="/subscriptions">Subscription</Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item asChild>
            <button onClick={() => signOut(() => router.push('/'))}>Sign Out </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## The Last Step!

Your new component is almost ready — it just needs some styling. Let’s add a little bit to get started. The code below is ready to drop right into your app, and then you can import the new `<UserButton />` into your header.

### The Custom User Menu

![Create Custom User Menu Radix tutorial illustration](./7d5be406f59f3f30aaecd91a4f4dab6393a7a0ff-366x385.png)

### Final code

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
// Import useUser() and useClerk()
import { useUser, useClerk } from '@clerk/nextjs'
// Import Next's router
import { useRouter } from 'next/navigation'
// Import the Image element
import Image from 'next/image'
// Import Link to add more buttons to the menu
import Link from 'next/link'

export const UserButton = () => {
  // Grab the `isLoaded` and `user` from useUser()
  const { isLoaded, user } = useUser()
  // Grab the signOut and openUserProfile methods
  const { signOut, openUserProfile } = useClerk()
  // Get access to Next's router
  const router = useRouter()

  // Make sure that the useUser() hook has loaded
  if (!isLoaded) return null
  // Make sure there is valid user data
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Render a button using the image and email from `user` */}
        <button className="flex flex-row rounded-xl border border-gray-200 bg-white px-4 py-3 text-black drop-shadow-md">
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
            className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-6 py-4 text-black drop-shadow-2xl">
          <DropdownMenu.Label />
          <DropdownMenu.Group className="py-3">
            <DropdownMenu.Item asChild>
              {/* Create a button with an onClick to open the User Profile modal */}
              <button onClick={() => openUserProfile()} className="pb-3">
                Profile
              </button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild>
              {/* Create a fictional link to /subscriptions */}
              <Link href="/subscriptions" passHref className="py-3">
                Subscription
              </Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator className="my-1 h-px bg-gray-500" />
          <DropdownMenu.Item asChild>
            {/* Create a Sign Out button -- signOut() takes a call back where the user is redirected */}
            <button onClick={() => signOut(() => router.push('/'))} className="py-3">
              Sign Out{' '}
            </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

Example repository:
[https://github.com/royanger/clerk-custom-user-menu](https://github.com/royanger/clerk-custom-user-menu)

## Explore the Powerful Customization Options Clerk Offers!

Take a look at our [Custom Flows](/docs/custom-flows/overview) documentation to explore more ways to customize your application using the many hooks and methods Clerks provides.

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless user management starts here!

---

# Introducing has(), protect(), and <Protect>
URL: https://clerk.com/blog/introducing-authorization.md
Date: 2023-12-13
Category: Engineering
Description: Authorization is now a core feature of Clerk – learn more about our new authorization helpers for B2B SaaS

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

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

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

## Custom roles and permissions

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

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

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

![Custom roles and permissions screenshot](./d69547abef435115556125370cab753a5e7d08f3-2062x1298.png "The new role manager in Clerk's dashboard")

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

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

### 1. has()

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

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

- `auth()` in Next.js App Router
- `useAuth()` in client components, including during SSR
- `getAuth(request)` in server contexts outside of the App Router (Remix Loaders, Node, Express, Fastify, etc)

```typescript
// Within a server component, server action, or route handler
const CreateWidget = () => {
  const { has } = auth()
  if (!has({ permission: 'org:widgets:create' })) {
    return null
  }
  // ...
}

// Within a client component
export const AdminsOnly = () => {
  const { has } = useAuth()
  if (!has({ role: 'org:admin' })) {
    return null
  }
  // ...
}

// Within an Express API route
app.post('/api/widgets', (req, res) => {
  const { has } = getAuth(req)
  if (!has({ permission: 'org:widgets:create' })) {
    return null
  }
  // ...
})
```

### 2. protect()

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

```typescript
export const POST = () => {
  const { userId, orgId } = auth().protect({ permission: 'org:widgets:create' })

  // Protect would throw an error if the user is unauthorized
  // If this point is reached, it is guaranteed that userId and ordId exist,
  // and that userId has the "org:widgets:create" permission in orgId

  // Bonus: TypeScript knows that userId and orgId exist!
  // ...
}
```

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

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

### 3. \<Protect>

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

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

```tsx
export const Navigation = () => {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/widgets">Widgets</Link>
      <Protect role="org:admin">
        <Link href="/admin">Admin Panel</Link>
      </Protect>
    </nav>
  )
}
```

## Documentation

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

- [Roles and Permissions overview](/docs/organizations/roles-permissions)
- [Create Roles and assign Permissions](/docs/organizations/create-roles-permissions)
- [Verify the active user’s permissions](/docs/organizations/verify-user-permissions)

## Authorization is now a core feature of Clerk

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

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

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

---

# Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan”
URL: https://clerk.com/blog/new-pricing-plans.md
Date: 2023-11-30
Category: Company
Description: Introducing NEW pricing for Clerk – a Pro Plan with additional features, Pro Add-Ons to fit your application's use case, and 10,000 MAUs free on all plans

We’re thrilled to introduce a simplified pricing structure that’s not only easier to understand, but also significantly cheaper for most applications currently using Clerk. **Every application gets 10,000 free monthly active users (MAUs)!**

Brand new users will also now receive their **“First Day Free,”** which means their activity is not counted until 24 hours after signing up. This ensures you are not charged for users who sign up, but churn within their first day and do not return.

Since Clerk’s inception, our primary goal has been to lower the barrier to building production-grade applications. Every application deserves secure and seamless user management that takes minutes to setup, not days — freeing you to focus on what’s important: *your application*.

Existing customers will be grandfathered into their current plans, however, it may be cheaper to switch, so please
evaluate accordingly and reach out to us if you have any questions.

Our new simplified pricing structure consists of “Free”, “Pro” and “Enterprise” plans. Where each plan includes your first 10,000 MAUs free and you will never pay for an inactive user. Additional features can be added to the Pro plan via “Pro add-ons” tailor made for specific use cases.

## Free Plan – Up to 10,000 MAUs

Our free plan remains generous, with no features removed. You still get beautifully designed, performant, and customizable sign-in pages on the domain of your choosing. However, we’ve now doubled the included number of MAUs! Get started, and iterate longer — completely for free!

[Visit the pricing page to see all features in the Free plan](/pricing)

## Going Pro – A complete feature set for most use cases

The new Pro plan includes the first 10,000 MAUs, and starts at $25/mo. Additional MAUs are only $0.02 each. This is a dramatic simplification, and includes 10X more MAUs compared to the previous Hobby plan. The Pro plan also now includes the ability to set a “Custom Session Duration,” which has been a key sticking point in the past.

**Additional Pro plan features include:**

- Removing Clerk Branding
- Custom Session Duration *(previously in the Business plan)*
- Password Complexity Requirements
- User Ban/Unban
- Allowlist/Blocklist
- Multiple Domains
- SMS Authentication
- SOC2 Report on Request

[Visit the pricing page to see all features in the Pro plan](/pricing)

## Pro Add-Ons – Customize to fit your needs

While the Pro plan is designed for the majority of applications, add-ons are tailored to specific business needs and are priced according to their added value. Today we’re launching with 3 Pro add-ons that will become more feature-rich over time:

- **Enhanced Authentication** — includes MFA
- **Enhanced Organizations** — includes domain restrictions, and custom roles and permissions *(Stay tuned for this new feature* 😉)
- **Enhanced Administration** — includes user impersonation

[Visit the pricing page to see all features in each Pro add-on](/pricing)

## The Future

We know pricing changes lead to uncertainty and we’re committed to minimizing them. Our end-goal here is to continue to drop the price of authentication as we add value in adjacent areas. Building applications should be getting easier and cheaper over time, and this pricing change is just one step in the right direction. Clerk will always be committed to the developer community, and hopefully this pricing overhaul reflects that.

[We’re here if you have any questions](https://clerk.com/contact/support) — Happy building!

---

# Next.js Authentication with Clerk: Streamlined SSR Handling
URL: https://clerk.com/blog/nextjs-auth-clerk-streamlined-ssr-efficiency.md
Date: 2023-11-18
Category: Engineering
Description: Discover streamlined Next.js authentication with Clerk, simplifying SSR for efficient user data handling in web development.

In the ever-evolving world of web development, streamlining tasks is paramount. Clerk, the versatile User Management platform, presents a contemporary way to manage user data in Next.js applications, leaving behind the complexity of old patterns for server-side rendering (SSR). Let's explore the key differences and the value of this new approach with basic code snippets.

## Navigating Next.js SSR: The Previous Approach

Previously, incorporating Clerk in a Next.js app involved intricate setups and token management. Here's a reminder of what the deprecated approach looks like

```typescript {{ title: 'Deprecated approach' }}
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(({ req, resolvedUrl }) => {
  const { sessionId } = req.auth

  if (!sessionId) {
    return { redirect: { destination: '/sign-in?redirect_url=' + resolvedUrl } }
  }

  return { props: {} }
})
```

## **The New Way: Streamlined Handling**

These are just a few examples of the new streamlined approaches. If you are looking for a more comprehensive breakdown of the best way to read session data in your Next.js app using the [Pages Router](https://nextjs.org/docs/pages) or [App Router](https://nextjs.org/docs/app), please [reference the docs](/docs/references/nextjs/read-session-data?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=mirco-blog).

### SSR in App Router – Server Component

The modern Clerk approach in App Router simplifies user data handling with straightforward helper functions. Here's a snippet of the new approach:

```tsx {{ title: 'NextJS new approach - App Router ' }}
import { currentUser } from '@clerk/nextjs'

export default async function Example() {
  const user = await currentUser()

  return (
    <div>
      Hello {user.firstName}, your ID is {user.id}
    </div>
  )
}
```

### SSR in Pages Router

If you are using the Pages Router, you can find more detailed examples beyond this basic snippet in the [docs.](/docs/references/nextjs/build-clerk-props?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=micro-blog)

```typescript {{ title: 'NextJS new approach - Pages Router ' }}
import { getAuth, buildClerkProps } from '@clerk/nextjs/server'
import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId } = getAuth(ctx.req)

  if (!userId) {
    // handle user is not logged in.
  }

  // Load any data your application needs for the page using the userId
  return { props: { ...buildClerkProps(ctx.req) } }
}
```

### **The Value of the New Approach**

1. **Simplicity**: The new approaches offer a cleaner, more straightforward codebase for effortless data access.
2. **Efficiency**: Data access becomes a breeze.
3. **Improved Workflow**: Focus on building features, not grappling with complex User Management setups.
4. **Maintainability**: A tidy codebase equals easier maintenance and fewer debugging hassles.

Dive headfirst into Clerk's innovative approach to supercharge your development process, ensuring your apps are always at the cutting edge. This newfound simplicity isn't just for show; it's here to make your work smoother, your code cleaner, and your applications more maintainable. And what's even more exciting? This flexibility isn't confined to one corner of your project; it stretches its arms to [Route Handlers](/docs/reference/nextjs/app-router/route-handlers) as well, making it the perfect fit for the demands of modern web development.

## **Ready to Implement Authentication in Your App?**

Don't hesitate to explore our [Next.js Clerk Docs](/docs/references/nextjs/overview?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=micro-blog) for a quick and comprehensive guide on integrating Clerk [Next.js authentication](/nextjs-authentication) into your application within minutes, not days.

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless User Management starts here!

---

# Clerk Webhooks: Data Sync with Convex
URL: https://clerk.com/blog/webhooks-data-sync-convex.md
Date: 2023-11-14
Category: Guides
Description: This post covers how to synchronize user data from Clerk into a Convex database using Webhooks.

Composing an application out of multiple data sources can be challenging, and while Clerk’s Backend APIs work great for most use cases, Webhooks offer the next level of integration that enables you to take full advantage of your existing stack. This post will cover how to synchronize user data from Clerk into your own backend using Webhooks.

## Data Sync with Convex

[Convex](https://convex.dev) is a real time [Backend-as-a-Service](https://en.wikipedia.org/wiki/Backend_as_a_service) Provider that makes building highly dynamic and interactive experiences with React easier than ever.

Clerk offers a first-class integration with Convex. The queries and mutations living in your Convex instance can be authenticated with a token that was created by Clerk. This integration is covered in more detail [in the documentation](/docs/integrations/databases/convex?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=webhooks-blog\&utm_term=convex-post).

Convex is also a great target for synchronizing user data from Clerk using webhooks, since you can take full advantage of Convex’s realtime capabilities for user data.

If you are not sure what webhooks are and how to use webhooks with Clerk, it’s recommended to read [this getting started post](/blog/webhooks-getting-started?utm_source=DevRel\&utm_medium=blog\&utm_campaign=blog-to-blog\&utm_content=webhooks-blog\&utm_term=convex-post) first.

To demonstrate data synchronization with Convex, we can use [Convex’s Clerk starter repo](https://github.com/thomasballinger/convex-clerk-users-table). You can clone and run this repo locally by following the instructions in the repo.

This post will:

1. Explain how the demo uses data sync with reactive queries together
2. Show code examples of the webhooks handler implementation in the demo

## The Demo

The demo application is a chat room where users can send and receive messages. To access the room, a user needs to sign up for an account using the Clerk integration. Once signed in, the user is redirected to a chat room, which establishes a reactive query to the Convex database. This reactive query looks for the user in the database, and returns a “No Clerk User” if the user is not found, and a “Logged In” if the user is found.

Initially after signing up, the user will not be found in the database. Since the signup process happened with Clerk, Convex has no record of this user.

![Webhooks Data Sync Convex setup guide](./1e2c045dab59119f46ab6a0767ce42e6e766e9c8-3600x1431.png)

This is where webhooks come into play. After the user signs up with Clerk, Clerk will send an http request to a pre-defined URL with data about the sign up. This URL will point to a Convex HTTP Action, which will parse the data and create a record in the Convex database for the user. Now the client can query for this user again and receive the expected response.

![Webhooks Data Sync Convex setup guide](./878c4fda4bc4fdfc93ba7e78ba644c1543666e6a-3600x1023.png)

But since Convex queries are reactive, the client doesn’t need to retry the query, instead it will automatically receive an update from the server with the user data. This will trigger React to re-render the UI, providing the user access to the chat room.

![Webhooks Data Sync Convex setup guide](./41c813e18fefb8873b6df2952569d7417acca319-3600x1023.png)

## The Code

To look at the webhook implementation, we can open the `convex/http.ts` file in this repo.

```typescript {{ title: 'convex/http.ts', prettier: false }}
// define the webhook handler
const handleClerkWebhook = httpAction(async (ctx, request) => {
  const event = await validateRequest(request)
  if (!event) {
    return new Response('Error occured', { status: 400 })
  }
  switch (event.type) { /* ... */ }
  return new Response(null, { status: 200 })
});

// define the http router
const http = httpRouter()

// define the webhook route
http.route({
  path: '/clerk-users-webhook',
  method: 'POST',
  handler: handleClerkWebhook,
})
```

The `handleClerkWebhook` function is responsible for… you guessed it… handling the Clerk webhook. It’s a function decorated with `httpAction` and receives the HTTP request and a context object to access other Convex resources.

This function runs security validation on the request to ensure that it came from a trusted source, and runs some database mutations based on the type of event received. The mutations simply mirror the event - creating, updating, or deleting a user in Clerk simply results in creating, updating, or deleting the same user in Convex. It forms a copy of the Clerk user table within Convex.

```typescript {{ title: 'convex/http.ts' }}
switch (event.type) {
  case 'user.created':
  case 'user.updated': {
    await ctx.runMutation(internal.users.updateOrCreateUser, {
      clerkUser: event.data,
    })
    break
  }
  case 'user.deleted': {
    const id = event.data.id!
    await ctx.runMutation(internal.users.deleteUser, { id })
    break
  }
  default: {
    console.log('ignored Clerk webhook event', event.type)
  }
}
```

This allows the most powerful feature of Convex - Queries, to access the user data directly, which means the application doesn’t need to make additional requests to Clerk to fetch the user data (also called the n+1 problem), and the user interface can get notified if the data changes (e.g. if the user changes their name or profile picture).

```typescript {{ title: 'convex/users.ts' }}
export const getUser = internalQuery({
  args: { subject: v.string() },
  async handler(ctx, args) {
    return ctx.db
      .query('users')
      .withIndex('by_clerk_id', (q) => q.eq('clerkUser.id', args.subject))
      .unique()
  },
})
```

## Full Potential

Webhooks are a powerful feature that enable integrations with existing backend tools, allowing you to use their technical potential to the fullest, while also using Clerk's user management capabilities to the fullest. They help glue disjointed parts of the backend together so that you can pick whatever technology suits your use case for storing and processing data.

## Ready to Integrate Authentication in Your App?

You can explore the [Clerk Webhooks docs](/docs/integrations/webhooks/overview?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=webhooks-blog\&utm_term=convex-post) to learn more about the webhook events and data that is exposed by Clerk and build your own integrations today!

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless User Management starts here!

---

# Exploring Clerk Metadata with Stripe Webhooks
URL: https://clerk.com/blog/exploring-clerk-metadata-stripe-webhooks.md
Date: 2023-11-09
Category: Guides
Description: Utilize Clerk Metadata & Stripe Webhooks for efficient user data management and enhanced SaaS experiences with our step-by-step tutorial.

## Introduction to User Metadata

By putting Clerk’s user metadata types to work, developers can proficiently handle user data, making their SaaS integrations run smoother, and work harder. It's like adding a turbocharger to your product's engine, enhancing functionality and improving the user experience, for a more comprehensive, customizable, and synchronizing systems ready to build any SaaS product out there.

A great feature in the wild world of SaaS product development to power integrations powering integrations with other powerful products. We're talking about a sturdy, malleable means for handling user data.

You've got three types of User Metadata – [public, private, and unsafe](/docs/users/metadata#user-metadata). Each one has its own unique access level and use case.

- **Public**: It’s an accessible from both the frontend and backend. It's like the town bulletin board where you post things everyone can see but can't change. Think membership levels, user roles, stuff like that.
- **Private**: It’s like the secret stash of user data only reachable from the backend. Perfect for things like account identifiers or subscription details, you know, the stuff you don't want out in the open.
- **Unsafe**: It might sound a bit ominous, but it is super flexible; treat it like form data and validate any user inputs. It can be modified and accessed from both frontend and backend. Great for things like user preferences, setting or just any of the nitty-gritty details that make a user's experience unique.

## User Metadata Meets Stripe Webhooks

Harnessing the power of User Metadata in tandem with Stripe’s webhooks offers significant advantages in SaaS product development. Clerk Metadata's flexible user data management paired with Stripe webhooks' real-time transaction updates creates a robust, efficient system. This combination ensures both comprehensive user data handling and prompt responsiveness to transaction events. Utilizing Clerk Metadata alongside Stripe’s webhooks lends itself well for streamlined and user-friendly SaaS development.

Utilizing Clerk's public User Metadata offers significant advantages for managing user data and transactions in your SaaS product. It allows for real-time updates, such as including a "paid" field after a transaction, offering a clear snapshot of payment statuses. This use of public metadata improves transparency, boosts data management efficiency, and enhances the overall user experience.

## Tutorial: Implementing Stripe Webhook with Clerk User Metadata

The first step will be setting up accounts at [Clerk](https://dashboard.clerk.com/sign-up?utm_source=DevRel\&utm_medium=blog\&utm_campaign=devrel-blog-signups) & [Stripe](https://dashboard.stripe.com/register). Once you have those accounts you will follow the well documented [Clerk’s Next.js Quickstart Guide](/docs/quickstarts/nextjs). To have access to the correct data from the Clerk session you will need to access the [custom session data](/docs/backend-requests/making/custom-session-token#click-the-edit-button) on the Dashboard, we will edit the session data to look like this:

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

The last part will be setting up from the Stripe quickstart, the basic [Stripe webhook](https://stripe.com/docs/webhooks/quickstart). We will modify it later for our own needs, and there will also be a repo for you to grab afterwards! By the end of the quickstarts, you should have something in your **.env.local** that looks like this.

```bash {{ title: 'Environment Variables' }}
# CLERK
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_***
CLERK_SECRET_KEY=sk_test_***

# STRIPE
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_***
STRIPE_SECRET_KEY=sk_test_***
STRIPE_WEBHOOK_SECRET=whsec_***
```

## Auth Middleware Setup

Once we have everything installed we are going to jump into a very basic app, with one private route **/members**, and the homepage route will serve as our public route where all the fun stuff will happen. Our [Middleware](/docs/references/nextjs/auth-middleware) is going to be handling the access.

```typescript {{ title: 'Middleware Routes' }}
export default authMiddleware({
  // Making sure homepage route and API, especially the webhook, are both public!
  publicRoutes: ['/', '/api/(.*)'],
  afterAuth: async (auth, req) => {
    // Nice try, you need to sign-in
    if (!auth.userId && !auth.isPublicRoute) {
      return redirectToSignIn({ returnBackUrl: req.url })
    }
    // Hey! Members is for members 😆
    if (
      auth.userId &&
      req.nextUrl.pathname === '/members' &&
      auth.sessionClaims.publicMetadata?.stripe?.payment !== 'paid'
    ) {
      return NextResponse.redirect(new URL('/', req.url))
    }
    // Welcome paid member! 👋
    if (
      auth.userId &&
      req.nextUrl.pathname === '/members' &&
      // How we get payment value "paid" is next, in the webhook section!
      auth.sessionClaims.publicMetadata?.stripe?.payment === 'paid'
    ) {
      return NextResponse.next()
    }
    // If we add more public routes, signed-in people can access them
    if (auth.userId && req.nextUrl.pathname !== '/members') {
      return NextResponse.next()
    }
    // Fallthrough last-ditch to allow access to a public route explicitly
    if (auth.isPublicRoute) {
      return NextResponse.next()
    }
  },
})
```

We can simplify the Middleware access logic for this app, but this explicit example can show how you can have far more complex access handling. Where do we get **paid** from!? That is coming up next.

### Webhook Endpoint

Since this app is our SaaS with member access, we need to provide a way for the user to pay and gain access. Let’s start with setting up the tokens for Clerk & instantiating Stripe.

```typescript
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
  apiVersion: '2023-10-16',
})
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET as string
```

After we have the credentials, the rest of the code should look very similar to the Stripe default logic for webhooks.

```typescript
export async function POST(req: NextRequest) {
  if (req === null) throw new Error(`Missing userId or request`, { cause: { req } })
  // Stripe sends this for us 🎉
  const stripeSignature = req.headers.get('stripe-signature')
  // If we don't get it, we can't do anything else!
  if (stripeSignature === null) throw new Error('stripeSignature is null')

  let event
  try {
    event = stripe.webhooks.constructEvent(await req.text(), stripeSignature, webhookSecret)
  } catch (error) {
    if (error instanceof Error)
      return NextResponse.json(
        {
          error: error.message,
        },
        {
          status: 400,
        },
      )
  }
  // If we dont have the event, we can't do anything again
  if (event === undefined) throw new Error(`event is undefined`)
  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object
      console.log(`Payment successful for session ID: ${session.id}`)
      break
    default:
      console.warn(`Unhandled event type: ${event.type}`)
  }

  return NextResponse.json({ status: 200, message: 'success' })
}
```

So what is next!? We need a way to know when a Clerk User has "paid." Well, let's extract that switch statement and add the secret sauce. That'll make this all work when we are done!

```typescript {{ title: 'Adding Clerk User Metadata to Event' }}
switch (event.type) {
  case 'checkout.session.completed':
    const session = event.data.object
    console.log(`Payment successful for session ID: ${session.id}`)
    // That's it? Yep, that's it. We love UX 🎉
    clerkClient.users.updateUserMetadata(event.data.object.metadata?.userId as string, {
      publicMetadata: {
        stripe: {
          status: session.status,
          // This is where we get "paid"
          payment: session.payment_status,
        },
      },
    })
    break
  default:
    console.warn(`Unhandled event type: ${event.type}`)
}
```

Some of you may have noticed **event.data.object.metadata?.userId** (where did that come from!?). We will get to that one too. The reason for this is that we can’t access Clerk’s session in the webhook, so we will get a little creative.

### Session Endpoint

We will now need to create an endpoint that will generate our Stripe session that will be used to make our payment and turn our Clerk User into a paid **Member**. This is where the **userId** in the webhook will also be coming from! Instantiate **stripe** the same as before, it will again be a Next.js POST endpoint.

```typescript {{ title: 'Stripe Session' }}
// This is our Clerk function for session User
const { userId } = auth()
// We are receiving this from the Client request, thats next!
const { unit_amount, quantity } = await req.json()

try {
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [
      {
        price_data: {
          currency: 'usd',
          product_data: {
            name: 'Membership',
          },
          unit_amount,
        },
        quantity,
      },
    ],
    // This is where "event.data.object.metadata?.userId" is defined!
    metadata: {
      userId,
    },
    mode: 'payment',
    success_url: `${req.headers.get('origin')}/members`,
    cancel_url: `${req.headers.get('origin')}/`,
  })

  return NextResponse.json({ session }, { status: 200 })
} catch (error) {
  // ...
}
```

We have now laid the groundwork for a SaaS leveraging Clerk’s User Metadata to manage User specific data! So, to really focus on the versatility and potential of this feature, the UI portion has been kept really simple. We have the Homepage with a button to navigate to **/members** page and to become a paid member, let’s take a look at the homepage.

```tsx {{ title: 'Homepage Implementation' }}
export default function Home() {
  const { isSignedIn } = useAuth()

  return (
    <main>
      {!isSignedIn ? (
        <div className="...">
          <SignIn redirectUrl="/" />
        </div>
      ) : (
        <div>
          <div className="...">You are signed in!</div>
          <div className="...">
            <CheckoutButton />
            <a className="..." href="/members">
              Go to members page
            </a>
          </div>
        </div>
      )}
    </main>
  )
}
```

## Wrap up!

This pattern can be used with any other transactions or user specific data you would like to handle in the backend and then utilize in the client. This keeps your User Management pragmatic & versatile, offloading the burden across multiple systems. This is only the beginning with what we can do with Clerk’s toolset, this time we only leveraged User Metadata! What should we do next? Let us know in the [Discord](https://clerk.com/discord) and on [X(Twitter)](https://x.com/clerk)!

Not forgetting, you will want the [complete codebase](https://github.com/JacobMGEvans/stripe-with-webhooks-and-metadata) to check out, and learn from!

---

# The Ultimate Guide to Next.js Authentication
URL: https://clerk.com/blog/nextjs-authentication.md
Date: 2023-11-02
Category: Guides
Description: In this guide, you will learn best practices for implementing secure authentication in your Next.js app.

[Next.js](https://nextjs.org) has become the go-to framework for JavaScript and React development. It has become so popular because of the superior developer experience, performance optimization features, and its ability to facilitate different rendering options and serverless functions with ease. The combination of these attributes makes it an appealing choice for both new and experienced developers.

In Next.js 13, the Next.js team significantly upgraded the capabilities of the framework. Moving to what they call the “App Router,” Next.js is now based on [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) which boast performance upgrades over the previous client-server architecture.

But this shift has also led to a change in how authentication works in Next.js. In this article we want to provide an up-to-date guide on how authentication now works on Next.js so you can easily understand the concepts involved and **easily add secure authentication to your Next.js applications**.

## The Next.js App Router

The [App Router paradigm was introduced in Next.js 13](https://nextjs.org/docs/app) and is a significant shift in how the Next.js framework works.

Next.js was initially built as a React framework to facilitate server-side rendered and statically generated sites. But as the framework grew, more options became available:

1. **Client-Side Rendering (CSR)** is where content is rendered on the client side, after the initial page load. This is ideal for pages that don’t require SEO or initial content served by the server. Common for user dashboards or pages behind authentication. You use React state and effects (`useState`, `useEffect`, etc.) as you would in a regular React app.
2. **Static Site Generation (SSG)** is where pages are pre-rendered at build time and served statically. Each request receives the same HTML. This is good for content that doesn’t change often and doesn’t need to be updated with every request, such as blogs, marketing pages, and documentation, etc. Here, you use `getStaticProps` to fetch the required data at build time.
3. **Incremental Static Regeneration (ISR)** lets you statically generate pages with the option to update them in the background. Once updated, subsequent requests get the new version. This is useful for content that updates periodically but doesn’t need real-time updates. Like with SSG, you can use `getStaticProps`, but this time you add a `revalidate` property. The `revalidate` interval (in seconds) determines how often the page should be updated.
4. **Server-Side Rendering (SSR)** means each request is rendered on-the-fly on the server, providing fresh data. It is ideal for pages that need real-time data or when content changes frequently. Also beneficial for SEO. You use `getServerSideProps` to fetch data on each request.

You could have some pages work with CSR, some SSG, and some SSR. But this also led to confusion and performance issues with sites not optimally built.

Next.js 13 and the App Router are designed to simplify the developer experience within Next.js, and rebuild the framework around [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components). With RSC, the default for the entire site is server-side rendering, with each page being opted-in to CSR as needed. This leads to performance gains, as rendering on the server is faster, and you don’t have to send heavy JavaScript payloads over the network for hydration. It also means more sites can work on the ‘edge’ network, where sites are served from multiple servers close to users.

But this does mean that the way developers have been using Next.js thus far has now changed. The old, Pages Router continues to work, and can be used side-by-side with the App Router, but significant changes in how data is served, such as no request/response objects being passed means developers will need to rethink how they work, and how their authentication works.

Here, we’re going to go through:

1. How authentication works with the pages router in Next.js
2. How authentication works with the App Router in Next.js

This will give you the ability to use either as you need, and to see the differences in each paradigm when it comes to authentication.

## Authentication With the Next.js Pages Router

Before we get into authentication with the App Router, let’s look at how authentication works with the Pages Router in Next.js.

Let’s create a Next.js project. To do this, we use:

```sh
npx create-next-app@latest
```

You will be prompted to choose different options, but given that we are explicitly using the Pages Router, you’ll want to select “no” when asked if you would like to use the App Router.

With that done, we can install Clerk:

```sh
npm install @clerk/nextjs
```

Next, we want to add our API keys as environmental variables in an `.env.local` file. To get these, sign up for a free [Clerk account](/):

```sh
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_*****
CLERK_SECRET_KEY=sk_test_*****
```

Before we go any further, we’ll also make some small changes to our homepage, at `pages/index.js`:

```jsx
import Head from 'next/head'

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-500 to-purple-500">
      <Head>
        <title>Hello Pages Router with Next.js & Clerk</title>
        <meta
          name="description"
          content="A simple Hello World homepage using Next.js and TailwindCSS"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="rounded-xl bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-blue-500">Hello, Pages Router!</h1>
        <p className="text-gray-600">This is a simple homepage built with Next.js and Clerk</p>
      </div>
    </div>
  )
}
```

If we now run `npm run dev`, we’ll get this homepage:

![Nextjs Authentication tutorial illustration](./e6980a8e3bf380cb0bdf8cac5a4ea4e503f0411e-2000x1264.png)

We’re now ready to start adding Clerk to our code. The first thing we need to do is mount the [ClerkProvider](/docs/components/clerk-provider#clerk-provider). The `ClerkProvider` is the core component of Clerk. It is what handles all the active session data and the user context. Whenever a Clerk hook (or any other components) needs authentication data, it is getting it from the `ClerkProvider`.

The `ClerkProvider` needs needs to access [headers](https://nextjs.org/docs/app/api-reference/functions/headers) to authenticate any user. This is important because:

`headers()` is a Dynamic Function whose returned values cannot be known ahead of time. Using it in a layout or page
will opt a route into dynamic rendering at request time.

To protect your entire application, it is recommended to wrap our main Component in the `_app.tsx|jsx` file:

```tsx
import { ClerkProvider } from '@clerk/nextjs'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default MyApp
```

This will protect every page within the application and will make the user context accessible anywhere within the app, but will also opt every page into dynamic rendering.

If you have statically served pages or are using incremental static regeneration, you can wrap route groups further into your application. For example, you might want to leave your marketing site unprotected, but wrap your dashboard components in the `ClerkProvider`.

Clerk [requires middleware](/docs/references/nextjs/auth-middleware#auth-middleware) to determine the routes that need to be protected. This is in `middleware.js` in our root directory. This matcher will put every page and API route on the site behind authentication:

```jsx
import { authMiddleware } from '@clerk/nextjs'

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your middleware
export default authMiddleware({})

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

The entire application is now protected. Accessing any page while signed out will redirect you to the sign up page. If we go to the homepage again, we get redirected:

![Nextjs Authentication tutorial illustration](./f1c7e0f565e3b201cff729570d3fef5862539c02-2000x1264.png)

Usually, this isn’t what you want. Your homepage, product, and marketing pages aren’t very helpful if they are behind authentication!

If you navigate to the homepage now you’ll also get this warning:

The request to / is being redirected because there is no signed-in user, and the path is not included in ignoredRoutes
or publicRoutes.

To prevent this behavior, choose one of:

1. To make the route accessible to both signed in and signed out users, pass `publicRoutes: ["/"]` to `authMiddleware`
2. To prevent Clerk authentication from running at all, pass `ignoredRoutes: ["/((?!api|trpc))(_next.*|.+\\.[\\w]+$)", "/"]` to `authMiddleware`
3. Pass a custom [afterAuth](/docs/references/nextjs/auth-middleware#using-after-auth-for-fine-grained-control) to `authMiddleware`, and replace Clerk’s default behavior of redirecting unless a route is included in public routes

So let’s make a change to add a public route:

```jsx
import { authMiddleware } from '@clerk/nextjs'

export default authMiddleware({
  publicRoutes: ['/'],
})

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

Now, when we head to the homepage, it is available again:

![Nextjs Authentication tutorial illustration](./e6980a8e3bf380cb0bdf8cac5a4ea4e503f0411e-2000x1264.png)

Great. But now there is no way for us to sign up or sign in. Clerk’s [Next.js Authentication SDK](/nextjs-authentication) provides helpers for determining whether a user is signed in or note, and conditionally changing the text. Let’s wrap our text in these components:

```jsx
import { SignedOut } from '@clerk/nextjs'
import Head from 'next/head'
import Link from 'next/link'

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-500 to-purple-500">
      <Head>
        <title>Hello Pages Router with Next.js & Clerk</title>
        <meta
          name="description"
          content="A simple Hello World homepage using Next.js and TailwindCSS"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="rounded-xl bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-blue-500">Hello, Pages Router!</h1>
        <p className="text-gray-600">This is a simple homepage built with Next.js and Clerk</p>

        <SignedOut>
          <Link href="/sign-up">
            <div>
              <h3 className="mb-4 text-xl font-bold text-blue-500">
                Sign in or sign up for an account
              </h3>
            </div>
          </Link>
        </SignedOut>
      </div>
    </div>
  )
}
```

Here, if a user is signed out, they’ll see a link to a sign up page. This page doesn’t exist yet, so lets create it. It will live at `pages/sign-up/[[...index]].jsx`:

```jsx
import { SignUp } from '@clerk/nextjs'

export default function Page() {
  return <SignUp />
}
```

Not much to it. If you try to go that page, you’ll be redirected to sign in/sign up, so it will look like its working. But you are actually being redirected because that page hasn’t been defined as a public route in the middleware, so it itself is behind authentication. To get around this, we’re actually going to add it to our environment variables as a special page:

```sh
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

Now, if we go to that sign up page, we’ll get the modal where we can sign up:

![Nextjs Authentication tutorial illustration](./98d8928b270f39bf6cbc462d88899e026b7f96c1-2000x1264.png)

Then, as our environment variables say, we’re redirected to the home page:

![Nextjs Authentication tutorial illustration](./d52c43bd5ba929652ae9b6563b8ea9cf9882c950-2000x1264.png)

As you can see, the link to sign in as gone, as it was wrapped in the component that only shows it if you are signed out.

We now have a problem. We are now signed in (great!), but we can’t sign out (uh oh!). We could create a specific link for this as well, but Clerk provides a [UserButton component](/docs/references/javascript/clerk/user-button#user-button-component) that allows us to do this easily. Let’s add that to the homepage:

```jsx
import { SignedOut } from '@clerk/nextjs'
import Head from 'next/head'
import Link from 'next/link'

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-500 to-purple-500">
      <Head>
        <title>Hello Pages Router with Next.js & Clerk</title>
        <meta
          name="description"
          content="A simple Hello World homepage using Next.js and TailwindCSS"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="rounded-xl bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-blue-500">Hello, Pages Router!</h1>
        <p className="text-gray-600">This is a simple homepage built with Next.js and Clerk</p>

        <SignedOut>
          <Link href="/sign-up">
            <div>
              <h3 className="mb-4 text-xl font-bold text-blue-500">
                Sign in or sign up for an account
              </h3>
            </div>
          </Link>
        </SignedOut>
        <SignedIn>
          <div>
            <UserButton afterSignOutUrl="/" />
          </div>
        </SignedIn>
      </div>
    </div>
  )
}
```

We’ve wrapped this `UserButton` within the [SignedIn component](/docs/components/control/signed-in#signed-in) so it’ll only show when the user is signed in:

![Nextjs Authentication tutorial illustration](./9ff4051f631c059104c26f01fc4cbb304f7a6f99-2000x1264.png)

Using that button, the user can easily adjust their profile as well as sign out of the application.

Now the user is signed in, they can visit other pages. Let’s create a `pages/protected.jsx` page:

```jsx
import { clerkClient } from '@clerk/nextjs'
import { getAuth, buildClerkProps } from '@clerk/nextjs/server'

const ProtectedPage = ({ user }) => {
  if (!user) {
    return (
      <div>
        <p>Please log in to view this content.</p>
        {/* Optionally add a login button or redirect logic here */}
      </div>
    )
  }

  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <p>This is your protected page.</p>
      {/* Other user-specific JSX components/data can be added here */}
    </div>
  )
}

export default ProtectedPage

export const getServerSideProps = async (ctx) => {
  const { userId } = getAuth(ctx.req)

  if (!userId) {
    return { props: {} } // This will pass an empty props object and the component will handle the "not logged in" state
  }

  const userFromClerk = userId ? await clerkClient.users.getUser(userId) : null
  const user = userFromClerk
    ? {
        id: userFromClerk.id,
        firstName: userFromClerk.firstName,
        lastName: userFromClerk.lastName,
        // ... Add other necessary fields here
      }
    : null

  return { props: { user, ...buildClerkProps(ctx.req) } }
}
```

The code uses the `clerkClient`, `getAuth`, and `buildClerkProps` utilities from the `@clerk/nextjs` library to handle user authentication. If a user is not authenticated (determined by the absence of a `user` prop), the page prompts the user to log in. If authenticated, the page displays a personalized welcome message.

The server-side function `getServerSideProps` checks for the `userId` in the incoming request using the `getAuth` function; if the `userId` is present (indicating authentication), it fetches detailed user information from Clerk via `clerkClient.users.getUser`. The fetched data is then streamlined to a simpler format, extracting only necessary fields like the user’s first name and last name.

The `getServerSideProps` function concludes by returning the user data, if any, along with additional props sourced from `buildClerkProps`, ensuring the frontend receives the necessary data to render the page appropriately.

This renders as:

![Nextjs Authentication tutorial illustration](./7247464b939968d7317dd044b2e85c4301b073a6-2000x1264.png)

Here, the authentication is checked on the server before the page is rendered via `getServerSideProps`. We can also check whether the user is authenticated on the client. Let’s create a `pages/protected-client.jsx` page:

```jsx
import { useUser } from '@clerk/nextjs'

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

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

  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <p>This is your protected page on the client.</p>
      {/* Other user-specific JSX components/data can be added here */}
    </div>
  )
}
```

Which renders this page:

![Nextjs Authentication tutorial illustration](./8fd94b0e65832bf268c37a8aa92c170f6b27de49-2000x1264.png)

One final part of Next.js we can protect with authentication–[API routes](/docs/references/nextjs/get-auth#protecting-api-routes). With the pages router, any route within pages/api is returned as an API. Let’s protect an API route at `pages/api/auth.js`:

```jsx
import { clerkClient } from '@clerk/nextjs'
import { getAuth } from '@clerk/nextjs/server'

export default async function handler(req, res) {
  const { userId } = getAuth(req)

  if (!userId) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  const user = userId ? await clerkClient.users.getUser(userId) : null

  return res.status(200).json({ user })
}
```

This uses the `getAuth` helper that retrieves the authentication state to protect the API route. If we call this within the browser, we’ll get the user JSON:

![Nextjs Authentication tutorial illustration](./19003492bcb5eb596cd8de307703043ed506f2b6-2000x1264.png)

You now have a protected Next.js application using the pages router. Let’s move on to the App Router.

## Authentication with the Next.js App Router

We’re now going to work through an entire example with [the Next.js App Router.](/docs/quickstarts/nextjs#app-router) Some of this is the same as above, particularly, the set up, but once we get into the actual authentication, there are going to be some differences.

Let’s create a new Next.js project:

```sh
npx create-next-app@latest
```

This time, when you are prompted to use the App Router, select “yes”.

Then install Clerk into this project:

```
npm install @clerk/nextjs
```

And then add our API keys in an `.env.local` file:

```sh
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_*****
CLERK_SECRET_KEY=sk_test_*****
```

We’ll spool up another bare-bones homepage, this time at `app/page.js`:

```jsx
import Head from 'next/head'

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-500 to-purple-500">
      <Head>
        <title>Hello Pages Router with Next.js & Clerk</title>
        <meta name="description" content="A simple Hello World homepage using Next.js and Clerk" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="rounded-xl bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-blue-500">Hello, Pages Router!</h1>
        <p className="text-gray-600">This is a simple homepage built with Next.js and Clerk</p>
      </div>
    </div>
  )
}
```

If we now run `npm run dev`, we’ll get this homepage:

![Nextjs Authentication tutorial illustration](./b25b2a50a76078b8517b027d0e5efed705d6576d-2000x1264.png)

Looks like we have some nicer default styling with the App Router.

Next up is to add the `ClerkProvider`. This is the first point at which there is a difference between the pages router and the App Router. With the App Router, we’re going to add the `ClerkProvider` to our `layout.js`:

```jsx
import { ClerkProvider } from '@clerk/nextjs'

export const metadata = {
  title: 'Next.js 13 with Clerk',
}

export default function RootLayout({ children }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

The App Router doesn’t use the `_app.js` file. Instead, layout.js lets you set a global layout for your application. Therefore you can add the `ClerkProvider` to e.g. the `dashboard/layout.js` file, and only the files within that route group would require a login. To repeat from above, this is important because the `ClerkProvider` requires dynamic rendering. If you nest the `ClerkProvider` at a lower level, this allows you to serve the landing and marketing pages statically.

The root layout is a server component. If you plan to use the `ClerkProvider` outside the root layout, it will need to
be a server component as well.

Next step is the middleware. Again, this is in `middleware.js` in our root directory. This time, we’ll start with our homepage as a public route:

```jsx
import { authMiddleware } from '@clerk/nextjs'

export default authMiddleware({
  publicRoutes: ['/'],
})

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

Clerk is now protecting the entire application. Let’s change our homepage to use the [SignedOut component](/docs/components/control/signed-out#signed-out) so we can sign in to the app:

```jsx
import { SignedOut } from '@clerk/nextjs'
import Head from 'next/head'
import Link from 'next/link'

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-500 to-purple-500">
      <Head>
        <title>Hello Pages Router with Next.js & Clerk</title>
        <meta
          name="description"
          content="A simple Hello World homepage using Next.js and TailwindCSS"
        />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div className="rounded-xl bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-blue-500">Hello, Pages Router!</h1>
        <p className="text-gray-600">This is a simple homepage built with Next.js and Clerk</p>

        <SignedOut>
          <Link href="/sign-up">
            <div>
              <h3 className="mb-4 text-xl font-bold text-blue-500">
                Sign in or sign up for an account
              </h3>
            </div>
          </Link>
        </SignedOut>
      </div>
    </div>
  )
}
```

The link now shows on the page:

![Nextjs Authentication tutorial illustration](./53272617b60538ee8c19d0a49d97c4f756342902-2000x1264.png)

Next we’ll add the two other components that will be important for signing up/in/out. First, the sign up page, which will be at `app/sign-up/[[...sign-up]]/page.jsx`:

```jsx
import { SignUp } from '@clerk/nextjs'

export default function Page() {
  return <SignUp />
}
```

Remember to also add these new endpoints to your `.env.local`:

```sh
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

Second, we need the user button:

```jsx
import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import Link from 'next/link'

export default function Home() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-500 to-purple-500">
      <div className="rounded-xl bg-white p-8 shadow-lg">
        <h1 className="mb-4 text-2xl font-bold text-blue-500">Hello, App Router!</h1>
        <p className="text-gray-600">This is a simple homepage built with Next.js and Clerk</p>

        <SignedOut>
          <Link href="/sign-up">
            <div>
              <h3 className="mb-4 text-xl font-bold text-blue-500">
                Sign in or sign up for an account
              </h3>
            </div>
          </Link>
        </SignedOut>
        <SignedIn>
          <div>
            <UserButton afterSignOutUrl="/" />
          </div>
        </SignedIn>
      </div>
    </div>
  )
}
```

Now, when we click the sign up/in link we again go through to the Clerk modal:

![Nextjs Authentication tutorial illustration](./a018d5cc5de01bc8cbfed9e160a38a96c5808f5b-2000x1264.png)

Once we sign in we are redirected back to the homepage, now with the user button:

![Nextjs Authentication tutorial illustration](./d4a7445cd988fc8f664da1720c1ff78222daf6f8-2000x1264.png)

With the user signed in, let’s do the same as with the pages router: create a protected page and a protected API.

Again, for protected pages, we can check for authentication on the server or on the client. Let’s quickly do the client side as that is similar to the pages router:

```jsx
'use client'

import { useUser } from '@clerk/nextjs'

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

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

  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <p>This is your protected page on the client.</p>
      {/* Other user-specific JSX components/data can be added here */}
    </div>
  )
}
```

The only difference between the App Router and the pages router version here is the “use client” directive. By default, all components in the App Router are server components, so will render on the server. The “use client” directive opts this component into client-side rendering, so we can then use Clerk hooks.

This renders as:

![Nextjs Authentication tutorial illustration](./e3266ebf4b05b8bf25e26f448f46a325ff460408-2000x1264.png)

For server rendering, Clerk has two App Router-specific helpers that you can use with your Server Components:

1. [auth()](/docs/references/nextjs/auth) will return the [Authentication](/docs/references/nextjs/authentication-object) object of the currently active user. One of the fundamental differences with the App Router is that you don’t have to inspect the request object constantly for authentication headers. Instead they are available in the global scope through Next.js’s [headers()](https://nextjs.org/docs/app/api-reference/functions/headers) and [cookies()](https://nextjs.org/docs/app/api-reference/functions/cookies) functions, that ClerkProvider gives you access to.
2. [currentUser()](/docs/references/nextjs/current-user) will return the [User](/docs/references/javascript/user/user) object of the currently active user so you can render information, like their first and last name, directly from the server.

Here, we’re going to use both to get information about the user on a protected page:

```jsx
import { auth, currentUser } from '@clerk/nextjs'

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

  if (userId) {
    // Query DB for user specific information or display assets only to logged in users
  }

  // Get the 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
  return (
    <div>
      <h1>Welcome, {user.firstName}!</h1>
      <p>This is your protected page.</p>
      {/* Other user-specific JSX components/data can be added here */}
    </div>
  )
}
```

Which gives us a page with user information rendered:

![Nextjs Authentication tutorial illustration](./43cb7ef9a02b2d0a7c846269d2eb6e0214e64e92-3346x2114.png)

For API routes, the routing is different, but the code is similar. Let’s build an API route under `app/api/user/route.jsx`:

```jsx
import { NextResponse } from 'next/server'
import { auth, currentUser } from '@clerk/nextjs'

export async function GET() {
  const { userId } = auth()

  if (!userId) {
    return new NextResponse('Unauthorized', { status: 401 })
  }

  const user = await currentUser()

  // perform your Route Handler's logic

  return NextResponse.json({ user }, { status: 200 })
}
```

We don’t have the request and response objects with the App Router, so get all our authentication data from our Clerk helpers (which get it from the ClerkProvider which gets it from the global scope via the [Next.js headers() function](https://nextjs.org/docs/app/api-reference/functions/headers)). We can return a JSON of the user’s information:

![Nextjs Authentication tutorial illustration](./4503ac653efdf0ba31a88581dad9641ea6cff2e7-2000x1264.png)

And that’s it. You have server-side, client-side, and API route authentication using the Next.js App Router.

## Authenticating with the App Router

The Next.js App Router offers significant performance gains compared to the traditional pages router. However, you can absolutely continue leveraging the pages router in your Next.js applications – Clerk supports authentication with both routing approaches.

To learn more about the App Router, check out the official [Next.js documentation](https://nextjs.org/docs/app). If you would like to get started with Clerk, [sign up for a free account](https://dashboard.clerk.com/sign-up) and follow along with our [Next.js Quickstart](/docs/quickstarts/nextjs).

---

# Empower Your Support Team With User Impersonation
URL: https://clerk.com/blog/empower-support-team-user-impersonation.md
Date: 2023-10-18
Category: Guides
Description: User impersonation enables support teams to assist customers without compromising privacy and security, essential for delivering great CX.

Unfortunately, no product is perfect. Once in a while, a user will have a problem with your application and need some help from the support team.

But if the problem is specific to the user–something about their data or their permissions within the product–how can customer support help? They could screenshare with the user, but that requires a lot of setup. They could just get the user to describe their problem and then walk them through the steps to fix it, but that is open to all kinds of errors. They could share their credentials with the support team, but that is a huge security risk!

The answer is to implement **user impersonation** in the product.

User impersonation is an admin feature that allows one user, usually an admin or the support team, to take on the identity of another user without knowing their password or other authentication credentials.

It is one of those features that gets missed in an initial roadmap but is critical to the product's long-term success. Without it, your success team flies blind when trying to help customers. Let’s explain why it’s important and how you can implement user impersonation in your application.

## The Importance of User Impersonation

The core reason to use user impersonation is troubleshooting and support. If a user reports an issue specific to their account, admins or the support team can impersonate the user to experience the application exactly as the user does. This helps in identifying and resolving the issue more efficiently.

But user impersonation can go beyond just straightforward support. User impersonation can be important for:

- **User Experience Testing**: Developers can use impersonation to see how the application or platform appears to users with different roles or permissions. This can be particularly useful for platforms with different user tiers, allowing for testing the user experience at each level.
- **Training and Onboarding**: In some cases, particularly within enterprise applications, user impersonation can be helpful for training. A trainer can impersonate a user role to guide new users through specific functionalities of the platform.
- **Data Access and Recovery**: If a user cannot access specific data or files, a support team member might use impersonation to access and recover that data on the user's behalf.
- **Auditing and Compliance**: In certain regulated industries, there might be a need to audit user actions or verify data accuracy. Impersonation can allow an auditor to access the system as a specific user to ensure compliance with regulations.

Of course, impersonating users is fraught with risks. Impersonation can easily lead to privacy breaches if not handled carefully. Admins and support can see personal or sensitive information. Also, if not implemented securely, impersonation features can be a potential attack vector for malicious actors. Ensuring that only authorized personnel can use impersonation and that all impersonation activities are logged for auditing is essential.

Because of these concerns, any system implementing user impersonation should have stringent security controls, logging, and auditing. It's also essential to have clear policies about when and why impersonation is used.

## Implementing User Impersonation

Implementing user impersonation is tricky. Literally, you are tricking your application into thinking that you are someone else. Here are the steps to consider through as you add user impersonation to your product:

1. **Ensure Strong Permissions and Auditing**: Only certain roles, such as admins and the support team, should be allowed to impersonate. As we said above, you should log every impersonation attempt, including the user doing the impersonation, the impersonated user, and the timestamp.
2. **Authentication & Authorization**: Established libraries, like [Clerk](/docs/custom-flows/user-impersonation#user-impersonation), have user impersonation built in. Use these wherever possible to lessen the likelihood of mistakes–get this wrong, and you can have serious privacy breaches. You should store roles in JWT or sessions to check user permissions.
3. **Impersonation**: When an authorized user requests to impersonate another user, you should store the original user's ID somewhere safe (e.g., in their session or another token), then update the user session/token to reflect the impersonated user's ID. When the user finishes impersonation, you must restore their session/token using the stored original user ID.
4. **UI Indication**: To avoid confusion, the UI should indicate when impersonation is active. A simple banner or color change can be used to show that the admin is in impersonation mode.

Let’s show how this would work with a basic JavaScript application. We’re going to set up two core components to this:

1. A backend Express server that will handle the actual impersonation.
2. A frontend React application that will provide the UI for the support team to use.

### The backend server

The backend is where we will incorporate the logic for our impersonation.

```javascript
const express = require('express')
const cors = require('cors')

const PORT = 3000

const app = express()
app.use(express.json()) // Middleware to parse JSON requests

const users = [
  { id: 1, username: 'admin', password: 'pass', roles: ['admin'] },
  // ... other users
]

// Use the cors middleware and set the frontend origin
app.use(
  cors({
    origin: '<http://localhost:3001>',
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
  }),
)

function ensureAdmin(req, res, next) {
  const roles = req.body.roles
  if (roles && roles.includes('admin')) {
    return next()
  } else {
    return res.status(403).json({ message: 'Access forbidden.' })
  }
}

app.post('/impersonate/:userId', ensureAdmin, (req, res) => {
  console.log(req)
  const userIdToImpersonate = req.params.userId
  const user = users.find((u) => u.id == userIdToImpersonate)
  if (!user) {
    return res.status(404).json({ message: 'User not found.' })
  }
  res.json({ message: 'Impersonation started.' })
})

app.post('/stop-impersonation', ensureAdmin, (req, res) => {
  res.json({ message: 'Impersonation stopped.' })
})

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
})
```

We start by importing the two modules needed:

- Express for the web server.
- cors for Cross-Origin Resource Sharing (useful when the frontend and backend are on different origins).

We then set up the Express server. We want to use the built-in `express.json()` middleware, enabling the server to parse incoming JSON payloads in requests.

After that, we set up a user array. This array simulates a database of users. Here, you would call your database to check users and roles. We then have to set up some CORS middleware. Without this, our server wouldn’t want to receive requests from our separate frontend.

ensureAdmin is the integral function of the server. This middleware checks if the user role includes "admin." If so, it allows the request to proceed. Otherwise, it sends back a forbidden status. This ensures the user is an admin before allowing them to impersonate another user.

We then have our two API Endpoints:

- `/impersonate/:userId`: An admin can attempt to impersonate another user by providing their ID.
- `/stop-impersonation`: An admin can stop the impersonation.

Finally, we start our server. Save this as `app.js` and you can run it with

```sh
node app.js
```

With that up and running, we can set up the frontend.

### The frontend application

The frontend will mimic the version of the application that an admin would see. When an admin chooses to impersonate, it makes a request to the `/impersonate/:userId` endpoint. It then updates the UI based on the response to show that impersonation is active. To stop impersonation, it makes a request to `/stop-impersonation`.

```jsx
import React, { useState } from 'react'
import './App.css'

function App() {
  const [isImpersonating, setIsImpersonating] = useState(false)
  const [message, setMessage] = useState('')

  const handleImpersonate = async (userId) => {
    try {
      const response = await fetch(`/impersonate/${userId}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ roles: ['admin'] }),
      })

      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`)
      }

      const data = await response.json()
      setIsImpersonating(true)
      setMessage(data.message)
    } catch (err) {
      console.error('Impersonation failed:', err)
      setMessage('Impersonation failed. Check console for details.')
    }
  }

  const handleStopImpersonating = async () => {
    try {
      const response = await fetch('/stop-impersonation>', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ roles: ['admin'] }),
      })

      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`)
      }

      const data = await response.json()
      setIsImpersonating(false)
      setMessage(data.message)
    } catch (err) {
      console.error('Failed to stop impersonation:', err)
      setMessage('Failed to stop impersonation. Check console for details.')
    }
  }

  return (
    <div className="App">
      {isImpersonating ? (
        <div className="impersonation-banner">You are impersonating another user!</div>
      ) : null}
      <button onClick={() => handleImpersonate(1)}>Impersonate User with ID 1 (admin)</button>
      {isImpersonating ? (
        <button onClick={handleStopImpersonating}>Stop Impersonating</button>
      ) : null}
      <p>{message}</p>
    </div>
  )
}

export default App
```

After importing React and our CSS, the core App function starts by declaring two state variables:

- `isImpersonating`: A boolean state that tracks whether the admin is currently impersonating another user.
- `message`: A string state to store and display messages from server responses or errors.

We then have our impersonation functions. `handleImpersonate` is an async function to impersonate a user by sending a POST request to `/impersonate/:userId`. If successful, it sets the state `isImpersonating` to `true` and displays a message from the server.

`handleStopImpersonating` is also an async function, this time to stop impersonation by sending a POST request to `/stop-impersonation`. If successful, it sets the state `isImpersonating` to false and displays a message from the server.

We then render our component. A button allows the admin to impersonate a user with ID 1 , and then a banner is displayed when the admin is impersonating another user. If the admin is impersonating, another button appears to stop the impersonation.

So when an admin logs in initially, they see this:

![Empower Support Team User Impersonation guide illustration](./cde8309564132cecdf8143f73c286562746e24f2-2000x1243.png)

Once they press the button, they start the impersonation:

![Empower Support Team User Impersonation guide illustration](./4de7c80017e36f443145441e7398316b59faa87a-2000x1264.png)

Then, they can stop the impersonation again:

![Empower Support Team User Impersonation guide illustration](./461c595a3f3254daeb412a146c6649070e15a1c0-2000x1264.png)

## Using Clerk for User Impersonation

What are the problems with the solution above? They are myriad.

First, we don’t have any authentication. This not only means the whole system is insecure, but it also means the impersonation is insecure. We’re just sending a plain text ‘admin’ message to the backend to tell them we can impersonate a user. The correct way to do this is using JWT. The token would contain the role information and be authenticated by the backend.

We also don’t have the logic incorporated to actually do anything with the impersonation. Neither do we have all the security checks in place to securely audit and log any impersonation.

We’re missing all this because impersonation is difficult to get right. You are coding an entirely different way to access your application, and you have to do it perfectly to avoid any security or privacy issues.

This is where using a platform like Clerk becomes essential. [User impersonation](/docs/custom-flows/user-impersonation) is incorporated directly into Clerk. All you have to do is call our [backend API](/docs/reference/backend-api/tag/actor-tokens/POST/actor_tokens) with your user ID (`user_id`) and the user ID of the user to impersonate (`sub`):

```javascript
const url = 'https://api.clerk.com/v1/actor_tokens'

const data = {
  user_id: 'user_1o4qfak5AdI2qlXSXENGL05iei6',
  expires_in_seconds: 600,
  actor: {
    sub: 'user_21Ufcy98STcA11s3QckIwtwHIES',
  },
}

async function postData() {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    })

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`)
    }

    const responseData = await response.json()
    console.log(responseData)
  } catch (error) {
    console.log('There was a problem with the fetch operation:', error.message)
  }
}

// Invoke the function to execute the fetch operation
postData()
```

This returns a token you can then use to impersonate the user for 10 minutes:

```json
{
  "sub": "user_1o4qfak5AdI2qlXSXENGL05iei6",
  "act": {
    "sub": "user_21Ufcy98STcA11s3QckIwtwHIES"
  }
}
```

You can then use this with the Clerk SDK for impersonation:

```javascript
import express from 'express'
import { ClerkExpressWithAuth } from '@clerk/clerk-sdk-node'

const app = express()

// Apply the Clerk express middleware
app.get(
  '/protected-endpoint',
  ClerkExpressWithAuth({
    // ...options
  }),
  (req, res) => {
    // The request object is augmented with the
    // Clerk authentication context.
    const { userId, actor } = req.auth

    res.json({ userId, actor })
  },
)

app.listen(3000, () => {
  console.log('Booted.')
})
```

There is an even easier way to use Clerk to impersonate a user–through your dashboard. Go to Users in your dashboard:

![Empower Support Team User Impersonation guide illustration](./b80a50270c2e867a35dd4822fa4c0fe576c3857e-2000x1090.png)

Then click to open the menu for the user you want to impersonate and choose "Impersonate user":

![Empower Support Team User Impersonation guide illustration](./a677bff1ae96063af5851ab123c37e66bd2aacfa-2000x1090.png)

Then, your support team is ready to impersonate a user straight away.

## More Support for Your Support Team

Adding user impersonation is a must for a well-functioning support team. It gives them to the tools they need to help your customers with their support needs without sacrificing privacy and security.

Getting it right in your application is a big challenge. Using an authentication solution such as Clerk with user impersonation built-in means you can easily have this embedded in your app, and you don’t have to worry about incorporating errors that lead to security issues. Check out the [Clerk user impersonation docs](/docs/custom-flows/user-impersonation#user-impersonation) to learn more about setting this up quickly with Clerk.

---

# Clerk Webhooks: Getting Started
URL: https://clerk.com/blog/webhooks-getting-started.md
Date: 2023-09-29
Category: Guides
Description: Learn how to get started with Webhooks to build integrations in a Nextjs application with Clerk's fully-featured authentication.

In an age of software that is composed of decoupled services built by specialized (often outsourced) teams, webhooks play an important architectural role. Webhooks enable us to outsource important user interactions and services to external platforms that are specifically designed for those purposes, and still have our applications be able to respond to those interactions.

For example, Clerk is specifically designed to handle authentication and user management related interactions on behalf of your applications. Webhooks allow Clerk to notify your application of these interactions so that you can implement custom follow-up logic. These interactions include (but are not limited to) signing in, changing profile information, creating an organization, or getting invited to an organization. Clerk ensures that all these important interactions are handled securely and responsively, while giving you a way to implement follow-up logic.

In this blog post we will build a simple webhook integration in a Next.js project. We will also explore the use cases of webhooks with Clerk and discuss how to best handle webhooks for production-ready applications.

This post assumes basic familiarity with Next.js and Clerk.

## Setting up Webhooks in Next.js

For this walkthrough we will start with a Next.js project that is already setup with Clerk. However, you can create a webhook subscription in any server-side web framework like Express or Fastify.

If you don’t already have a Next.js project setup with Clerk, you can start by cloning the Clerk Next.js App Router template.

```bash
git clone https://github.com/clerkinc/clerk-next-app.git
```

This will create a minimal Next.js project fully setup with Clerk’s embedded components and a secured dashboard page.

Create a `.env.local` file in the root of the project and add your keys here from the Clerk dashboard.

```env {{ title: '.env.local' }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_pub_key
CLERK_SECRET_KEY=your_clerk_secret_key
```

Let’s create an API route that will handle the Clerk webhook. This handler will live in `app/api/clerk/route.ts` and will have the URL of `your-app.com/api/clerk`.

Webhooks are POST requests by default, so let’s create a handler for receiving POST requests.

```typescript {{ title: 'app/api/clerk/route.ts' }}
export async function POST(request: Request) {}
```

The webhook event data is available as the request payload. Let’s parse it out and log it to the console.

```typescript {{ title: 'app/api/clerk/route.ts' }}
import { WebhookEvent } from '@clerk/nextjs/server'

export async function POST(request: Request) {
  const payload: WebhookEvent = await request.json()
  console.log(payload)
}
```

Clerk provides a `WebhookEvent` type for strong typescript inference. For now we will simply assert the webhook payload to be of this type.

Let’s also create a simple GET handler to test if we can access this URL.

```typescript {{ title: 'app/api/clerk/route.ts' }}
import { WebhookEvent } from '@clerk/nextjs/server'

export async function POST(request: Request) {
  const payload: WebhookEvent = await request.json()
  console.log(payload)
}

export async function GET() {
  return Response.json({ message: 'Hello World!' })
}
```

Add this endpoint as a public route to the Clerk middleware

```typescript {{ title: 'middleware.ts' }}
import { authMiddleware } from '@clerk/nextjs'

export default authMiddleware({
  publicRoutes: ['/', '/api/clerk'],
})

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

Start the development server.

```bash
npm run dev
```

Now we can navigate to this endpoint in the browser.

![Endpoint GET response screenshot](./972a73b2d937d644c6bb5a9b2f0134f8d8106f22-4000x1256.png)

That’s all we need to setup a simple webhook endpoint! Now we can provide Clerk with a URL to this endpoint, and we can start receiving events.

However, we are not ready to test this locally yet. While the application is accessible on `localhost` in our machine, it’s not accessible to anything outside our local network. Since webhook requests are originated from Clerk’s servers, the `localhost` URL will just point back to the Clerk server instead of our machine.

![Local network firewall access diagram](./b791b1246f3bf158efb58bbe197a82e9c49f7481-4000x2400.png)

While we could provide Clerk with the public IP address of our machine, any incoming requests to our local home/work networks will be blocked by firewalls.

![Firewall blocking external access diagram](./9ed5794a6cf6a053731e4eb9154f320829866cf6-4000x2400.png)

To work around this problem, we can use a tunneling service like [localtunnel](https://theboroer.github.io/localtunnel-www), which creates a secure tunnel to our local machine from the cloud.

![Localtunnel architecture diagram](./5016844b43d4fc00cf02cd6daf6b815f3ce48f5f-4000x2400.png)

Let’s setup localtunnel in a new terminal window.

```bash
npm install -g localtunnel
```

Once the installation is complete, we can create a tunnel to our local server.

```bash
lt --port 3000
```

You should see an output in your terminal like this.

![Localtunnel terminal output screenshot](./1d3c3bc2a40be4e3231b8a4017a53fb5418915af-4000x1256.png)

Localtunnel will generate a random URL on the `loca.lt` domain that will point to your locally running server on port 3000. We can navigate to this new URL in our browser.

Localtunnel has a security mechanism built in to prevent abuse from malicious actors and phishing links. It asks for the public IP address of your local machine to ensure that it’s really you trying to access your application. Follow the instructions on this page to get your public IP address, and submit it on this page.

Once that’s done, we can access our application on this URL. We should also be able to access our webhook endpoint.

If the application doesn’t show up, make sure both the nextjs server and localtunnel are running on separate terminal windows.

Now we are ready to plug our endpoint into Clerk’s dashboard. Go to the Webhooks page, and click Add Endpoint. Enter the localtunnel URL here. You can also add a description for this endpoint.

![Add webhook endpoint in Clerk dashboard screenshot](./f593b54494ffd98bd34a4c372921c8a898a92199-4000x2596.png)

Here you can select which specific events you want to receive at this endpoint. Let’s leave these unselected for now so that we can receive webhooks for all events. Hit Create.

Once the webhook endpoint is created, we can navigate to the Testing tab. Here we can trigger an example event manually to test our webhook endpoint. Select any event from the dropdown, and hit Send Example.

![Send example webhook event screenshot](./4006a769624fd7e31d9e62b3e6381ca8ca55019f-4000x3972.png)

If your application is running locally and tunneling is set up correctly, you should see a message in your console with the example event.

![Webhook payload logged in console screenshot](./97d57b6839a70569bf7698ee2a479568f7ff92a2-4000x1904.png)

You will also see a log entry in the Testing tab that shows the status of the webhook.

![Webhook delivery status log screenshot](./7dbbcb838ce8602bce5cce8fb1610b0512a1de01-4000x1412.png)

Even though the webhook was correctly received and logged to the console, the dashboard marks the webhook delivery as failed. To ensure that the delivery is marked as succeeded, we need to return a success response from our endpoint.

```typescript {{ title: 'app/api/clerk/route.ts' }}
export async function POST(request: Request) {
  const payload: WebhookEvent = await request.json()
  console.log(payload)
  return Response.json({ message: 'Received' })
}
```

Let’s send another example event. This time it the webhook should be marked as succeeded.

![Successful webhook delivery screenshot](./d1f7fd540f886be2559a152cd905f86e7a7bb64b-4000x1636.png)

Now we can test the webhook with real events. Navigate to your application and sign up with a new account. This should log two webhook events in the console - a `user.created` and a `session.created` event.

Congratulations, we now have a functioning webhook receiver in our application! We can now further explore what events are triggered by Clerk. For example,

- Updating the user profile sends a `user.updated` event
- Signing out of the application sends a `session.ended` event
- Remotely signing out a different device will result in a `session.revoked` event
- Creating a new organization will trigger an `organization.created` and an `organizationMembership.created` event

We can also see logs for real webhook events in the dashboard.

![Clerk dashboard webhook logs screenshot](./08a50b64eb3680e584d89b53cb1fd01c34db2fcf-4000x2636.png)

When you deploy this app to a cloud environment like Vercel, you will need to create a new endpoint in the Clerk dashboard that points to the deployed application instead of a localtunnel URL.

Note that localtunnel will generate a random URL everytime we start the tunnel, which means everytime we are testing with webhooks locally, we will need to create a new endpoint in the Clerk dashboard. To solve this, we can ask localtunnel to provision a specific URL by adding a `--subdomain` argument.

```bash
lt --port 3000 --subdomain unique-url-name
```

If the requested URL is available, it will be provisioned for us instead of a randomly generated URL. This will allow us to reuse the same endpoint in the Clerk dashboard everytime we are testing locally.

## Webhook Use Cases

So now that we are receiving webhook events from Clerk, what can we do with it?

### Data Synchronization

While Clerk’s own database acts as the primary source of truth for authentication and user management, you can create a copy of this data in your own database which gets updated asynchronously through webhooks. This allows you to query your own database for user data instead of relying on Clerk’s APIs, along with flexibility with the data model, complex queries, transactional guarantees, and real time behavior. This mechanism is called **data synchronization**.

### Event Driven Systems

Some advanced use cases might rely on asynchronous workflows and background jobs, such as sending notifications to users or other external systems. These workflows can be triggered to execute when a webhook event is received, for example, sending new users an onboarding email to your application, or subscribing them to mailing lists for software updates and promotional campaigns. Such systems are called **event driven systems**.

### Bring Your Own Email/SMS

Clerk allows you to bring your own Email and/or SMS servers for delivering auth-related messages which would usually be delivered by Clerk. Whenever an auth-related message is created, Clerk sends a webhook event with that message to your application. You can configure Clerk to not deliver these messages in the Clerk dashboard, and instead handle the delivery yourself by listening to the webhook events. (add links to HWR and docs)

We will walk through implementing some of these use-cases in future articles.

## Webhook Best Practices

While setting up a simple webhook receiver for testing purposes is straightforward, operating a webhook-driven system in production requires some additional work. Clerk implements webhook deliveries through [Svix](https://www.svix.com), a service that specializes in handling webhook deliveries. Svix manages concerns like scalability, logging, retries, security, and delivery guarantees. However, we still need to ensure that our application is setup to receive and process webhooks correctly. Let’s explore some best practices for implementing webhook endpoints for production-ready applications.

### Error Handling

Webhook deliveries are marked as successful or failed depending on the HTTP response returned by our webhook endpoint. A failed webhook delivery is retried with exponential backoff until it succeeds, ensuring that our application will never miss an event. The exponential backoff ensures that our application servers are not overloaded with retries and explode server resources.

Our webhook endpoints should be setup to return a successful response (20x) if everything went well. But in case of an error during the processing of a webhook, we can simply throw an error response (40x or 50x) and expect a retry, which often succeeds. We can also monitor the webhook status in the Clerk dashboard, which provides observability into failed webhook deliveries and help discover issues.

```typescript {{ title: 'app/api/clerk/route.ts' }}
import { WebhookEvent } from '@clerk/nextjs/server'

export async function POST(request: Request) {
  try {
    const payload: WebhookEvent = await request.json()
    console.log(payload)

    // process the event

    // everything went well
    return Response.json({ message: 'Received' })
  } catch (e) {
    // something went wrong
    // no changes were made to the database
    return Response.error()
  }
}
```

### Security

Since webhooks requests can originate on any remote server, webhook endpoints are vulnerable to attacks from malicious actors. Securing webhook endpoints is important to ensure malicious actors cannot make unwanted changes to your application.

Svix adds security measures to webhook requests by adding a secure hash in the request headers created using a secret key. We can use the `svix` library in our webhook endpoint to validate that the request was sent by a legitimate Svix server and not a malicious actor.

```typescript {{ title: 'app/api/clerk/route.ts' }}
import { WebhookEvent } from '@clerk/nextjs/server'
import { headers } from 'next/headers'
import { Webhook } from 'svix'

const webhookSecret = process.env.CLERK_WEBHOOK_SECRET || ``

async function validateRequest(request: Request) {
  const payloadString = await request.text()
  const headerPayload = headers()

  const svixHeaders = {
    'svix-id': headerPayload.get('svix-id')!,
    'svix-timestamp': headerPayload.get('svix-timestamp')!,
    'svix-signature': headerPayload.get('svix-signature')!,
  }
  const wh = new Webhook(webhookSecret)
  return wh.verify(payloadString, svixHeaders) as WebhookEvent
}

export async function POST(request: Request) {
  const payload = await validateRequest(request)
  console.log(payload)
  return Response.json({ message: 'Received' })
}
```

We can get the webhook signing secret from the Clerk dashboard and store in a `CLERK_WEBHOOK_SECRET` environment variable.

![Webhook signing secret screenshot](./cb560fdd36109e0abd0ee80c2cdbc83ff625a859-4000x2596.png)

Now our webhook endpoint is fully secure and ready to be deployed to production servers.

### Selective Events

In our walkthrough, we configured Clerk to trigger the webhook endpoint for all events. While this was useful for testing, we want to minimize the use of server resources by only processing events that we actually care about.

For production endpoints, we can apply event filters in the Clerk dashboard by selecting the events that we are processing in the application, and leaving everything else unselected.

![Select webhook events screenshot](./0d52d69df0819bc7ac254509dddc9864c0764a63-4000x2176.png)

## Summary

Webhooks play an important role in creating unified user experiences through integrations like data sync and event driven systems. In this article we setup a simple webhook endpoint, explored some use cases, and addressed concerns like security and error handling.

We will dive deep into specific use cases around data sync and event driven systems in future blog posts and walk through implementing them into our projects.

To learn more about webhooks, check out [the ultimate webhooks guide](/blog/what-are-webhooks), or to learn more about the webhook events exposed by Clerk, [read the documentation](/docs/integrations/webhooks/overview).

---

# A Complete Guide to Session Management in Next.js
URL: https://clerk.com/blog/complete-guide-session-management-nextjs.md
Date: 2023-09-27
Category: Guides
Description: Session management allows users to stay logged in across multiple tabs devices and maintains security by tracking user sessions.

Session management is a concept that flies under the radar in most applications. It’s built into every authentication library you are using, and seamlessly allows users to stay logged in, use different tabs, and stay secure while they are using your app.

But because it is abstracted away by auth systems, it’s also opaque. How does session management work to keep track of your usage?

Here, we want to build session management in Next.js without using any authentication library to show you what is really happening under the hood.

## What is a session?

This might seem like a trivial question, but it doesn’t have a trivial answer. One of the reasons that session management is often abstracted away from developers is that it’s a difficult concept to grok and implement.

A [session](/blog/what-is-session-management) is a series of interactions between a user and an application that occur within a given time frame.

A session is initiated when a user logs in or starts interacting with the application and is typically terminated when the user logs out or after a period of inactivity. It is a way to preserve certain data/state across multiple requests between the client and the server in an inherently stateless environment, which is the HTTP protocol.

These are the main components of a session:

1. **Session Identifier** (Session ID): A unique string that distinguishes one session from another. It is sent by the server to the client after successful login and is usually stored in a cookie on the client side.
2. **Session Store**: A storage mechanism, often on the server side, where session data, such as user details and preferences, are stored. This could be in-memory storage, a database, a file system, or a distributed cache, depending on the application's requirements.
3. **Session Data**: Information stored in the session, often including user preferences, user identification and authentication data, and temporary application state. This data is used to personalize the user experience and to maintain the state of the application between different requests from the client.
4. **Session Timeout**: A mechanism to terminate sessions after a predefined period of inactivity to minimize the risk of unauthorized access. Once a session is timed out, the user needs to re-authenticate to continue interacting with the application.
5. **Session Cookie**: A type of HTTP cookie sent from the server to the client’s browser to store the session ID.

So a session lifecycle starts with **creation**, when a session is created from a user login or starts interacting with an application. This is when a unique session ID is generated and associated with the user. The session is **maintained** through the session ID that is transmitted with each subsequent request from the client and is used to retrieve and manage session data on the server. The session **ends** when the user logs out or after a period of inactivity (session timeout). Any data stored in the session is usually deleted or invalidated.

## Why session management is important

Session management is pivotal for the seamless functioning and robust security of web applications. When it's not implemented effectively, applications become vulnerable to a host of security issues and often provide an experience that's frustrating for users. Thus, grasping and applying solid session management strategies are absolutely critical if one wants to build web applications that are secure, scalable, and user-friendly.

### Importance for Security

Session management acts as the security guard of web applications. It works to correctly identify and authenticate users, ensuring they only access what they’re allowed to. It’s there to protect sensitive information belonging to the users by allowing only authenticated and authorized individuals to access it. It also safeguards session identifiers as they are transmitted and stored. It shields applications from security threats, such as session hijacking, session fixation, and Cross-Site Request Forgery (CSRF).

### Enhancement of User Experience

From a user experience standpoint, sessions empower applications to remember user preferences and deliver personalized content, crafting an experience that is more engaging for the user. Efficient session management allows users to move between different devices and browser tabs when interacting with applications without needing to keep logging in. This ease of access enhances overall user convenience and experience. Sessions are like invisible assistants, remembering temporary data between user requests, meaning users can roam freely within an application without the fear of losing their progress or context.

Beyond security and user experience, sessions are a tool for collecting valuable data regarding user behavior and preferences. This kind of information is a goldmine for businesses, helping them make well-informed decisions and refine their strategies based on user interaction and needs. In essence, it’s like having a pulse on user behavior, enabling the refinement of business strategies and decision-making.

## Setting up session management in Next.js

We’re going to produce a simple two page site that allows us access to a protected page if we are logged in. Fundamentally, this is an authentication setup, but we are going to set it up using JSON Web Tokens (JWT) that we’ll store on the client. This will give the user a live "session," so once they have logged in, they can continue to access the protected page, until the token and session expires.

We’re going to use Next.js 13 and the App Router. Let’s first create a new Next project:

```sh
npx create-next-app@latest
```

To follow this tutorial you should use the defaults from the prompts. We’ll then open up the code in our IDE. We’re using VS Code, so we can:

```sh
cd my-app
code .
```

We also want to install the libraries we’re going to use. None of these are authentication libraries. Instead they are libraries that let us directly access cookies and databases, and implement JWTs.

```sh
npm install js-cookie sqlite sqlite3 jsonwebtoken
```

- `js-cookie` is a lightweight JavaScript library that provides a straightforward API to handle browser cookies, allowing you to create, read, and delete cookies in a way that works with various JavaScript environments, like the browser and Node.js.
- `sqlite` is a library that serves as a lightweight, file-based database engine, allowing developers to utilize SQL-based database functionality without the need for a full-fledged database management system.
- `sqlite3` is a Node.js library that provides bindings to SQLite3, enabling interaction with SQLite databases, allowing developers to perform operations like querying, updating, and deleting records in SQLite databases from within Node.js applications.
- `jsonwebtoken` is a Node.js library that allows you to securely handle JSON Web Tokens (JWTs), which are compact, URL-safe means of representing claims to be transferred between two parties, commonly used for authentication and information exchange in web development.

Now, we’re ready to code.

### The login page

First, let’s remove the boilerplate from the app/page.js file and replace it with this code:

```jsx
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'

function LoginPage() {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const router = useRouter()

  const handleLogin = async (e) => {
    e.preventDefault() // Prevent default form submission

    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          username,
          password,
        }),
      })

      if (!response.ok) throw new Error('Login failed')

      const { token } = await response.json()
      document.cookie = `token=${token}; path=/`
      router.push('/protected')
    } catch (error) {
      console.error(error)
    }
  }

  return (
    <div>
      <form onSubmit={handleLogin}>
        <label>
          Username:
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            required
          />
        </label>
        <br />
        <label>
          Password:
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
        </label>
        <br />
        <button type="submit">Log In</button>
      </form>
    </div>
  )
}

export default LoginPage
```

### Login Code

This is going to be our login page. The UX is simple–just a form with a username field, a password field, and a submit button.

![Complete Guide Session Management Nextjs guide illustration](./1b1e772a51af40ea7d19df96151a464aac6a65c9-2000x1264.png)

When the button is clicked, the `handleLogin` function is called.

The `handleLogin` function is an asynchronous event handler that deals with the logic of the login attempt. There are five main components to this function:

1. `const response = await fetch("/api/login", {...})`. This sends a POST HTTP request to the `/api/login` endpoint with the username and password as the body of the request.
2. `if (!response.ok) throw new Error("Login failed")`. This checks if the response received from the server is not ok (i.e., the HTTP status code is not in the range 200-299). If it's not ok, it throws an error with the message "Login failed".
3. `const { token } = await response.json()`. If the response is ok, this parses the JSON body of the response and extracts the token property from it. This is the token that authenticates the user for subsequent requests.
4. `document.cookie = token=${token}; path=/`. This sets a cookie in the user's browser with the name token and the value received from the login API, which is accessible to any path in the domain.
5. `router.push("/protected")`. This navigates the user to the `/protected` route of the app.

So this is calling the login API, and if it receives a token back, sets that token in the browser and passes the user to the protected page. If the login fails and no token comes back, it just tells the user “Login failed.”

### The login API

Let’s take a look at the login API next:

```jsx
import { NextResponse } from 'next/server'
import sqlite3 from 'sqlite3'
import { open } from 'sqlite'
import jwt from 'jsonwebtoken'

async function authenticateUser(username, password) {
  let db = null

  // Check if the database instance has been initialized
  if (!db) {
    // If the database instance is not initialized, open the database connection
    db = await open({
      filename: 'userdatabase.db', // Specify the database file path
      driver: sqlite3.Database, // Specify the database driver (sqlite3 in this case)
    })
  }

  const sql = `SELECT * FROM users WHERE username = ? AND password = ?`
  const user = await db.get(sql, username, password)
  return user
}

export async function POST(req) {
  const body = await req.json()
  const { username, password } = body

  // Perform user authentication here against your database or authentication service
  const user = await authenticateUser(username, password)
  const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {
    expiresIn: '1m',
  })
  return NextResponse.json({ token })
}
```

Let’s go through the POST function first. This is the part of the code that receives the POST call from the login page and authenticates our users.

- `const body = await req.json()` reads the JSON body from the incoming request object (req). It is awaited because reading the body is an asynchronous operation.
- `const { username, password } = body`. After the body is read, the username and password are destructured from it. These would be the username and password sent in the request, likely provided by the user through a form in the frontend.
- `const user = await authenticateUser(username, password)` calls the authenticateUser function to authenticate users against the user database.
- `const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: "1m" })`. If the user is successfully authenticated, a JWT is generated using the jwt.sign method. This token includes the user’s ID (user.id) as part of its payload. The token is signed using a secret key stored in `process.env.JWT_SECRET`, and it’s set to expire in 1 minute (expiresIn: "1m").
- `return NextResponse.json({ token })`. Finally, if everything is successful, the function returns a response with the generated token in JSON format.

The core parts here are the `authenticateUser` call and the JWT signing. We’ll look closer at the `authenticateUser` call in a moment, but let’s discuss JWTs first as they are integral to session management.

### JSON Web Tokens

JSON Web Tokens (JWTs) are a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of the token, which is then signed to secure the information.

JWTs are commonly used for authentication and information exchange in web development. When a user logs in, the server generates a JWT that encodes user information (like user ID) and sends this token to the client. The client then includes this token in the Authorization header in subsequent requests, allowing the server to identify and authorize the user.

JWTs consist of three parts separated by dots (.):

1. **Header**: The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.
2. **Payload**: The payload contains the claims. Claims are statements about a user and additional metadata, such as expiry times, which are critical in session management.
3. **Signature**: The signature is used to verify the message wasn't changed along the way and, in the case of tokens signed with a private key, it can also verify the sender of the JWT.

A JWT might look like this:

```text
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```

- The first part is the Header, Base64Url encoded.
- The second part is the Payload, also Base64Url encoded.
- The third part is the Signature.

So here, the payload is our user ID, and we’re signing the token with our `JWT_SECRET` in our `.env.local`. This secret should be a long, random string. You can [generate a random JWT secret](https://mojitocoder.medium.com/generate-a-random-jwt-secret-22a89e8be00d) like this:

```sh
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```

### Authenticating the user

The `authenticateUser` function is an asynchronous function intended to authenticate a user based on a provided username and password against a user record in an SQLite database.

Session management is really user management. So, you need to have a database set up of all your users so you can get, in this case, their IDs to add as the JWT payload. Here, we’ve set up a small SQLite database locally with a single user. To do this properly, you’ll need a full database for all your users, plus a way to add those users.

In this function, we initialize a connection to an SQLite database file named `userdatabase.db` using the sqlite [`open`](https://javascript.plainenglish.io/using-sqlite-with-next-js-13-cfa270e1d7ba) method. Once the database connection is established, we create an SQL query string, `sql`, to select a user from the users table where the `username` and `password` match the provided arguments.

We then execute this SQL query using `await db.get(sql, username, password)`, which will return the first row that satisfies the conditions (i.e., where the username and password match the input), or return undefined if no such row exists.

We then return that user to the `POST` function, which uses the ID within the JWT payload, and returns that to the client.

### Validating the token

After we’ve got the user and added the token to the user’s browser cookies, we are routed to the /protected page.

```jsx
'use client'
import Cookies from 'js-cookie'
import jwt from 'jsonwebtoken'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'

function ProtectedPage() {
  const router = useRouter()

  useEffect(() => {
    const token = Cookies.get('token')

    if (!token) {
      router.replace('/') // If no token is found, redirect to login page
      return
    }

    // Validate the token by making an API call
    const validateToken = async () => {
      try {
        const res = await fetch('/api/protected', {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        })

        if (!res.ok) throw new Error('Token validation failed')
      } catch (error) {
        console.error(error)
        router.replace('/') // Redirect to login if token validation fails
      }
    }

    validateToken()
  }, [router])

  return <div>Protected Content</div>
}

export default ProtectedPage
```

Not too much is happening on this page, apart from checking the token exists on the local client and then sending it as part of the authorization header as a bearer token to the protected API endpoint. That endpoint is where we do two things:

1. Check that the token hasn’t expired
2. Check that the token is valid

```jsx
import jwt from 'jsonwebtoken'
import { NextResponse } from 'next/server'
import { headers } from 'next/headers'

export async function GET() {
  try {
    const headersInstance = headers()
    const authHeader = headersInstance.get('authorization')

    const token = authHeader.split(' ')[1]

    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    if (!decoded) {
      return NextResponse.json(
        { message: 'Expired' },
        {
          status: 400,
        },
      )
    } else if (decoded.exp < Math.floor(Date.now() / 1000)) {
      return NextResponse.json(
        { message: 'Expired' },
        {
          status: 400,
        },
      )
    } else {
      // If the token is valid, return some protected data.
      return NextResponse.json(
        { data: 'Protected data' },
        {
          status: 200,
        },
      )
    }
  } catch (error) {
    console.error('Token verification failed', error)
    return NextResponse.json(
      { message: 'Unauthorized' },
      {
        status: 400,
      },
    )
  }
}
```

The first part is to extract the authorization header from the request, and then extract the token by splitting it from `Bearer`.

Then we use the verify method on the JWT with the secret we used to sign it originally. This will allow us to show it is an authenticated token. If the token is invalid, `jwt.verify` throws an error. From there, we want to check whether the token has expired by comparing the exp field in the decoded token with the current timestamp.

If the token is valid and not expired, the function returns a JSON response (with some data if you wanted). If the token is invalid or expired, or if any error occurs during verification, it returns a JSON response with a 400 status code and a message "Unauthorized".

If all is good, the user will be directed to the protected page:

![Complete Guide Session Management Nextjs guide illustration](./da7c8a02cfdbb7b9a65e0f53ae3c725e060bcdc8-2000x1264.png)

Because the token persists within the browser, they can also go to the protected page in another tab:

![Complete Guide Session Management Nextjs guide illustration](./419859cc96776f5a43a5d90463299cc8f812cd6b-2000x1264.png)

But the token expires after only a minute. If they try and reload after that time, they are redirected to the login page again:

![Complete Guide Session Management Nextjs guide illustration](./1b1e772a51af40ea7d19df96151a464aac6a65c9-2000x1264.png)

And you have successfully managed a session!

### The problems with this approach

This is the most basic code to get session management working. But even here we’ve had to manage our own user database, manage our own secrets, and manage all of the logic in between. It isn’t a robust system:

- We’re missing the substantial error handling that is needed for this to work properly. For instance, we want to handle the case where the Authorization header is missing or malformed, to avoid issues like calling split on undefined, which would throw an error.
- We’re missing any of the checks in the user management system for encrypting passwords.
- We’re not managing our database calls well or persisting a connection.

If you are building your own session management system, you are also building your own user management system, and becoming a database administrator.

## Session management in Next.js with Clerk

The easier way is to [use a specifically designed authentication library](/nextjs-authentication). Here, we’re going to use Clerk, but any auth library is going to take this headache away from you.

First, we’ll set up the project in exactly the same way:

```
npx create-next-app@latest
```

Again, use the defaults from the prompts. Then open the code

```
cd my-clerk-app
code .
```

We don’t need to install all the libraries from before. All we need this time is the Clerk SDK:

```sh
npm install @clerk/nextjs
```

We’ll start with adding the environment variables we need for Clerk–our `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`–into our `.env.local file`:

```text
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_something
CLERK_SECRET_KEY=sk_test_something
```

After that, we need to add the [`<ClerkProvider />`](/docs/components/clerk-provider) wrapper to the app. This is the critical component for session management. It is what we provide the active session and user information to all of Clerk’s components anywhere in the app. We add it in` Layout.js`, wrapping the entire body of our application:

```jsx
import { ClerkProvider } from '@clerk/nextjs'

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

export default function RootLayout({ children }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

We’ll then add some middleware. This is what decides which pages are protected and which aren’t. The default code below protects every page on the site:

```jsx
import { authMiddleware } from '@clerk/nextjs'

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your middleware
export default authMiddleware({})

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

We then need three pages. First, a sign up page, which will go at `app/sign-up/[[...sign-up]]/page`:

```jsx
import { SignUp } from '@clerk/nextjs'

export default function Page() {
  return <SignUp />
}
```

Then a sign in page at `app/sign-in/[[...sign-in]]/page`:

```jsx
import { SignIn } from '@clerk/nextjs'

export default function Page() {
  return <SignIn />
}
```

Finally, we’ll add a button to interact with these pages on our home page:

```jsx
import { UserButton } from '@clerk/nextjs'

export default function Home() {
  return (
    <div>
      <UserButton afterSignOutUrl="/" />
    </div>
  )
}
```

That’s a lot less code. We need to add paths to these in our `.env.local` as well:

```text
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

And that’s it. Run `npm run dev` and you’ll get a way to sign in:

![Complete Guide Session Management Nextjs guide illustration](./0eb8660cecafbbf96fd7fe4858a3fab97b6868d1-2000x1264.png)

Sign in, and you’ll be at a protected account page:

![Complete Guide Session Management Nextjs guide illustration](./0eb8660cecafbbf96fd7fe4858a3fab97b6868d1-2000x1264.png)

And that is all that’s needed for session management with Clerk. You can set up other [session options](/docs/authentication/configuration/session-options) like token timeouts and custom payloads in your [dashboard](https://dashboard.clerk.com).

## The pitfalls of session management

Session management is a critical component in web development, acting as the gatekeeper to user-specific, sensitive information and functionalities.

But implementing robust session management is not without its challenges. Security, performance, and usability issues are all concerns you’ll have to deal with when building session management in Next.js. Like many aspects of authentication, [session management](/blog/what-is-session-management) is one that is best left to dedicated libraries and solutions. Check out the [Clerk session docs](/docs/authentication/configuration/session-options) to find out more about setting this up easily with Clerk.

---

# The Advanced Guide to Passwordless Authentication in Next.js
URL: https://clerk.com/blog/advanced-guide-passwordless-authentication-nextjs.md
Date: 2023-08-25
Category: Guides
Description: Learn how to implement passwordless authentication in Next.js using magic links, social OAuth and SAML SSO.

The password has been the bedrock of account security for decades. But their vulnerabilities are well-known. From weak password choices to recycling them across platforms, users inadvertently make it simple for bad actors to gain unauthorized access.

The concept of “passwordless authentication” has become a solution to this problem. Single Sign-Ons (SSOs), OAuth, SAML, [magic links](/blog/magic-links) —each of these techniques can enhance user experience and increase security in your app. What has previously stopped developers is the difficulty of implementation. But with modern JavaScript libraries, these options now come out-of-the-box.

In this guide, we’ll implement [passwordless authentication in Next.js](/nextjs-authentication) all the way from creating the sign in page to testing each of the above techniques to usher in more secure, streamlined authentication.

## Setting Up Next.js Application

This article will use [Next.js 13](https://nextjs.org) and [Clerk’s Next.js SDK](https://www.npmjs.com/package/@clerk/nextjs) to create a passwordless flow. Start by creating a new Node project.

```sh
 npm init -y
```

Create a new Next.js app. This command will use TypeScript by default, which we recommend:

```sh
npx create-next-app@latest
```

Once you have a Next.js application ready, you also need to install [Clerk’s Next.js SDK](https://www.npmjs.com/package/@clerk/nextjs) library. The SDK contains prebuilt React components and hooks, allowing for fast time-to-market.

```sh
npm i @clerk/nextjs
```

Now, create a new `.env.local` file in the source of your project, which will contain all keys and endpoints. See keys in the [Clerk dashboard](https://dashboard.clerk.com/last-active?path=api-keys).

```sh
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
```

Continue by mounting the `<ClerkProvider>` wrapper which will serve as the session and user context provider to the app.

It is advised that the `<ClerkProvider>` wraps the `<body>` element, allowing context-accesibility anywhere within the app.

```tsx
// app/layout.tsx
import './globals.css'
import { Inter } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'

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

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={inter.className}>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

If you intend to use the `<ClerkProvider>` outside the root layout, you must ensure it is also a server component, just like the root layout.

### Protect the App

After giving the app Clerk’s context, you will now create a middleware, which will dictate which page should be public and which need to be behind an authentication wall.

Create a `middleware.ts` file in the root folder of your app (or inside `src/` if you opted for that).

```jsx
import { authMiddleware } from '@clerk/nextjs'

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
export default authMiddleware({})

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

This will protect your entire application. By accessing the application you will be redirected to the Sign Up page. Read more about [authMiddleware](/docs/nextjs/middleware) to see how to make other routes public.

## Implementing Magic Links in Next.js Using Clerk

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./610e8c4b679fbc725708dd90ea03dc2e496c4436-3592x1198.png)

Magic links offer a seamless and secure alternative to traditional password-based authentication, elevating user convenience while bolstering security and representing a modern shift in authentication.

In this section, you will methodically explore the steps to [implement magic links in Next.js](/blog/magic-links) through the Clerk platform.

### Creating the Sign Up Page

We’ll start off by creating a simple sign up page, containing a magic link flow. Start by following the steps outlined below:

1. Create a new folder `sign-up` in the `app` folder (so `app/sign-up`).

2. Create a subfolder `[[...sign-up]]` in the `sign-up` folder (so `app/sign-up/[[...sign-up]]`). This will use [Next.js optional catch-all route](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes).

3. Create a new file `page.tsx` inside that subfolder (finally, `app/sign-up/[[...sign-up]]/page.tsx`).

4. Import the necessary dependencies:

```jsx
'use client'
import React from 'react'
import { useRouter } from 'next/navigation'
import { useSignUp } from '@clerk/nextjs'
```

5. Continue by creating a new functional component and a few hooks that will be used later down the road to conditionally render components and perform authentication:

```jsx
export default function SignUp() {
  const [emailAddress, setEmailAddress] = React.useState('')
  const [expired, setExpired] = React.useState(false)
  const [verified, setVerified] = React.useState(false)
  const router = useRouter()
  const { signUp, isLoaded, setActive } = useSignUp()
}
```

6. You’ll now check whether the `useSignUp` is loaded:

```jsx
if (!isLoaded) {
  return null
}
```

7. Next, destructure methods that will be used to initiate and cancel a magic link flow:

```jsx
const { startMagicLinkFlow, cancelMagicLinkFlow } = signUp.createMagicLinkFlow()
```

8. You’ll now create a method that will perform actual magic link flow:

```tsx
async function submit(e: any) {
  e.preventDefault()
  setExpired(false)
  setVerified(false)
  if (signUp) {
    // Start the sign up flow, by collecting
    // the user's email address.
    await signUp.create({ emailAddress })

    // Start the magic link flow.
    // Pass your app URL that users will be navigated
    // when they click the magic link from their
    // email inbox.
    // su will hold the updated sign up object.
    const su = await startMagicLinkFlow({
      redirectUrl: 'http://localhost:3000/verification',
    })

    // Check the verification result.
    const verification = su.verifications.emailAddress
    if (verification.verifiedFromTheSameClient()) {
      setVerified(true)
      return
    } else if (verification.status === 'expired') {
      setExpired(true)
    }

    if (su.status === 'complete') {
      // Sign up is complete, we have a session.
      // Navigate to the after sign up URL.
      setActive({ session: su.createdSessionId || '' })
      router.push('/after-sign-up')
      return
    }
  }
}
```

This method takes the inputted email address of an user and starts the magic link flow. Then, a link is sent to the given email with a specified redirect URL. Later, the method will perform verification and update the state accordingly.

9. Now, let’s add some conditional logic to render the components giving the user some input and feedback.

```jsx
if (expired) {
  return <div>Magic link has expired</div>
}

if (verified) {
  return <div>Signed in on other tab</div>
}

return (
  <form onSubmit={submit}>
    <input type="email" value={emailAddress} onChange={(e) => setEmailAddress(e.target.value)} />
    <button type="submit">Sign up with magic link</button>
  </form>
)
```

That’s it for the sign up page. As we specified a redirect URL above in the submit method, we also need to handle that logic.

### Setting Up the Verification Page

To complete the magic link flow, we need to perform the verification that will be the final decision between authentication or denial.

1. Start by creating a new folder `verification` in the `app` folder (so, `app/verification`).

2. Create a new file `page.tsx` inside the folder (so, `app/verification/page.tsx`).

3. Import the dependencies:

```jsx
import { MagicLinkErrorCode, isMagicLinkError, useClerk } from '@clerk/nextjs'
import React from 'react'
```

4. Next, create a functional component for the page and the hooks:

```jsx
function Verification() {
  const [verificationStatus, setVerificationStatus] = React.useState('loading')

  const { handleMagicLinkVerification } = useClerk()
}
```

5. Now, we’ll use the `useEffect` hook from React to perform the flow verification.

```tsx
React.useEffect(() => {
  async function verify() {
    try {
      await handleMagicLinkVerification({
        redirectUrl: 'https://redirect-to-pending-sign-up',
        redirectUrlComplete: 'https://redirect-when-sign-up-complete',
      })
      // If we're not redirected at this point, it means
      // that the flow has completed on another device.
      setVerificationStatus('verified')
    } catch (err: any) {
      // Verification has failed.
      let status = 'failed'
      if (isMagicLinkError(err) && err.code === MagicLinkErrorCode.Expired) {
        status = 'expired'
      }
      setVerificationStatus(status)
    }
  }
  verify()
}, [])
```

The verification will happen on the page load, hence the `useEffect` hook.

6. Finish it off with some conditional rendering again:

```jsx
if (verificationStatus === 'loading') {
  return <div>Loading...</div>
}

if (verificationStatus === 'failed') {
  return <div>Magic link verification failed</div>
}

if (verificationStatus === 'expired') {
  return <div>Magic link expired</div>
}

return <div>Successfully signed up. Return to the original tab to continue.</div>
```

### Testing the Magic Link Flow Integration

After writing down the code, let’s test the magic flow integration by starting up the Next.js app with the following command:

```sh
npm run dev
```

After the app has started, navigate to `http://localhost:3000` in your browser. In the input area presented, enter a valid email address on which you will receive a magic link.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./0a1647dbf3a2f262e28679c5f702b9d3c933a516-584x62.png)

Then, press **Sign up with magic link button**. Shortly, an email from Clerk will arrive with the magic link for signing up.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./6053b9de32b521426c399fb571dca03925384ed0-1230x688.png)

If the verification is successful, you will be presented with a confirmation message.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./614b77c622741d4f4b0207b70fde8b9dd1d1c328-956x60.png)

## Implementing OAuth in Next.js Using Clerk

OAuth stands as a cornerstone in modern authentication, allowing users to securely log in using third-party accounts without sharing passwords.

When integrated effectively, it can transform the user authentication experience, making it both swift and secure.

In this section, we will delve into the intricacies of setting up OAuth, harnessing the utility of Clerk.

### Setting Up Social Connection in Clerk Dashboard

To enable a social connection provider, go to the [Clerk Dashboard](https://dashboard.clerk.com), select your **Application**, and navigate to **User & Authentication >** **Social Connections.** Social connection configuration consists of the following steps:

1. Enable the providers you want to use.
2. (production instances only) Enter your OAuth credentials (Client ID and Client Secret) for each provider
3. (production instances only) Copy the `Authorized redirect URI` from the Clerk Dashboard to the provider's app configuration.

Clerk supports multiple providers, but for the purposes of this guide we will enable social connection with **Google**.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./ee5b5d833d6af497e0502c3a49363c7a58024ff2-3522x1958.png)

In development, after applying these changes, you're good to go! To make the development flow as smooth as possible, Clerk uses pre-configured shared OAuth credentials and redirect URIs.

Using shared OAuth credentials should not be treated as secure and you will be considered to operate under Development
mode. For this reason, you will be asked to authorize the OAuth application every single time. Also, they are not
allowed for production instances, where you should provide your own instead.

### Creating the Social Sign Up Page

You can start with a blank sign-up page (`app/sign-up/[[…sign-up]]`), described above in the section **Implementing Magic Links in Next.js Using Clerk**.

1. Import the dependencies:

```jsx
'use client'
import { useSignIn } from '@clerk/nextjs'
import { OAuthStrategy } from '@clerk/nextjs/server'
```

2. Create a functional component with some hooks:

```jsx
export default function SignInOAuthButtons() {
  const { signIn } = useSignIn()
}
```

3. Next, create a method that will handle the single-sign on (SSO) process:

```tsx
const signInWith = (strategy: OAuthStrategy) => {
  if (signIn) {
    return signIn.authenticateWithRedirect({
      strategy,
      redirectUrl: '/sso-callback',
      redirectUrlComplete: '/',
    })
  }
}
```

4. And finish it off with rendering a simple button for signing up:

```jsx
return (
  <div>
    <button onClick={() => signInWith('oauth_google')}>Sign in with Google</button>
  </div>
)
```

### Creating a Single-Sign On (SSO) Callback in Next.js

[Single Sign-On (SSO)](https://en.wikipedia.org/wiki/Single_sign-on) streamlines user access across multiple services, providing a centralized and more efficient authentication process.

In this section, you’ll create an SSO callback using a very simple component from Clerk.

1. Create a new folder `sso-callback` in the `app` folder (so `app/sso-callback`).

2. Create a new file `page.tsx` in that folder (so `app/sso-callback/page.tsx`).

3. Import a dependency:

```jsx
import { AuthenticateWithRedirectCallback } from '@clerk/nextjs'
```

4. Finish it off by returning a Clerk redirection component - [AuthenticateWithRedirectCallback](/docs/component-reference/authenticate-with-redirect-callback), used to complete an OAuth flow:

```jsx
export default function SSOCallback() {
  // Handle the redirect flow by rendering the
  // prebuilt AuthenticateWithRedirectCallback component.
  // This is the final step in the custom SAML flow
  return <AuthenticateWithRedirectCallback />
}
```

### Testing the Social OAuth Flow Integration

Let’s test the social integrations by starting up the Next.js app with the following command:

```sh
npm run dev
```

Navigate to `http://localhost:3000` in the browser and you’ll be presented with a button Sign in with Google.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./46c659f01aa89cf77ed0822c03847b6b326a0a12-268x70.png)

Press the button and you will be redirected to the Google OAuth. Select an account you want to use.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./778d5c5d55cb14cc2243c0899e4c7a5abfa7adf1-1078x1424.png)

After successful authentication, you will be redirected to the user page.

## Implementing SAML SSO in Next.js Using Clerk

[The Security Assertion Markup Language (SAML)](https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language) is a pivotal standard for Single Sign-On (SSO) implementations, offering secure and efficient user authentications across different services.

Mostly used by B2B companies, it allows companies to utilize one set of credentials across all of their tools.

This section will cover how to integrate SAML SSO in the Next.js, using Clerk.

### Setting Up SAML SSO in Clerk Dashboard

To enable a social connection provider, go to the [Clerk Dashboard](https://dashboard.clerk.com), select your Application, and navigate to **User & Authentication > Enterprise Connections**.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./baba635074a4ce6b4c79b27e65baff08b1755d2a-3600x2002.png)

Then, press + Create connection. Enter the name and the domain for the connection.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./d03820277a6182edf88d44a60793001c1b54bbf7-1256x886.png)

Go to the newly created connection and perform the setup.

![Advanced Guide Passwordless Authentication Nextjs tutorial illustration](./ad1eecbfde9469e47a5b17f746a856ab5f051737-3600x2004.png)

Here, you can assign the Identity Provider (IdP) SSO URL, IdP Entity ID and the certificate.

### Creating the SAML Sign In Page in Next.js Using Clerk

Creating the SAML sign in page is very simple. We will tweak the OAuth page slightly and should get a working SAML sign in page.

Here’s the OAuth page code, let’s see what we need to change.

```tsx
'use client'
import { useSignIn } from '@clerk/nextjs'
import { OAuthStrategy } from '@clerk/nextjs/server'

export default function SignInOAuthButtons() {
  const { signIn } = useSignIn()
  const signInWith = (strategy: OAuthStrategy) => {
    if (signIn) {
      return signIn.authenticateWithRedirect({
        strategy,
        redirectUrl: '/sso-callback',
        redirectUrlComplete: '/',
      })
    }
  }
  // Render a button for each supported OAuth provider
  // you want to add to your app
  return (
    <div>
      <button onClick={() => signInWith('oauth_google')}>Sign in with Google</button>
    </div>
  )
}
```

1. We need to change the strategy `import { OAuthStrategy } from "@clerk/nextjs/server"` to `import { SamlStrategy } from "@clerk/types";`

2. Also the `signInWith` method:

```tsx
const signInWith = (strategy: SamlStrategy) => {
  if (signIn) {
    return signIn.authenticateWithRedirect({
      identifier: 'email_goes_here',
      strategy: strategy,
      redirectUrl: '/sso-callback',
      redirectUrlComplete: '/',
    })
  }
}
```

3. And finally the parameter in the button `onClick` handler:

```jsx
return (
  <div>
    <button onClick={() => signInWith('saml')}>Sign in with SAML</button>
  </div>
)
```

## Next Steps and Resources

In this guide you’ve learned how to implement three major methods of passwordless authentication – [magic links](/blog/magic-links), social OAuth and SAML SSO, using Clerk - an advanced user-management platform.

Have an issue within the code? Refer to the extensive [documentation](/docs?utm_source=www.google.com\&utm_medium=referral\&utm_campaign=none).

Continue by implementing [reCAPTCHA in React](/blog/implementing-recaptcha-in-react). Maybe skip [Next.js middleware for static and public files](/blog/skip-nextjs-middleware-static-and-public-files)? Or find your next project idea on Clerk’s [blog](/blog).

---

# How We Roll – Chapter 10: Roundup
URL: https://clerk.com/blog/how-we-roll-roundup.md
Date: 2023-08-11
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter provides a roundup of the topics discussed in this series.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## **Chapter 10: Roundup**

Clerk is more than just a set of authentication APIs - it’s a tool that offers a comprehensive solution for user management with **User Experience**, **Application Security**, and **Developer Experience** as the core tenets. The last nine chapters of How We Roll have been in-depth explorations into Clerk’s auth implementation. For this 10th chapter, we will round up all the topics described thus far and see how everything fits together through the lens of these three core tenets.

If this is your first time reading a post in our How We Roll series, this chapter will serve as an index to this series and the topics covered within. From here, you can choose which topics you want to learn more about and visit the chapters dedicated to those topics.

## User Experience

Let’s jump into the shoes of a user and experience Clerk from a user’s perspective.

Clerk’s embeddable components handle every authentication-related user interaction. This goes beyond sign-up, password reset, and OAuth flows – the User Profile is one of Clerk's most comprehensive authentication components. We believe that the user should have complete control over their authentication data and sessions, which is reflected in the self-service capabilities of the User Profile. Users can access and modify their profile, OAuth accounts, passwords, multi-factor steps, and active devices, all from a single `<UserProfile />` component.

[Learn how Clerk rolls the User Profile](/blog/how-we-roll-user-profile)

Every embeddable component provided by Clerk is also used on [dashboard.clerk.com](https://dashboard.clerk.com), which allows us to study and optimize every interaction in depth and offer the best possible out-of-the-box experience. However, we also acknowledge that every company and product must assert its brand identity on every user interface. This is reflected in the extensive customization options provided for Clerk’s components.

[Learn how Clerk rolls Customization](/blog/how-we-roll-customization)

Avatars are a critical component of user identity but are usually treated as an afterthought. Clerk brings a lot of care into ensuring that user avatars are high quality and are present throughout the authentication interactions to build a sense of user identity. Little details like the shimmer effect on hover and beautifully marbled default avatars elevate the user experience.

[Learn how Clerk rolls Avatars](/blog/how-we-roll-avatars)

## Application Security

While user experience is something we put a lot of effort into, the biggest priority for an auth provider like Clerk is application security.

The web has been slowly moving towards a passwordless application experience by embracing various forms of Single Sign-On. However, passwords are still the most familiar form of authentication. While over half of all signups happen through Single Sign-On providers like Google, Clerk ensures that the users and developers who prefer passwords are not under-served. Clerk’s default password requirements ensure that passwords are strong and compliant with NIST guidelines, implement countermeasures to detect breached passwords, and provide developers the flexibility to configure password rules.

![How We Roll Roundup guide illustration](./a43eae985fcc2f06e8c87db46569fa470ff85f50-2400x1260.png)

[Learn how Clerk rolls Passwords](/blog/how-we-roll-passwords)

Email verification is an essential piece of authentication. Password reset, account management, and team invitations require the presence of an email, and verification is necessary to mitigate spam or bot accounts. Clerk is configured by default to verify user emails before allowing sign-ups and employs both link-based and OTP-based verifications. For OAuth flows, if there is a question as to whether the OAuth provider verified the user’s email, Clerk requires an additional email verification step.

[Learn how Clerk rolls Email Verification](/blog/how-we-roll-email-verification)

Clerk’s implementation of Sessions enables various security capabilities. Clerk tracks the client devices used to sign in, along with the timestamps of the most recent sign-in and user activity, so users can quickly identify devices that are not supposed to be signed into their account and promptly sign out of them. Clerk also ensures that the application will never be in an invalid auth state (signed out, changed permissions, etc.) for more than 60 seconds through short-lived tokens.

![How We Roll Roundup guide illustration](./eefb254ce7a5410af709348cd9ee05250aa25cad-2400x1260.png)

[Learn how Clerk rolls Sessions](/blog/how-we-roll-sessions)

Clerk provides Multifactor Auth through SMS verification, Authenticator apps, and physical security keys for the more security-conscious folks. Users can choose to use their preferred factor for MFA within the options developers enable.

[Learn how Clerk rolls Multifactor](/blog/how-we-roll-multifactor)

## Developer Experience

Authentication should not be a hassle for developers to implement. Clerk makes it ridiculously easy for developers to get up and running with fully functioning auth.

A secure and scalable authentication infrastructure goes beyond simple client-server systems. Clerk’s robust infrastructure delivers the end-to-end authentication user experience and makes onboarding extremely easy for front-end developers. Clerk also eliminates specific configuration steps until developers are ready to release to production, ensuring absolute security across all authentication flows.

![How We Roll Roundup guide illustration](./145b05891db45736be24882aa1ac20a187db1fe6-1200x671.png)

[Learn how Clerk rolls Infrastructure](/blog/how-we-roll-infrastructure).

While Clerk’s SDKs provide easy integration with backend applications, developers might wish to leverage a Backend-as-a-Service provider like Supabase instead of building their own. Clerk also offers seamless integration with these providers with minimal effort from the developers through JWT Single Sign-On.

[Learn how Clerk rolls JWT SSO](/blog/how-we-roll-jwt-sso)

## Summary

The nine topics covered in this series are by no means an exhaustive list of how Clerk implements authentication. However, these topics demonstrate the care and effort put into making Clerk applications secure, easy to start, and a great user experience.

---

# How We Roll – Chapter 9: Infrastructure
URL: https://clerk.com/blog/how-we-roll-infrastructure.md
Date: 2023-08-04
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers the infrastructure that powers Clerk’s authentication capabilities.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 9: Infrastructure

A system that provides secure authentication with a great user experience requires a lot of complex infrastructure. This goes beyond the typical client-server systems developers are familiar with and build.

- Sensitive data needs to be protected both in transmit and at rest
- Integrations with third party services need to be configured and orchestrated
- Work needs to be delegated to background to keep the application responsive
- Failures and timeouts need to be handled and observable to developers
- Operations need to be scalable to handle millions of users

In this chapter of How We Roll, we will explore the infrastructure that is required to build and operate an authentication system, and dive into the infrastructure that allows Clerk to offer an authentication experience that can be integrated into a project within minutes.

### Self-Hosted Auth

An application that implements auth directly or through imported libraries is self hosted, which means the developers have to manage the auth infrastructure on their own.

We can imagine an application with self-hosted auth that would look like this.

![How We Roll Infrastructure setup guide](./c8dbf4c3c324efaeef80269f07617b0418861410-1200x504.png)

The client would be a web, mobile, desktop, or console application that users interact with. The server provides the ability to sign in and check passwords, and the database is where the user data is stored securely.

The ability to send emails becomes a requirement very early, and is basically table-stakes for most consumer-facing applications. SMS is usually optional but is essential to many industries, and offering flexibility to the users to choose between email and phone is great experience.

![How We Roll Infrastructure setup guide](./5fefabb160b8972d644daa3ae2b5c793a8890727-1200x508.png)

This adds the infrastructure cost of either self-hosted servers or integrating with third-parties like [Sendgrid](https://sendgrid.com) and [Twilio](https://www.twilio.com).

Along with the development cost spent into the integration, sending email and text notifications is often high latency and failure-prone. If these operations are performed in the regular request-response cycle, it results in bad user experience since requests take too long to get a response.

![How We Roll Infrastructure setup guide](./832787cf55ca55b172cbe0da0c52ff262f687fe5-1200x554.png)

Delegating these operations to asynchronously-scheduled background jobs becomes necessary to ensure the application is snappy and responds to user interactions quickly. The jobs must ensure that all notifications are delivered, retried on failure, and offer developers observability into failures and bottlenecks. This can also be delegated to a provider like [Upstash](https://upstash.com), which again adds more cost of integration.

OAuth integration requires acquiring credentials and building adapters for each OAuth provider. The latter can usually be delegated to an open-source library like [NextAuth](https://next-auth.js.org).

This is not an exhaustive list. Some use-cases require additional infrastructure like SAML integration, blockchain adapters, compliance guarantees, or bot detection.

### Clerk-Hosted Auth

Clerk is designed to allow developers to build applications that look like this.

![How We Roll Infrastructure setup guide](./c07d33983423a94d4161a25b242ad4af255d3440-1200x339.png)

Clerk offers complete authentication capabilities directly on the client. Developers are not required to host a server to enable Clerk’s auth. This also allows Clerk to offer a great user experience and developer experience through pre-built components.

That said, many applications will have their own server and database. However, all auth-related responsibilities are delegated to Clerk, and Clerk’s backend package makes integration on the server easier and supports all non-node environments.

![How We Roll Infrastructure setup guide](./bb3661a1bd785eecaba37c2323a94239af4d73d3-1200x463.png)

But since the server is optional, developers can decide to not host one at all, and leverage a Backend-as-a-Service solution like [Firebase](https://firebase.com). Clerk also offers integrations to popular BaaS providers through [JWT Single Sign-On](/blog/how-we-roll-jwt-sso).

![How We Roll Infrastructure setup guide](./e53ed34009c8057fc66521edb69b09c7846222b3-1200x350.png)

Let’s zoom into the diagram and explore the infrastructure that powers the Client-Server integrations.

![How We Roll Infrastructure setup guide](./b9f130f6f7268d2037d6ff77cd3edf63b7dfaf8b-1200x630.png)

The [Javascript library](/docs/reference/clerkjs/installation) and [pre-built components](/docs/components/overview) interact with Clerk’s [Frontend API](/docs/reference/frontend-api), which is where the user signs in. For magic-link and OAuth based flows, the API redirects the user back to the Client after a sign in. Clerk is responsible for syncing the sign-in session with the Client.

The [Backend API](/docs/reference/backend-api) powers the [backend SDKs](/docs/references/javascript/overview) that authenticate client requests and access user data stored in Clerk’s database. The backend also delivers events to the application Server through [Webhooks](/docs/integrations/webhooks/overview) to enable asynchronous and event-based systems.

![How We Roll Infrastructure setup guide](./6e05d46aeec5d06570e9db1d684142c8cda6d648-1200x630.png)

Internally Clerk is split into the Frontend and Backend API services that run on the highly scalable and efficient runtime of [Google Cloud Run](https://cloud.google.com/run). The API services share access to the data, which is managed by [Google Cloud SQL](https://cloud.google.com/sql) and stored with security and integrity guarantees. Clerk also manages background jobs to delegate any non-critical work to. The background jobs also leverage Cloud Run and are configured with various priority levels, which are determined by the urgency of the operation being delegated (e.g. sending a message is high priority, cleanup tasks are low priority). This helps Clerk provide extremely responsive APIs, which is essential to ensure the authentication UX is fast and snappy.

![How We Roll Infrastructure setup guide](./32a8c39a636852fc2ee6866f4b28720d9b0e4ab9-1200x696.png)

Clerk also takes on the cost of integrating with other external services. SMS, Emails, and Webhooks are automatically delivered through [Sendgrid](https://sendgrid.com), [Twilio](https://www.twilio.com), and [Svix](https://www.svix.com) respectively, without any additional cost of integration. OAuth provisioning is also completely eliminated, as Clerk provides shared OAuth credentials and adapters for most popular OAuth services that can be enabled with a simple switch.

Since Clerk powers the complete end-to-end authentication flow, any additional infrastructure that might be required for special use-cases, like SAML, blockchain, or security compliance, can be added by Clerk without developers having to build any extra integrations. [A recent release](/blog/changelog-2023-07-07) of Clerk included built-in bot detection, that allows developers to build AI applications that offer free credit to new users, while Clerk ensures that malicious actors cannot spam create new accounts and abuse free credits.

![How We Roll Infrastructure setup guide](./12bacf949d2dd15dcdb8cd4acb5dcb238632efe5-1200x786.png)

Bot detection capabilities are enabled by a combination of Clerk’s in-house countermeasures and [Cloudflare](https://cloudflare.com), which are made available to Client applications out of the box.

### Bring your own Infra

Thanks to this infrastructure, getting up and running with fully functional auth in an application is a matter of minutes with Clerk. However, for various reasons, developers will prefer to leverage some of their own infrastructure. Clerk can be configured to not deliver messages if the developers wish to use their existing Email or SMS servers. Clerk sends webhook events for those messages to the application server, which can then integrate with self-provisioned Email and SMS servers to deliver the message to the user.

### Bring your own Configuration

Another contributor to Clerk’s quick onboarding experience is shared DNS and OAuth configurations.

![How We Roll Infrastructure setup guide](./9e7145464a1c7845cb548d9d7e621c4962f3d8c2-1200x630.png)

In a development instance, Clerk’s Frontend API is hosted on a subdomain of `accounts.dev`. This subdomain is a randomly generated slug unique to each application, e.g. `random-dove-45.accounts.dev`. The API is fully functional cross-origin, so the client application can be running on any domain, including `localhost` and [preview deployments](/docs/deployments/vercel), and will have full access to all of Clerk’s auth. Emails are automatically sent from an address like `no-reply@accounts.dev`, and OAuth flows use shared credentials.

While the cross-origin authentication capability contributes to the extremely quick onboarding DX of Clerk, it opens up vulnerabilities to certain cross-site scripting attacks, since sensitive authentication tokens are being shared across origins. This also means that [magic links](/blog/magic-links) and emails use a Clerk-owned `accounts.dev` URL instead of the application URL, which is a non-starter for production applications. Similarly, OAuth consents are provided to a third-party (Clerk) instead of to the application directly.

In a [production instance](/docs/deployments/overview), Clerk requires developers to add some DNS settings to their production domain, which allows Clerk to host the Frontend API at a subdomain of the application’s own origin. This eliminates cross-site vulnerabilities and guarantees absolute security of the application across all authentication flows. All links and email addresses also use the application’s domain so the risk of users mistaking

Clerk also requires production instances to have their own OAuth credentials so that all OAuth consents are provided directly to the application owner.

![How We Roll Infrastructure setup guide](./2e3b8c93ba1f2a09ed6364463caf39a8b54845f4-1200x630.png)

In addition to full security, hosting the API as a subdomain of the Client origin allows any other subdomain to access the same sign-in sessions as the primary Client. Users only need to sign-in once, and the session is automatically made available Clients on any subdomain by the Frontend API.

![How We Roll Infrastructure setup guide](./fa6fe5bacac768babc9a82ae80521aaeb3c9c1b3-1200x630.png)

## Summary

Clerk is designed not only to serve all the authentication needs of modern web applications, but to provide developers with an extremely easy onboarding experience along with the flexibility to turn off certain parts and integrate with their own systems. Clerk goes above and beyond with infrastructure to ensure that no compromises are made in terms of user and developer experience.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# Password-Based Authentication in Next.js
URL: https://clerk.com/blog/password-based-authentication-nextjs.md
Date: 2023-07-26
Category: Guides
Description: This article explores password authentication, risks, and better solutions like SSO, MFA, and passwordless login.

Passwords. The best and worst thing that ever happened to internet security. We talk about passwordless and [SSO](/features/social-sso) and [magic links](/blog/magic-links) and [MFA](/features/multifactor-authentication), but [98% of the world’s websites](https://www.shrm.org/resourcesandtools/hr-topics/technology/pages/the-password-is-slowly-becoming-extinct.aspx) only accept password authentication.

It is expected that a site will allow you to enter your own username/email and a password to sign up and log in. Even though we know there are security flaws with this approach and much better ways to build authentication, your site has to have password-based authentication, otherwise it looks… odd.

So how do you do that? Here we’re going to show you. But this is a demonstration of the internal workings of password authentication to give you an understanding of how it works. This isn’t something you should truly build yourself if you care about your users. Use libraries and services built by experts if you are going to implement password authentication in your app–we show you how to do this with Clerk at the end.

## Building passwords from basics in Next.js

You need four components to build password authentication:

1. A frontend where the user can enter their username and password to sign up or log in
2. A backend to deal with the signup/login logic
3. A way to hash the password so it can be stored securely
4. A storage mechanism (and logic) to save the username and hashed password

Parts 1-3 are are possible with Next.js. For storage, we’ll be using [Supabase](https://supabase.com). Supabase is an open source Firebase alternative that provides a large selection of database management tools. It integrates with a large number of modern SaaS tools, including [Clerk](/docs/integrations/databases/supabase). Here, we won’t be using the Supabase libraries. Instead, as under the hood Supabase is a Postgres database, we’ll use the low-level methods to connect and then use SQL to load and recall our data.

Let’s start with creating a new Next.js application:

```
npx create-next-app@latest password-auth
cd password-auth
```

We’ll only be using two extra libraries in this build:

- bcrypt: Bcrypt is a password hashing function which incorporates a salt to protect against rainbow table attacks.
- pg: Pg (short for “node-postgres”) is a collection of Node.js modules for interfacing with PostgreSQL databases, offering features such as connection pooling and prepared statements.

You can install them with npm install:

```
npm install bcrypt pg
```

### Storage

We’ll actually work backwards from our list above and start with our database functionality. As we said, we’re using Supabase. Supabase is an open-source Firebase alternative that provides developers with a suite of tools and services for building serverless applications. It offers real-time databases, instant APIs, and authentication and authorization functionalities.

Here, we’re only going to use the database component, and even then we won’t use the Supabase libraries. Instead, we’ll just use the general Postgres account information to connect directly to the database.

[Sign up for Supabase](https://supabase.com) and create a new project. The project can be called anything, but take note of the password you create as you will need that for the connection. The project will take a few minutes to spool up, but once it has, go to “Database → Table → new table” to create a new table. You’ll only need two fields in this table:

- Username - Text
- Password - Text

You can keep the `id` and `created_at` fields. Make sure, for the purposes of this demo at least, you turn Row Level Security off as otherwise it’ll be much more difficult to access the database.

Row-Level Security (RLS) is a feature that allows fine-grained control over which rows in a table can be accessed and modified by users. With RLS, the database admin can define policies that restrict access to certain rows based on the attributes of the user or the data. If you are building for production, RLS is a must.

With the table created, head to Project Settings -> Database. You’ll see “Connection info.” This is what we’ll need to connect to this database:

With that information (and the password you created earlier), we can now start building the code and logic for our password authentication.

Open up your password-app directory in your favorite IDE and add a “utils” directory at the root. Within utils, create a db.js file that will handle your database connection:

```jsx
import { Pool } from 'pg'

const pool = new Pool({
  user: 'User',
  host: 'Host',
  database: 'Database name',
  password: 'Password',
  port: 5432,
})

export default pool
```

Fill in the `User`, `Host`, `Database name`, and `Password` with the “Connection info” from Supabase.

(Like with RLS, don’t do it this way if you are deploying to production–never hardcode variables like this in your code. Instead save them as environmental variables and import them via process.env).

### Hashing and salting

With our database connection sorted, we can start to create the backend logic we need to route our user requests and deal with the username and password.

There should already be a pages/api directory. Within that create a signup.js file. Add this code to the file:

```jsx
import pool from '../../utils/db'
import bcrypt from 'bcrypt'

export default async function signup(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body

    try {
      // Hash the password
      const hashedPassword = await bcrypt.hash(password, 10)

      // Store the username and hashed password in the database
      const result = await pool.query(
        'INSERT INTO users(username, password) VALUES($1, $2) RETURNING *',
        [username, hashedPassword],
      )

      // If user is created successfully, return a success message
      res.status(201).json({ status: 'Created', user: result.rows[0] })
    } catch (error) {
      res.status(500).json({ status: 'Error', message: error.message })
    }
  } else {
    res.status(405).json({ status: 'Method Not Allowed' })
  }
}
```

Here’s a breakdown of what the code does:

- `import pool from ‘../../db’` imports the `pool` object from `db.js` which is a pool of database connections we’re using to connect to Postgres
- `import bcrypt from ‘bcrypt’` imports the `bcrypt` library, which is a password-hashing function. You’ll use it to securely hash passwords before storing them in the database.
- `export default async function signup` defines an asynchronous function called `signup` which is the main function of this module.
- `if (req.method === ‘POST’)` checks if the HTTP request method is POST. If it’s not, it returns a 405 status code.
- `const { username, password } = req.body` destructures the request body to extract the username and password properties from the request body.
- `const hashedPassword = await bcrypt.hash(password, 10)` uses `bcrypt` to hash the user’s password asynchronously with a salt round of 10. The hashed password is then stored in hashedPassword.
- `const result = await pool.query(…)` sends a SQL query to the Supabase to insert a new row into the `users` table. It inserts the username and the hashed password.
- `’INSERT INTO users(username, password) VALUES($1, $2) RETURNING *’` is the SQL query being sent to the database. It’s parameterized to prevent SQL injection attacks. The `$1` and `$2` are placeholders for the `username` and `hashedPassword` that will be inserted.
- The final lines are logic for catching errors or returning success messages.

There’s a lot there, but basically when this API route is called it takes the username and password the user entered, hashes and salts the password, and then saves them in the database.

**What are hashing and salting?**

- Hashing: Hashing is the process of converting an input of any length into a fixed size string of text, using a mathematical algorithm. Hashing is often used to securely store sensitive data such as passwords. Even a small change in the input text will produce a drastic change in the output hash, making it computationally infeasible to derive the original input from the hashed output.
- Salting: Salting is a technique used in conjunction with hashing to increase the security of stored passwords. A salt is a random piece of data generated for each user that is added to the password before it is hashed. This means that even if two users have the same password, their hashed passwords will be different because the salts are different. Salting helps protect against rainbow table attacks, where an attacker pre-computes the hash values for possible passwords and looks for matches with hashed passwords.

All that happens in the line `const hashedPassword = await bcrypt.hash(password, 10)`. Doing this is fundamental to password security. Without it, passwords would be saved in plain text, and anyone having access to the database would be able to retrieve everyone’s passwords. With hashing and salting, what is saved in the database isn’t the raw password.

Now that we can sign up, let’s create a file called pages/api/login.js so we can also log in. In this file, you would fetch the user from the database, compare the hashed passwords, and if they match, return a success:

```jsx
import pool from '../../utils/db'
import bcrypt from 'bcrypt'

export default async function login(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body

    try {
      // Get the user with the provided username
      const user = await pool.query('SELECT * FROM users WHERE username = $1', [username])

      if (user.rows.length > 0) {
        const passwordMatches = await bcrypt.compare(password, user.rows[0].password)

        if (passwordMatches) {
          // Passwords match, return a success message
          res.status(200).json({ status: 'Success', message: 'Login successful' })
        } else {
          // Passwords don't match, return an error message
          res.status(403).json({ status: 'Error', message: 'Invalid password' })
        }
      } else {
        res.status(404).json({ status: 'Error', message: 'User not found' })
      }
    } catch (error) {
      res.status(500).json({ status: 'Error', message: error.message })
    }
  } else {
    res.status(405).json({ status: 'Method Not Allowed' })
  }
}
```

This is very similar to the previous route, but instead of hashing and then storing, we’re retrieving and then comparing the stored hash to a hash of the password the user tried with the login page.

We retrieve the saved password from Supabase with `const user = await pool.query(‘SELECT * FROM users WHERE username = $1’, [username])`. This grabs the user with the username just entered by the user.

We then use `const passwordMatches = await bcrypt.compare(password, user.rows[0].password)` to compare the entered password with the retrieved password. `bcrypt.compare` is a function that takes a plain-text input and a hashed string as arguments, and returns true if the plain-text input, when hashed with the same salt as the hashed string, matches the hashed string, thus verifying the authenticity of the input.

### Creating the frontend with Next.js

All we need now is some logic on the frontend for the user to interact with. First, we’ll create a Profile component in components/profile.jsx:

```jsx
import React from 'react'

const Profile = ({ user }) => {
  return (
    <div>
      <h1>Your Profile</h1>
      <p>Welcome, {user.username}!</p>
    </div>
  )
}

export default Profile
```

In this component, we simply display a welcome message with the user’s username. Then we’ll remove the current Next.js boilerplate from index.js and add this code to include a login/signup form and the profile display.

```jsx
import React, { useState } from 'react'

const AuthForm = () => {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [isLoggedIn, setIsLoggedIn] = useState(false)
  const [user, setUser] = useState(null)

  const handleSubmit = async (e, path) => {
    e.preventDefault()
    const response = await fetch(`/api/${path}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password }),
    })
    if (response.ok) {
      const user = await response.json()
      setUser(user)
      setIsLoggedIn(true)
    } else {
      console.log(`${path} failed`)
    }
  }

  return (
    <div>
      {!isLoggedIn ? (
        <form>
          <label>
            Username:
            <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
          </label>
          <label>
            Password:
            <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
          </label>
          <button type="submit" onClick={(e) => handleSubmit(e, 'signup')}>
            Signup
          </button>
          <button type="submit" onClick={(e) => handleSubmit(e, 'login')}>
            Login
          </button>
        </form>
      ) : (
        <Profile user={user} />
      )}
    </div>
  )
}

export default AuthForm
```

In this component, we have a form with fields for username and password, and two buttons for signup and login. When either button is clicked, the `handleSubmit` function is called with the corresponding path. If the request is successful, we update the `isLoggedIn` and `user` states, which will cause the `Profile` component to be rendered.

So the user sees this (very simple) form:

![Clerk Login](./1052cd293fef425f61aa9b3b11c5120e38262053-1102x72.png)

If they sign up/log in correctly, they get to their profile page:

![Clerk Profile](./c072c74366600461f737bd84de0aa4c1a7a7173c-368x186.png)

On the backend, we can see the new user in our database in Supabase, with their hashed and salted password:

![Hashed Password](./7663231353d3d9d11a1d426bb3252b18904aaa0d-1786x216.png)

### Extra security

This is a basic example and does not handle all the edge cases you might need to cover in a production app, such as:

- Checking for unique usernames or emails during signup. For instance, the hashing and database retrieval depends on unique usernames.
- Handling password resets.
- Adding email verification.

You can check whether a username exists by running an additional query on the database before you try and select the user:

```jsx
export default async function signup(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body

    try {
      // Check if a user with the same username already exists
      const userExists = await pool.query('SELECT username FROM users WHERE username = $1', [
        username,
      ])

      if (userExists.rows.length > 0) {
        res.status(409).json({ status: 'Error', message: 'Username already exists' })
        return
      }

      // Hash the password
      const hashedPassword = await bcrypt.hash(password, 10)

      // Store the username and hashed password in the database
      const result = await pool.query(
        'INSERT INTO users(username, password) VALUES($1, $2) RETURNING *',
        [username, hashedPassword],
      )

      // If user is created successfully, return a success message
      res.status(201).json({ status: 'Created', user: result.rows[0] })
    } catch (error) {
      res.status(500).json({ status: 'Error', message: error.message })
    }
  } else {
    res.status(405).json({ status: 'Method Not Allowed' })
  }
}
```

Here, you’ll get an error message if the user already exists.

Handling password resets and email verification are much more difficult. To handle password resets, you typically need to do the following:

- Generate a unique token for the password reset request.
- Associate this token with the user in your database, and set an expiry time for it.
- Send an email to the user with a link containing this token.
- When the user clicks the link, verify the token and its expiry time.
- If the token is valid, allow the user to enter a new password.

Email verification is similar, but instead of adding a new password at the end, you’ll have a verified boolean on the user that you set to true.

We also haven’t added any ability to force stronger passwords on users. You can do that through using a library such as the validator library. The validator library provides a collection of string validation and sanitization methods, simplifying data validation tasks in the server side, client side, or even for data stored in the database:

```jsx
import bcrypt from 'bcrypt'
import validator from 'validator'
import pool from '../../utils/db'

export default async function signup(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body

    // Validate the password
    if (
      !validator.isStrongPassword(password, {
        minLength: 8,
        minLowercase: 1,
        minUppercase: 1,
        minNumbers: 1,
        minSymbols: 1,
        returnScore: false,
      })
    ) {
      res.status(400).json({
        status: 'Error',
        message: 'Password does not meet complexity requirements',
      })
      return
    }

    try {
      const hashedPassword = await bcrypt.hash(password, 10)
      const result = await pool.query(
        'INSERT INTO users(username, password) VALUES($1, $2) RETURNING *',
        [username, hashedPassword],
      )

      res.status(201).json({ status: 'Created', user: result.rows[0] })
    } catch (error) {
      res.status(500).json({ status: 'Error', message: error.message })
    }
  } else {
    res.status(405).json({ status: 'Method Not Allowed' })
  }
}
```

Here we can set that the password must have eight characters and one uppercase letter, one lowercase, one number, and one symbol.

There is a lot to think about to just do the basics of password authentication. Don’t want to do all that?

## Using Clerk with Next.js for password authentication

Clerk allows you to quickly and easily add password [authentication to Next.js](/nextjs-authentication) and has both client and server side components.

Before we get to the code, we first want to set up our application to use email and password authentication in the Clerk dashboard. Head to your dashboard and select “Email, Phone, Username” under the “User & Authentication” menu. Make sure email is selected:

![Clerk User & Authentication Menu](./ffec29def468461c607f10f34705648584d1aaf0-2048x884.png)

You can see you can make this required and used for sign-in, which is what we want here. We can also, with a quick toggle rather than dozens of lines of JavaScript, say that we want email verification.

Then, in the same menu, find the “Authentication factors” options and select “Password.” Again here you can easily select to force the user to use a more secure password:

![Authentication Factors](./95cfa2ef5079df45e735309765f7515521d5e76e-2050x1068.png)

Now we can start with the code. Let’s create a new Next.js app:

```
npx create-next-app@latest clerk-auth
cd clerk-auth
```

Now we can install Clerk:

```
npm install @clerk/nextjs
```

Next, we want to create our environment variables for our Clerk API keys and [routes](/docs/nextjs/api-routes). Create a `.env.local file` in the root directory and add your keys and these routes (that we’re going to create in a moment):

```
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_****
CLERK_SECRET_KEY=sk_test_****
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/signin
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/signup
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
```

With the keys and routes in place, we can add wrapper. This provides an active session and user context to Clerk’s hooks and other components. We want to wrap the entire to enable the context to be accessible anywhere within the app, so we put it in our `_app.jsx` file:

```tsx
import { ClerkProvider } from '@clerk/nextjs'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default MyApp
```

We’re also going to add some middleware, which is the function that decides which pages are protected. Here, we’re going to protect everything. Add a middleware.js file to the root directory with this code:

```jsx
import { authMiddleware } from '@clerk/nextjs'

// This example protects all routes including api/trpc routes
// Please edit this to allow other routes to be public as needed.
// See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
export default authMiddleware({})

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

We’ll now create our sign up and sign in pages. To create the sign up page, create a file at page/signup/\[\[…index]].jsx:

```jsx
import { SignUp } from '@clerk/nextjs'

export default function Page() {
  return <SignUp />
}
```

The \[\[…index]] syntax defines a catch-all route, so anything the user entered under signup (e.g. signup/a or signup/b) will still go to this page.

We’ll do the same for signing in, with the page at page/signin/\[\[…index]].jsx:

```jsx
import { SignIn } from '@clerk/nextjs'

export default function Page() {
  return <SignIn />
}
```

Finally, all we need is a button on the client to sign up and log in:

```jsx
// pages/index.jsx
import { UserButton } from '@clerk/nextjs'

export default function Example() {
  return (
    <>
      <header>
        <UserButton afterSignOutUrl="/" />
      </header>
      <div>Your page's content can go here.</div>
    </>
  )
}
```

Now when we `npm run dev`, we’ll get the Clerk sign in modal:

![Clerk Sign In](./c98e16686ed716b048388f304bf555a6385c2bdd-884x698.png)

As we don’t have an account, we’ll switch to the sign up option:

![Create Account](./4e24a55a569a4ec42093a1acb304f92b6a38cf2b-900x850.png)

We can then enter our email address and password. We have email verification turned on, so we have to go and click the link in our email. After that we’ll be redirected to our content:

![Next.js Content](./069e85f6ed8b8df28a28dd8fe0d7669605781392-438x140.png)

## Easier passwords in Next.js

That’s it. You now have password authentication set up with the necessary security features. Clerk offers custom [password flows](/docs/authentication/custom-flows/password) so you can design the sign in flow for your app as you want

People expect passwords on their sites. They aren’t going anywhere soon. So if you are creating a new site and want password authentication, use a well-designed library or service rather than creating your own–you and your users will be grateful.

---

# Exploring the Intricacies of OTP Authentication in Next.js
URL: https://clerk.com/blog/otp-authentication-nextjs.md
Date: 2023-07-24
Category: Guides
Description: Learn how one-time passwords work, best practices for using OTPs in authentication, and how to implement OTPs in Next.js.

Passwords aren’t great. They’re often weak, reused, and need to be stored indefinitely making them susceptible to attacks and leaks. [have i been pwned?](https://haveibeenpwned.com) tells me passwords associated with just one of my email addresses have been leaked 18 times, and I have over 800 individual passwords stored in my password manager.

This is why [magic links](/blog/magic-links), [SSO](/blog/social-sso-in-next-js), and one-time passwords (OTPs) are becoming standard authentication methods. They provide better or additional security for your users.

Here we’re going to look at OTPs. One-time passwords are a great option for improving the security of your application. Let’s go through exactly what one-time passwords are, how they can improve the security of your application, the best practices for using them in authentication, and how you can [implement OTPs in Next.js](/features/email-sms-passcodes-otp).

## What is a one-time password?

OTP or One-Time Password authentication is a method in which a unique code is sent to a user's device and the user enters this code into an application to verify their identity. It’s valid for only one login session and usually time-limited.

There are two ways OTPs are implemented:

1. As the main factor for logging in a user. If you don’t want to use any username/password combinations in your authentication flow, you can send a one-time password for log in. You can also use it as an alternative to username/password. This is not particularly common, but can be seen on sites such as local social networking site Nextdoor.
2. As an additional step in [multi-factor authentication (MFA)](/docs/custom-flows/mfa#multi-factor). After a user has logged in with their username/password or another authentication provider (such as Google, Twitter, or GitHub), they are then sent an OTP to a registered email address or phone number as a secondary layer of security for applications. This second option is much more common.

You’d think that one-time passwords would be long and complicated like the suggested passwords from a password manager. But because they are transient in nature and rarely subject to dictionary or brute force attacks, they can be much simpler. OTPs can have different formats, but they usually consist of a series of alphanumeric characters or purely numeric characters. The length of OTPs can vary, but a common format is a 6 or 8-digit numeric code.

The choice between numeric and alphanumeric OTPs depends on the context and requirements. Numeric OTPs can be easier to enter, especially on mobile devices, which makes them a popular choice for SMS-based OTPs. However, alphanumeric OTPs provide a larger possible combination set for the same number of characters, which can be more secure against brute-force attacks.

There are six main steps in the OTP flow:

![Otp Authentication Nextjs tutorial illustration](./2ba42bc94e55e995017d20204bb6ee2a763a8d8a-2000x698.png)

1. **Trigger**. The user initiates the OTP process. This could be a login attempt, a transaction verification, or any other scenario where identity needs to be confirmed.
2. **OTP generation**. The server generates an OTP. This is typically a random string or number that is time-limited.
3. **OTP delivery**. The generated OTP is sent to the user through a predetermined method. This could be an SMS to their phone, an email to their registered email address, or generated in a hardware token or software app, such as Google Authenticator.
4. **User input**. The user receives the OTP and enters it into the application.
5. **OTP verification**. The server then verifies the OTP. The server also verifies that the OTP is used within the allowed time period, and hasn't been used before.
6. **Authentication**: If the OTP is verified, the server authenticates the user and allows them to proceed. If the OTP is incorrect or expired, the server rejects the request.

Though this is only six steps, there are a lot of moving parts in OTP use. You need extra frontend UI design and logic to handle the OTP inputs; you need to integrate a delivery mechanism such as SMS, email, or an authenticator app; you need extra authentication logic to handle OTP errors; and you need the OTP generation and verification logic as well.

There are several ways to generate and verify an OTP:

- **Time-Synchronized**: In this method, the OTP is generated by applying a cryptographic hash function to a shared secret and the current time, typically measured in intervals of 30 seconds. The Google Authenticator app uses this method.
- **Counter-Based**: Here, the OTP is generated by hashing a shared secret with a counter which increments with each new OTP.
- **Algorithm-Based**: In this method, a mathematical algorithm is applied to the previous password to generate the new one.

If a time-based or counter-based OTP was used, the server repeats the same OTP generation process during verification and checks if the received OTP matches the one it generated.

These algorithms aren’t easy to implement. As you are dealing with an authentication factor, there are specific designs that you must use and RFCs that you must follow, such as this one for [Time-Based OTPs](https://datatracker.ietf.org/doc/html/rfc6238)**.** This is the biggest challenge around building your own one-time password (or any authentication) system–implementation.

## The security benefits (and challenges) of OTPs

You can quickly see the complexity involved in setting up OTP authentication. But doing so is worth it as they provide two key security benefits.

### Risk mitigation

The mitigation of risk with OTPs comes from two different avenues.

The first is mitigating the risk associated with static passwords. Traditional static passwords, if stolen, provide ongoing access until they are changed. OTPs are dynamic and expire after a single use or after a short period of time, limiting the potential damage if they are intercepted or stolen. Another problem is users reusing the same password across multiple services. If one service is compromised, all accounts using the same password are at risk. OTPs eliminate this risk because they are unique for each login session.

The second is the reduction of attack vectors. Because OTPs are typically time-limited, it makes brute-force attacks infeasible. An attacker doesn't have the time to try all possible combinations before the password expires. They also help with phishing. Even if a user is tricked into entering their OTP into a phishing site, the attacker can't reuse that OTP to gain future access to the account.

Additionally, since an OTP is valid for only one login session or transaction, it cannot be reused, preventing replay attacks. In a replay attack, an attacker tries to reuse a password that was intercepted in a previous session. However, if there's a flaw in the system's design where the OTP doesn't expire immediately after use or isn't time-bound, there's a possibility for replay attacks.

### Identity security and verification

OTPs also play a significant role in enhancing identity security and verification processes. They provide an additional layer of protection beyond traditional static passwords, aiding in the confirmation of a user's identity in several ways:

- OTPs are often used as part of a two-factor authentication process. In addition to a traditional username and password (something the user knows), the user must enter an OTP (something the user has, typically their mobile device). This confirms that the user has access to a specific device (like a phone) that is associated with the account, verifying the identity of the user.
- OTPs are commonly used in financial transactions or account changes to verify the identity of the user making the transaction. For example, when conducting a bank transfer or changing account details, an OTP might be sent to the registered mobile number or email address. The user enters the OTP to confirm the transaction, verifying that they are the account holder.
- OTPs are often used in password recovery processes to verify the user's identity. The service sends an OTP to the user's registered email address or mobile number, which the user then enters to verify their identity and proceed with resetting their password.
- When a user logs in from a new device, an OTP might be sent to their registered contact information. The user must enter the OTP to verify they have access to the registered device, confirming their identity and that the new device is trusted.

By integrating OTPs into authentication and verification processes, services can add an extra level of security and significantly reduce the risk of unauthorized access or identity theft.

## Best practices for using one-time passwords

OTPs aren’t infallible, though. But the associated risks are generally around poor implementation rather than inherent to the method. To ensure OTP effectiveness and avoid some of the above potential vulnerabilities, you can follow some best practices.

### Basic security practices

These practices are the principles of any good system:

- **Secure delivery channel**. Use a secure delivery channel for sending the OTP. If you're sending the OTP via email or SMS, ensure the communication channel is secure.
- **Encrypt communication**. Ensure all communications between the client and the server are done over HTTPS to prevent any interception of the OTP.
- **Use secure storage**. When storing OTPs on the server side, consider hashing them. This ensures that even if someone gains access to your storage, they cannot obtain the actual OTPs.

### Good OTP design

These relate to how well you design your OTP algorithm and logic:

- **Use a strong OTP**. Use an OTP that is long and complex enough to resist brute-force attacks. An OTP with at least 6 digits is usually recommended.
- **Time-Bound OTP**. Make sure the OTP is valid only for a short period of time. This reduces the window an attacker has to use a stolen OTP. Usually, OTPs are valid for about 2-10 minutes.
- **Limit OTP attempts**. Implement rate limiting on your OTP endpoints to protect against brute force attacks. After a certain number of incorrect attempts, either block the user or implement a cool-down period.
- **Expiry after use.** The OTP should expire immediately after it has been used once, to prevent replay attacks.

### Good UX

These help users use your OTP and make sure they don’t turn it off:

- **Backup codes**. For applications using OTP as a second factor, provide backup codes that the user can write down and use if they lose access to their OTP delivery method (like losing their phone).
- **Fallback Options**. In case the primary delivery channel fails (e.g., SMS not being delivered), have a secondary option like email or voice call.

Also consider educating users about using OTPs. One of the main threats to OTP use are physical–if an attacker steals a user's phone then they’ll have access to the SMS or email used with OTPs. Or a fraudster can fake a user’s identity to trick a telecoms company into assigning a new SIM with the user’s phone number to them (known as [SIM swapping](https://en.wikipedia.org/wiki/SIM_swap_scam)).

While OTPs can greatly enhance security, they are not foolproof. Implementation within a broader security strategy is key.

## Setting up OTP Authentication in Next.js

Let’s walk through setting up a one time password system within Next.js. If you already have a Next.js app up and running you can add this code directly. Otherwise create a new app using:

```sh
npx create-next-app@latest
```

We’ll also need to use a few modules to help us with our OTPs, namely:

- [**Twilio**](http://twilio.com). Twilio allows us to send SMS messages programmatically. Here we’re going to use it to send the OTP to the user's phone number via SMS.
- [**bcryptjs**](https://www.npmjs.com/package/bcryptjs). bcryptjs is a JavaScript library for hashing and comparing passwords. We’re going to use it to hash the OTP before storing it in the database for added security.
- [**MongoDB**](https://www.mongodb.com). MongoDB is a NoSQL database that we’ll use to store hashed OTPs along with the associated phone numbers and expiry times.
- [**Upstash**](http://upstash.com). Upstash is a serverless data platform. We’re going to use it’s rate limiter and Redis functionality.

Install these with:

```sh
npm install twilio bcryptjs mongodb @upstash/ratelimit @upstash/redis
```

For Twilio and MongoDB, you’ll also need to sign up for accounts and then need your `TWILIO_ACCOUNT_SID`, your `TWILIO_AUTH_TOKEN`, and your `MONGODB_URI`. For Twilio, you’ll also need to buy a `TWILIO_PHONE_NUMBER` that will be used to send your SMS messages.

You’ll also need an account with Upstash, and then your `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` variables.

With that done, we’ll first create the API route that will generate our OTP:

```javascript
// pages/api/generateOTP.js
import crypto from 'crypto'
import twilio from 'twilio'
import bcrypt from 'bcryptjs'
import { MongoClient } from 'mongodb'

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).end() // Method Not Allowed
  }

  // Generate a six digit number using the crypto module
  const otp = crypto.randomInt(100000, 999999)

  // Hash the OTP
  const hashedOtp = await bcrypt.hash(otp.toString(), 10)

  // Initialize the Twilio client
  const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN)

  try {
    // Send the OTP via SMS
    await client.messages.create({
      body: `Your OTP is: ${otp}`,
      from: process.env.TWILIO_PHONE_NUMBER, // your Twilio number
      to: req.body.phone, // your user's phone number
    })

    // Store the hashed OTP in the database along with the phone number and expiry time
    const mongoClient = new MongoClient(process.env.MONGODB_URI)
    await mongoClient.connect()
    const otps = mongoClient.db().collection('otps')
    await otps.insertOne({
      phone: req.body.phone,
      otp: hashedOtp,
      expiry: Date.now() + 10 * 60 * 1000, // OTP expires after 10 minutes
    })
    await mongoClient.close()

    // Respond with a success status
    res.status(200).json({ success: true })
  } catch (err) {
    console.error(err)
    res.status(500).json({ error: 'Could not send OTP' })
  }
}
```

With this code we initially import all our dependencies, then create a handler function for our POST endpoint. The body of the POST request will contain the phone number of the user that we’ll get from the frontend. Within the endpoint, we’re doing a few things:

- Creating a six-digit random OTP
- Hashing that OTP with `bcryptjs` for storage
- Creating a Twilio client and then sending the OTP to the user’s phone number
- Creating a MongoDB client and storing the OTP along with the user’s phone number and an expiry time for the password.

Is this best practice? Absolutely not. We are doing a few things right, such as setting an expiry time on the OTP and hashing them. But our OTP generating ‘algorithm’ is laughably simple.

Let’s quickly create a frontend for this now:

```jsx
import { useState } from 'react'

const OTPGenerator = () => {
  const [phone, setPhone] = useState('')
  const [otp, setOTP] = useState('')
  const [isLoading, setIsLoading] = useState(false)
  const [message, setMessage] = useState('')
  const [otpSent, setOtpSent] = useState(false)

  const handleSendOTP = async (event) => {
    event.preventDefault()
    setIsLoading(true)
    setMessage('') // reset message

    try {
      const response = await fetch('/api/generateOTP', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone }),
      })

      if (response.ok) {
        setMessage('OTP has been sent to your phone.')
        setOtpSent(true)
      } else {
        const data = await response.json()
        setMessage(data.error)
      }
    } catch (error) {
      setMessage('An error occurred. Please try again.')
      console.error(error)
    } finally {
      setIsLoading(false)
    }
  }

  const handleVerifyOTP = async (event) => {
    event.preventDefault()
    setIsLoading(true)
    setMessage('') // reset message

    try {
      const response = await fetch('/api/verifyOTP', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone, otp }),
      })

      if (response.ok) {
        setMessage('OTP verification successful!')
        setOtpSent(false)
        setPhone('')
        setOTP('')
      } else {
        const data = await response.json()
        setMessage(data.error)
      }
    } catch (error) {
      setMessage(error)
      console.error(error)
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div>
      {!otpSent ? (
        <form onSubmit={handleSendOTP}>
          <label>
            Phone Number:
            <input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} required />
          </label>
          <button type="submit" disabled={isLoading}>
            {isLoading ? 'Sending...' : 'Send OTP'}
          </button>
        </form>
      ) : (
        <form onSubmit={handleVerifyOTP}>
          <label>
            Enter OTP:
            <input type="text" value={otp} onChange={(e) => setOTP(e.target.value)} required />
          </label>
          <button type="submit" disabled={isLoading}>
            {isLoading ? 'Verifying...' : 'Verify OTP'}
          </button>
        </form>
      )}
      {message && <p>{message}</p>}
    </div>
  )
}

export default OTPGenerator
```

All this code will just show a single form on the page. On first load this form will ask for the user’s phone number.

![Otp Authentication Nextjs tutorial illustration](./1633315883a8a1ff7d1274d4cb1f3174f28be50d-2000x206.png)

When the user enters their phone number and hits submit, the above generateOTP endpoint will be called. This will send the OTP to the user’s phone number:

![Otp Authentication Nextjs tutorial illustration](./4f315b984011eab37c084d6add0805fb54c63bf5-2000x346.jpg)

Hitting submit will also change the form to accept the OTP as the input. The user can then check their phone and enter the six-digit code: and hit submit again to send the OTP and phone number to a verifyOTP endpoint for verification:

```javascript
// pages/api/verifyOTP.js
import bcrypt from 'bcryptjs'
import { MongoClient } from 'mongodb'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const rateLimiter = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(2, '3 s'),
})

export default async function handler(req, res) {
  const user_ip = req.headers['x-forwarded-for']
  const { success } = await rateLimiter.limit(user_ip)

  if (!success) {
    return res.status(429).json({ error: 'Too Many Requests' })
  }

  if (req.method !== 'POST') {
    return res.status(405).end() // Method Not Allowed
  }

  const mongoClient = new MongoClient(process.env.MONGODB_URI)
  await mongoClient.connect()
  const otps = mongoClient.db().collection('otps')

  try {
    // Fetch the OTP record from the database
    const otpRecord = await otps.findOne({ phone: req.body.phone })

    if (!otpRecord) {
      return res.status(400).json({ error: 'Invalid phone number or OTP' })
    }

    // Check if the OTP has expired
    if (Date.now() > otpRecord.expiry) {
      return res.status(400).json({ error: 'OTP has expired' })
    }

    // Check if the OTPs match
    const otpMatch = await bcrypt.compare(req.body.otp.toString(), otpRecord.otp)
    if (!otpMatch) {
      return res.status(400).json({ error: 'Invalid phone number or OTP' })
    }

    // OTP is valid and has not expired, so we can delete it now
    await otps.deleteOne({ phone: req.body.phone })

    // Respond with a success status
    res.status(200).json({ success: true })
  } catch (err) {
    console.error(err)
    res.status(500).json({ error: 'Could not verify OTP' })
  } finally {
    await mongoClient.close()
  }
}
```

When called, this API loads the OTP MongoDB database and finds the one associated with the phone number. It checks whether it has expired, and if not, matches the hashed OTPs. If it's valid, the OTP gets deleted from the database and a 200 code is returned to the frontend.

![Otp Authentication Nextjs tutorial illustration](./fa2cf32dae69b1db97696e978df995bce600e361-2000x374.png)

We could then work that response into any other authentication flow we had set up. We also have a basic rate limiter set into that will make sure a user can’t input more than 2 codes in 3 seconds, to try and prevent brute force attacks:

![Otp Authentication Nextjs tutorial illustration](./326e38b1b19e83387e3cfaac1381657ba7c43bda-2000x438.png)

And that’s it. You have one-time passwords working in Next.js.

## OTPs are intricate but worth implementing

This code gives you a one-time password option for [Next.js authentication](/nextjs-authentication). But we’ve only scratched the surface. We haven’t implemented this within any other authentication flow–this just generates, sends, and verifies the OTP, it doesn’t use it to authenticate the user.

We’ve also generated the OTP in the most basic manner, not following the RFCs and guidelines. We do have some nice best practices–rate-limiting, time-bounding, and expiry–but again these are all basic implementations. We also had to buy a new phone number!

This is the intricacy of OTPs. They are easy to set up, but difficult to get right. Like most authentication methods, it is better to use a provider than trying to create your own. Check out Clerk’s [OTP solution here](/docs/custom-flows/email-sms-otp#email-sms-otp) to have these intricacies taken care of for you.

---

# Build a Cookie Clicker App with Clerk and Hasura
URL: https://clerk.com/blog/build-a-cookie-clicker-app-with-clerk-and-hasura.md
Date: 2023-07-23
Category: Guides
Description: In this tutorial we will use Clerk with Hasura to build a full-stack Next.js app with a database and GraphQL API, all without having to write any backend code.

[Hasura](https://hasura.io) provides a GraphQL engine that can help you build real-time APIs and ship modern apps faster. Although Hasura itself does not handle authentication, you can bring your own auth server and integrate it with Hasura via JWTs. This is where [Clerk](/) fits in.

In this tutorial we will use Clerk with Hasura to build a full-stack Next.js app with a database and GraphQL API, all without having to write any backend code.

> If you would like to skip ahead and see the completed codebase, browse to the repo [here](https://github.com/clerkinc/more-cookies-please).

## Assumptions

This tutorial makes the following assumptions:

- Basic command line usage
- Node.js installed and `npm` (or `yarn` if you prefer) for package management
- Experience with React components and hooks
- Familiarity with the Next.js application structure (created with `create-next-app`)
- Comfortable running Docker Compose commands (alternative path is to use Hasura Cloud)
- [Clerk](https://dashboard.clerk.com/sign-in) and [Hasura](https://cloud.hasura.io/login) accounts already set up (if you haven’t done so, do it now\... we’ll wait)

## Set up Clerk project

We’re going to start off with creating a new project from the Clerk dashboard. I’m going to name this application “More Cookies Please” (you’ll see why shortly) and leave the default options selected for authentication strategy and turn on social login with Google.

![Build A Cookie Clicker App With Clerk And Hasura guide illustration](./deb49deb5a617c0b5dc625a4c03525a1c4110354-1176x1514.png)

The next thing we need to do is navigate to JWT Templates from the left menu and create a template based on Hasura.

![Hasura template](./c32bdcf8bc58e5c2e8787e5f5cfbc32d7fd2b352-1636x1000.png)

The next thing we need to do is navigate to JWT Templates from the left menu and create a template based on Hasura.

![Hasura token claims](./460f05efd18bcbfa6120d6c2296969b088c000e0-2072x1360.png)

Click the “Apply changes” button and navigate back to the Home dashboard. Here we’re going to copy the Frontend API key.

![Frontend API key](./848ed7537b737ab642164c7df56400766316b770-2050x506.png)

That’s all we need to do in the Clerk dashboard.

## Clone the starter repo

Now it’s time to clone the [clerk-hasura-starter](https://github.com/clerkinc/clerk-hasura-starter) repo from GitHub. Name this project directory `more-cookies-please` to match the Clerk instance name.

```bash
git clone https://github.com/clerkinc/clerk-hasura-starter.git more-cookies-please
```

Once you have the repository downloaded to your computer, run the following commands to change into the directory, install the package dependencies, and copy the `.env.local.sample` so we can set a couple environment variables:

```bash
cd more-cookies-please/
npm install
cp .env.local.sample .env.local
```

Add in the Frontend API key from Clerk that was copied earlier. We’re also going to set the GraphQL endpoint even though it hasn’t been created yet.

```bash
NEXT_PUBLIC_CLERK_FRONTEND_API=<YOUR_FRONTEND_API>
NEXT_PUBLIC_HASURA_GRAPHQL_API=http://localhost:8080/v1/graphql
```

**Note**: `CLERK_API_KEY` is available in the sample but isn’t needed for this tutorial.

Once those environment variables are set, start up the application with `npm run dev`

Open your web browser to [http://localhost:3000](http://localhost:3000) and you should see the starter application homepage, which prompts you to sign up. So now let’s sign up for an account.

Click the Sign up button and you will be redirected to a Sign Up form generated for your application with Clerk components. I’m going to choose **Sign up with Google** since it's the fastest (and doesn’t require a new password).

Once you’ve signed up and logged in, you should see the following screen:

![App welcome](./2586548dde72a7d28cd9ea95d1a9d5b9ffedd8ae-2276x1666.png)

Now it’s time to customize this application. We’re going to install a little package I created based on the excellent [react-rewards](https://github.com/thedevelobear/react-rewards) library and inspired by [Cookie Clicker](https://en.wikipedia.org/wiki/Cookie_Clicker) (thank [@bsinthewild](https://twitter.com/bsinthewild) for reminding me of this).

```bash
npm install react-cookie-clicker
```

**Note**: ⚠️ You will see some warnings about conflicting peer dependencies due to a mismatch of React versions, but it’s safe to proceed.

We’re going to create a new file called `MoreCookies.js` inside of the `components/` folder.

Because this library does not support server-side rendering (SSR), we need to make use of the [dynamic imports from Next.js](https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr) or we’ll get some nasty errors.

```jsx
import dynamic from 'next/dynamic'
const CookieClicker = dynamic(() => import('react-cookie-clicker'), {
  ssr: false,
})

const MoreCookies = () => {
  return (
    <div style={{ marginTop: 150 }}>
      <CookieClicker />
      <h2>Click the cookie</h2>
    </div>
  )
}

export default MoreCookies
```

Now let’s add the component to `pages/index.js`:

```jsx
import styles from '../styles/Home.module.css'
import Link from 'next/link'
import { SignedIn, SignedOut } from '@clerk/nextjs'
import MoreCookies from '../components/MoreCookies'

const SignupLink = () => (
  <Link href="/sign-up">
    <a className={styles.cardContent}>
      <img src="/icons/user-plus.svg" alt="Clone the starter repo illustration" />
      <div>
        <h3>Sign up for an account</h3>
        <p>Sign up and sign in to explore all the features provided by Clerk out-of-the-box</p>
      </div>
      <div className={styles.arrow}>
        <img src="/icons/arrow-right.svg" alt="Clone the starter repo illustration" />
      </div>
    </a>
  </Link>
)

const Main = () => (
  <main className={styles.main}>
    <SignedOut>
      <h1 className={styles.title}>Welcome to your new app</h1>
      <p className={styles.description}>Sign up for an account to get started</p>
      <div className={styles.cards}>
        <div className={styles.card}>
          <SignupLink />
        </div>
      </div>
    </SignedOut>
    <SignedIn>
      <MoreCookies />
    </SignedIn>
  </main>
)
```

The `ClerkFeatures` component can be removed, but the `Footer` and `Home` components should remain untouched.

You can see here we are making use of the `SignedIn` and `SignedOut` components from Clerk. We can also finally see the big cookie button!

Go ahead and click it for a nice reward. You’ve earned it. 🍪

![Cookie button](./a99cf1d2ef740351be1972ded05a878652b92a3a-1686x1206.png)

## Set up Hasura GraphQL engine

Clicking the cookie is a lot of fun, but what is even more fun? Keeping count of all those clicks!

This is where the Hasura GraphQL integration comes in.

There are two different ways we can connect to Hasura: we can use Hasura Cloud or Hasura Core running from a Docker container. We’re going to do the latter for this tutorial, but you can see instructions for connecting with Hasura Cloud in our [integration documentation](/docs/integrations/databases/hasura).

The starter repo already contains the `docker-compose.yml` file we need.

The only part we need to update here is the JWT secret. Uncomment the line for `HASURA_GRAPHQL_JWT_SECRET` and add in the value for your Clerk Frontend API. The JWT URL path points to the JSON Web Key Set (JWKS) endpoint we have set up for your application.

```yaml
HASURA_GRAPHQL_JWT_SECRET: '{"jwk_url":"https://<YOUR_FRONTEND_API>/.well-known/jwks.json"}'
```

**Note**: Make sure the **https\://** protocol is included in the URL in front of the Frontend API.

Once the JWT secret is set, run the following command:

```bash
docker compose up -d
```

This will spin up Docker services for GraphQL engine as well as a Postgres database.

You can confirm that the services are running correctly with the following:

```bash
docker compose ps
```

If all is good, you should see output similar to:

```bash
COMMAND                  SERVICE           STATUS        PORTS
"graphql-engine serve"   graphql-engine    running       0.0.0.0:8080->8080/tcp
"docker-entrypoint.s…"   postgres          running       5432/tcp
```

Head to [http://localhost:8080/console](http://localhost:8080/console) to open the Hasura console.

![Hasura GraphiQL console](./24485ecee44a1614483815c81820affa357376d0-2544x1670.png)

Hasura has already done the work of setting up a GraphQL endpoint for us and also provided the GraphiQL integrated development environment (IDE) to explore the API.

It’s time to set up the database to keep the score count.

Navigate to the **Data** page and fill out the form to Connect Existing Database. (Remember we have the Postgres one running from Docker Compose?)

![Connect database](./c8140b09ad7a9a761bb9633a079b5c8c85f260cd-1668x1430.png)

Name the database `default` (or something more clever) and input the database URL copied from the `docker-compose.yml` file. Click connect and the data source will be added.

The next step is to create the database table named `scoreboard` and add two fields:

1. `user_id` is a Text field that will contain the user ID from Clerk
2. `count` is an Integer field that will keep track of the click count

![Add new table](./a8236a9c8daf4eff6717e6055e3fd52feec692df-2244x1324.png)

Set the `user_id` as the **Primary Key** for the table. Then click the Add Table button.

The next thing we need to do is set permissions for the “user” role. Click on the **Permissions** tab and enter `user` as a new role. Then we need to set the basic **CRUD** (Create, Read, Update, Delete) operations on the table.

### Insert (Create)

For **Insert** permissions, set the following values:

- **Row insert permissions**: Without any checks
- **Column insert permissions**: `count` checked
- **Column presets**: `user_id` from session variable `X-Hasura-User-Id`

![Insert permissions](./6f63cdd947fd37c6bd707ac93579f28b074f031d-1966x1300.png)

With the Clerk integration, the user ID will be set as the session variable and Hasura will then set that as the `user_id` column when the request is made.

### Select (Read)

For **Select** permissions, set the following values:

- **Row select permissions**: With custom check `{"user_id":{"_eq":"X-Hasura-User-Id"}}`
- **Column select permissions**: `count` and `user_id` checked

![Select permissions](./0cfbbb9668e9a9d95c05b6c457301f30136977d2-1966x1412.png)

The custom check ensures only the current authenticated user can read their own count. If the user ID from the session variable matches the one from the table, the user is granted read permission to every column in their database row.

### Update

For Update permissions, set the following values:

- **Pre-update check**: With same check as select `{"user_id":{"_eq":"X-Hasura-User-Id"}}`
- **Column update permissions**: `count` checked

![Update permissions](./23862bf8f3bbffaef05ad5d6f9d239f2a2efd5fc-1958x1464.png)

Having the same custom check prevents another authenticated user from updating someone else’s count.

### Delete

We can skip over **Delete** permissions since we aren’t implementing a mechanism to delete user records from the scoreboard.

Your final permissions access chart should look like the following:

![Permissions chart](./b4cfa610c9a9316934c6ea992f1e6c9973009aad-1744x920.png)

## Configure the client

Now it’s time to make authenticated requests from the codebase to Hasura.

If you take a look at `hooks/index.js`, you can see the `useQuery` hook that has been set up. It makes use of [graphql-request](https://github.com/prisma-labs/graphql-request), a minimal GraphQL client, with the [useSWR](https://swr.vercel.app) hook to perform query requests to the GraphQL endpoint.

```jsx
import { request } from 'graphql-request'
import { useAuth } from '@clerk/nextjs'
import useSWR from 'swr'

export const useQuery = (query, variables, blockRequest) => {
  if (!query) {
    throw Error('No query provided to `useQuery`')
  }
  const { getToken } = useAuth()
  const endpoint = process.env.NEXT_PUBLIC_HASURA_GRAPHQL_API
  const fetcher = async () =>
    request(endpoint, query, variables, {
      authorization: `Bearer ${await getToken({ template: 'hasura' })}`,
    })

  return useSWR(query, blockRequest ? () => {} : fetcher)
}
```

What we’re doing is reading the custom JWT (from the template we named `hasura`) from the session object provided by Clerk. Note that the call to `getToken` is asynchronous and returns a Promise that needs to be resolved before accessing the value.

We pass the custom fetcher function, which accepts a GraphQL query and optional variables, to `useSWR`. The `blockRequest` parameter is something we’ll make use of later to prevent certain calls from happening.

Let’s try this out to make sure we can get some data. Open up `components/MoreCookies.js` and import the `useQuery` hook and log the `data` to the console:

```jsx
import dynamic from 'next/dynamic'
import { useQuery } from '../hooks'
const CookieClicker = dynamic(() => import('react-cookie-clicker'), {
  ssr: false,
})

const MoreCookies = () => {
  const { data } = useQuery(`query { scoreboard { count } }`)
  console.log('data >>', data)

  return (
    <div style={{ marginTop: 150 }}>
      <CookieClicker />
      <h2>Click the cookie</h2>
    </div>
  )
}

export default MoreCookies
```

If all went well, you should see the following:

```jsx
data >> undefined
data >> { scoreboard: Array(0) }
```

The data is `undefined` at first but then gets populated. `scoreboard` is an empty Array because we haven’t recorded any click counts yet. So let’s do that now.

In order to make the GraphQL mutation we’re going to create another custom hook in `hooks/index.js`:

```jsx
export const useCountMutation = (count, data) => {
  const prevCount = data?.scoreboard[0]?.count ?? 0
  const blockRequest = count < 1 || prevCount === count

  // Block mutation if count is less than 1 or equal to previous value
  return useQuery(
    `mutation {
      insert_scoreboard_one(
        object: { count: ${count} },
        on_conflict: { constraint: scoreboard_pkey, update_columns: count }) {
        count
        user_id
      }
  }`,
    null,
    blockRequest,
  )
}
```

This hook accepts the `count` as the first parameter and the `data` object containing previous data as the second parameter. It makes use of the `useQuery` hook to apply the `insert_scoreboard_one` insert mutation as an [upsert](https://hasura.io/docs/2.0/mutations/postgres/upsert). Instead of adding multiple rows to the database table, the `on_conflict` argument sets a constraint on the primary key (`user_id`) and if it already exists, only the `count` column will be updated. Both `count` and `user_id` values are returned from a successful mutation.

If we replace the `useQuery` with `useCountMutation` in the `MoreCookies` component, we can give us some credit for those clicks we’ve already made. (I set mine at `10` but you can be more or less generous.)

```jsx
import dynamic from 'next/dynamic'
import { useCountMutation } from '../hooks'
const CookieClicker = dynamic(() => import('react-cookie-clicker'), {
  ssr: false,
})

const MoreCookies = () => {
  const { data } = useCountMutation(10)
  console.log('data >>', data)

  return (
    <div style={{ marginTop: 150 }}>
      <CookieClicker />
      <h2>Click the cookie</h2>
    </div>
  )
}

export default MoreCookies
```

If you look at the browser console, you should now see something like:

```
data >> { insert_scoreboard_one: { count: 10, user_id: 'user_29IqLFGiidcpkwqplE1F8C8EnD1' } }
```

You can confirm that this made it into the Postgres database by going to the **Data** tab in the Hasura Console and clicking into **scoreboard** and **Browse Rows**:

![Browse rows](./31968443a55bf5e2b2d0bced0969311a221cc0d9-1940x924.png)

Success! The count (fake or not) has made it into the database and is associated with the authenticated user.

So now that everything is working as intended, we can go ahead and connect it all together. We’ll add one more custom hook that performs both the initial `useQuery` and the `useCountMutation`. This is going to need both `useState` and `useEffect` from React so make sure you import those.

```jsx
export const useScoreboard = () => {
  const [count, setCount] = useState(0)
  const { data } = useQuery(`query {
    scoreboard { count, user_id }
  }`)
  const increment = () => {
    setCount(count + 1)
  }

  // Perform mutation on count
  useCountMutation(count, data)

  useEffect(() => {
    if (!count && data?.scoreboard[0]) {
      // Set initial count from database
      setCount(data.scoreboard[0].count)
    }
  }, [count, data])

  return [count, increment]
}
```

It returns the current `count` as well as an `increment` function, both of which we can pass as props to the `<CookieClicker />` component.

```jsx
import dynamic from 'next/dynamic'
import { useScoreboard } from '../hooks'
const CookieClicker = dynamic(() => import('react-cookie-clicker'), {
  ssr: false,
})

const MoreCookies = () => {
  const [count, increment] = useScoreboard()

  return (
    <div style={{ marginTop: 150 }}>
      <CookieClicker count={count} onClick={increment} />
      <h2>Click the cookie</h2>
      <p>Current count: {count}</p>
    </div>
  )
}

export default MoreCookies
```

By setting the `count` and `onClick` props on `CookieClicker`, it will now reward you with cookies based on the number of times the button is clicked. Keep clicking for more cookies!

![Build A Cookie Clicker App With Clerk And Hasura guide illustration](./276b94611d06d56484ba13d0b5cdb9b803964b0a-1526x1265.jpg)

## Closing thoughts

Hope you had fun building this. You now have a complete Cookie Clicker app built using Clerk, Hasura, and Next.js — with no backend code required! To take it even further, you could implement an actual scoreboard that keeps track of all the cookie clicks from multiple users. Then [deploy this app to production](/docs/deployments/overview), share it with your friends, and see how much idle time they have on their hands. 😆

If you enjoyed this tutorial or have any questions, feel free to reach out to me ([@devchampian](https://x.com/devchampian)) on X, follow [@clerk](https://x.com/clerk), or join our [Discord community](https://clerk.com/discord) to connect with other developers. Happy coding!

---

# RedwoodJS Blog Tutorial with Clerk
URL: https://clerk.com/blog/redwoodjs-blog-tutorial-with-clerk.md
Date: 2023-07-23
Category: Guides
Description: Branching off from the excellent (and mighty) Redwood tutorial, the guide will lead you through setting up Clerk as the authentication provider.

> \[!WARNING]
> As of April 4, 2025, RedwoodJS is no longer actively developed. [Learn more](https://community.redwoodjs.com/t/the-future-of-redwood-launches-today/7938).

Branching off from the excellent (and mighty) Redwood tutorial, the guide will lead you through setting up Clerk as the authentication provider. You can think of Clerk as the ranger of the forest, only allowing verified users pass through the secluded parts of your app. (Be prepared for tree puns ahead — you’ve been warned! 🌲)

## Assumptions

This tutorial makes the following assumptions:

- Basic command line usage
- Node.js installed with both `npm` and `yarn` (required by Redwood)
- Experience with React components and hooks
- Have gone through the first part of the [Redwood tutorial](https://learn.redwoodjs.com/docs/tutorial/welcome-to-redwood)
- [Clerk](https://dashboard.clerk.com/sign-in) account already set up (if you haven’t done so, do it now\... we’ll wait)

> If you would like to skip ahead and see the completed codebase, browse to the repo [here](https://github.com/clerkinc/redwood-tutorial-with-clerk).

## Getting started

This journey begins with cloning the [example repo](https://github.com/redwoodjs/redwood-tutorial) that Redwood has set up.

```bash
git clone https://github.com/redwoodjs/redwood-tutorial.git redwood-tutorial-with-clerk
```

Follow their instructions to run the following commands:

```bash
cd redwood-tutorial-with-clerk/
yarn install
yarn rw prisma migrate dev
yarn rw dev
```

**Note**: If the `rw` (short for `redwood`) command isn’t working, make sure you have the proper versions of [Node.js and Yarn](https://learn.redwoodjs.com/docs/tutorial/prerequisites#nodejs-and-yarn-versions) installed.

If all went well, you should be looking at the most beautiful blog you’ve ever seen...

![Redwood blog](./2733571d4e74adab3f76818c54be7968d468dd9c-2348x1372.png)

If you try to navigate to [http://localhost:8910/admin/posts](http://localhost:8910/admin/posts) you will be redirected back to the homepage. This is due to the `<Private unauthenticated="home">` wrapper around the admin routes that was already set up.

## Set up Clerk authentication

As part of the [Authentication chapter](https://learn-redwood.netlify.app/docs/tutorial/authentication) of the Redwood tutorial, they went through the setup of their built-in database-backed authentication system called dbAuth. (Can’t blame them for planting those seeds.)

To transplant Clerk as the new authentication provider, first, navigate to your [Clerk dashboard](https://dashboard.clerk.com) and create a new application. Give your application any name you’d like, leave the default authentication strategy selected, and then choose a social login provider if you would like. (Google OAuth is pretty fast and doesn’t make you create a new password.)

![Redwoodjs Blog Tutorial With Clerk guide illustration](./079ce27b85ebf6511a2a2f55d090c87dcf4cafd0-1174x1352.png)

Once the application has been created and the confetti clears, scroll down to the **Connect your application** section to grab your API keys.

Back in the codebase, create a new `.env` file in the project directory and set the following environment variables to the respective values from your Clerk dashboard:

```bash
CLERK_API_KEY=<YOUR_BACKEND_API_KEY>
CLERK_FRONTEND_API_URL=<YOUR_FRONTEND_API_KEY>
```

Once you have the environment variables set, the next step is to root out the existing auth logic. The quickest way to do this is to run:

```bash
yarn rw setup auth clerk --force
```

Note: If you’re into the more labor-intensive, manual way of doing things, [here are the instructions](https://redwoodjs.com/docs/authentication#clerk) for you. (Chainsaws not required.)

You should see terminal output similar to the following:

```bash
✔ Overwrite existing /api/src/lib/auth.[jt]s? … yes
  ✔ Generating auth lib...
    ✔ Successfully wrote file `./api/src/lib/auth.js`
  ✔ Adding auth config to web...
  ✔ Adding auth config to GraphQL API...
  ✔ Adding required web packages...
  ✔ Adding required api packages...
  ✔ Installing packages...
  ✔ One more thing...

You will need to add two environment variables with your Clerk URL and API key.
Check out web/src/App.{js,tsx} for the variables you need to add.
See also: https://redwoodjs.com/docs/authentication#clerk
```

If you already followed the instructions to add your environment variables, great job! If you didn’t, please add them now.

In your code editor of choice, open up `web/src/App.js`

Wrap the Redwood `<AuthProvider />` component with `<ClerkAuthProvider />` and replace the prop `type="dbAuth"` with `type="clerk"`:

```jsx
const App = () => (
  <FatalErrorBoundary page={FatalErrorPage}>
    <RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
      <ClerkAuthProvider>
        <AuthProvider type="clerk">
          <RedwoodApolloProvider>
            <Routes />
          </RedwoodApolloProvider>
        </AuthProvider>
      </ClerkAuthProvider>
    </RedwoodProvider>
  </FatalErrorBoundary>
)
```

## Add Clerk components

Now that Clerk is set up, restart the dev server with `yarn rw dev`. If you had the dev server running, it needs to be restarted to read the newly added environment variables.

Open the `web/src/layouts/BlogLayout/BlogLayout.js` component in your code editor and add the following imports:

```jsx
import { SignInButton, UserButton } from '@clerk/clerk-react'
```

While the `login` and `logOut` methods from the Redwood `useAuth()` hook will work, Clerk provides nice UI components to accomplish the same thing. Remove the `logIn` and `logOut` methods and replace the last navigation list item with the following code:

```jsx
<li className={isAuthenticated ? 'ml-2' : null}>
  {isAuthenticated ? (
    <UserButton afterSignOutAll={window.location.href} />
  ) : (
    <SignInButton mode="modal">
      <button className="rounded px-4 py-2 transition duration-100 hover:bg-blue-600">
        Log in
      </button>
    </SignInButton>
  )}
</li>
```

**Note**: The `afterSignOutAll` prop needs to be set to the current URL when using Clerk Hosted Pages to redirect back to your app.

Making use of the `isAuthenticated` property checks if there is an active user session. If there isn’t one, the Clerk `<SignInButton />` component renders a custom button element matching the blog styles. Clicking “Log in”, opens a modal window allowing you to sign in with Google or sign up with an email and password.

![Clerk sign in modal](./1a05816face419ca2799bb6268f42d82c5ea0824-2348x1366.png)

After signing in, you should see the `<UserProfile />` component with an avatar.

If you look in the code right underneath the navigation, there is a conditionally rendered text block for `currentUser.email`. This doesn’t render anything because the Clerk user object is structured a little differently. To display the current user’s email address, make the following tweaks:

```jsx
{
  isAuthenticated && (
    <div className="text-right text-xs text-blue-300">
      {currentUser?.emailAddresses[0]?.emailAddress}
    </div>
  )
}
```

This reads the primary email address property and removes the absolute positioning styles.

The navigation header should now look similar to:

![Navigation header](./c013386659d283218c0ea71a36cc353597168e6b-1778x166.png)

(Displaying your own account of course.)

Clerk makes it super easy to add in these authentication components. There are more options for customization available as well.

Now that you have an authenticated user, you should be able to safely navigate to [http://localhost:8910/admin/posts](http://localhost:8910/admin/posts) and manage the blog posts.

That’s really all there is to it.

## Going deeper

If you’d like to go deeper into the forest (really stretching this metaphor here), you could try your hand at the following:

- Update the Login and Sign Up pages with [mounted Clerk components](/docs/components/authentication/sign-in)
- Add `roles: ['admin']` to the user public metadata to [implement role-based access control](/docs/guides/basic-rbac)
- Go outside and visit the [Redwood National and State Parks](https://www.nps.gov/redw/index.htm) to see these majestic trees in person
- Connect with other developers or say hi in our [Discord community](https://clerk.com/discord) 👋

---

# How We Roll – Chapter 8: Sessions
URL: https://clerk.com/blog/how-we-roll-sessions.md
Date: 2023-07-21
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers how Sessions unlock security and performance capabilities for Clerk.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 8: Sessions

One of the primary objectives of Clerk is to ensure application security while providing the developer with ways to adjust it. Clerk’s auth is configured for a smooth user experience by default, but developers can enable additional layers of security like [multi-factor auth](/docs/authentication/custom-flows/multifactor) depending on the needs of the business.

Sessions is another tool in the authentication toolkit that allows fine-tuning of application security and user experience. A session is an abstract concept that refers to the period of time that a user is signed-in to an application. Like most abstract concepts, it can be modeled and designed to fit your needs.

Before describing Sessions, it’s helpful to grasp the idea of Clients.

### Clients

If a Session is the period of time a user is logged in, the Client is the device with which the user is logged in. Clients can be browsers, native applications, or any other medium that is usually the requesting part in a request/response architecture.

It’s also helpful to think of a Client as a running instance of ClerkJS. When the ClerkJS library is loaded into a browser by an application, it creates an active Client instance. A Clerk Client supports connecting to multiple sessions. This allows users to be logged in to multiple accounts at the same time and easily switch between them.

To learn more about multi-session handling [read the documentation](/docs/authentication/multi-session-applications).

Clerk stores information about the Client and the device it’s running on. This allows users to easily identify which devices they have logged in with and sign that device out of their account. This feature is crucial in case the device gets lost or stolen, or an account gets compromised.

![How We Roll Sessions guide illustration](./142db168fc61365864c470ef858a2001e034bc73-1818x1424.png)

### Creating a Session

To achieve the “Sign out of this device” functionality, Clerk stores the Session that is created when a user logs in. This Session object stores a reference to the Client that was used to create it. In the account security section, Clerk shows all the Sessions for the current user along with the Client information, and when the Session was created to further help identify what devices should and should not be signed in. Signing out from a Client is as simple as deleting the Session object.

Sessions have a maximum lifetime that can be adjusted on the Clerk Dashboard in the Sessions tab. If the application attempts to authenticate with an expired session, the authentication will fail and the user will have to log in again.

Upon sign-in, a reference to the created Session is stored in the `__client` cookie. This cookie is *Secure* and *HttpOnly*, so it’s inaccessible to [MitM](https://developer.mozilla.org/en-US/docs/Glossary/MitM) and [XSS](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) attacks.

From here, this cookie can be used to authenticate with an application. When an HTTP request is received, the cookie can be used to fetch the Session object which has the user ID. If the Session doesn’t exist, the user has signed out of this Client, and the request would throw an Unauthorized error.

However, this process adds extra overhead to the request by introducing a database call. Usually developers attempt to mitigate this overhead by storing the Session object in memory, or in a low-latency store such as a Redis cache. This reduces the cost of fetching the Session object, but requires additional infrastructure to scale.

### Web APIs love JWTs

[JWTs](/blog/guide-JWT-authentication-JSON-Web-Tokens) were invented primarily to solve this additional overhead problem. Since JWTs can be verified to have been generated on a trusted server, the contents of the JWT can be trusted. This means Clerk can encode the Session object into a JWT, which the Client will use to authenticate with the application. When the application receives an HTTP request, the verified token can be used to access the Session object and process the request, instead of having to fetch the Session object from Clerk’s Backend API. The overhead required to verify a JWT is significantly lower than fetching data over a network. This Session token is stored in the `__session` cookie where it’s accessible to the Client. Alternatively, the Client can store this Session token in the browser’s local storage or a native equivalent, and pass it to the server through the Authorization HTTP header.

**JWKS refresher:** [JSON Web Key Set (JWKS)](https://medium.com/javarevisited/json-web-key-set-jwks-94dc26847a34) is a way for an application to advertise the public key that can be used to verify that a token was generated and signed by that application. Clerk applications expose their public keys at the `clerk.your-site.com/.well-known/jwks.json` endpoint.

When an API receives a request with a token, the JWKS is used to verify that the token was generated by Clerk. The API server fetches the JWKS at startup time and stores it in memory to save the network latency of fetching JWKS on demand. JSON Web Keys are stable and don’t need to be refreshed.

However, since the Session object is now stored on the Client that created it, only this Client is able to invalidate or terminate the session. This removes the ability to remotely sign out from a specific device. So while JWT Sessions solve the extra overhead, they disable a key security feature.

### It Always Comes Down To Cache Invalidation

Since the ability to remotely sign out of devices is a crucial feature, the source of truth has to be the Session object stored on the server. It cannot be the one stored on the Client. But fetching from the database is expensive. Whereas the JWT essentially acts as a cached version of the Session object that can never be programmatically invalidated. It’s the other extreme of the spectrum.

Fortunately, JWTs have a lifetime of their own and can be set to expire at some point in the future. When Clerk generates a Session token for a sign-in, the token is set to expire 60 seconds into the future. This Session token can be used to authenticate for the next 60 seconds. A database fetch for the Session is only required once the token has expired and a new token needs to be generated.

When a Session is deleted (user signs out of a device), new tokens cannot be generated, but the most recently generated token can still be used if it was generated less than 60 seconds ago. This guarantees that authentication states in an application will never be invalid for more than 60 seconds.

This approach allows Clerk to offer highly performant authentication without compromising on the security benefits of persistent Sessions.

### Inactivity

Generating short-lived tokens for a long-lived session comes with an additional benefit - it is a great indicator of user activity. If the user stops interacting with the application for more than 60 seconds, a new Session token is not generated. The creation time of the most recent session token can be used by Clerk to determine when the user was last *using* the application instead of just being logged in.

Clerk allows developers to enable Inactivity timeout - this is the maximum amount of time the user can be away from the application and come back to it without having to log in again. Inactivity timeout can also be set on the Clerk Dashboard in the Sessions tab.

### Dynamic Permissions

Sessions are not only created and destroyed - they can also be mutated within their lifetime. The most common use case for mutating Sessions is changing permissions or authorization policies. While a user is logged into the application, they might get added to or removed from organizations, or their roles within an organization could change.

Clerk saves this information in the Session object and ensures it’s updated so that new Session tokens can cache it and the application can process the request without making additional calls to Clerk.

Clerk also allows customization of the Session tokens. Developers can add additional claims to the Session token in the Clerk Dashboard. Shortcodes can be used to inject user data and metadata. In this example, a new claim `verified` is added to the token. Users with an unverified email address will have this claim set to `false` in the token. Once they verify their email, the next Session token that is generated will have this set to true, so any access rules depending on this claim will work as expected.

To learn more about Session token customization [read the documentation](/docs/backend-requests/making/custom-session-token).

## Summary

Sessions are a very powerful tool for enabling security capabilities like multi-sign-in, inactivity timeout, remote sign-out, and dynamic permissions. Clerk takes on the burden of implementing the complex mechanism of Session handling so that developers have access to a performant and flexible authentication system that is easy to integrate.

To learn more about the elements of session management and relevant security concerns, check out [this blog post](/blog/what-is-session-management).

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 7: JWT Single Sign-On
URL: https://clerk.com/blog/how-we-roll-jwt-sso.md
Date: 2023-07-14
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers how Clerk integrates with BaaS providers with JWT SSO.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 7: JWT Single Sign-On

Clerk offers [seamless integration](/docs/integration/overview) with Backend as a Service (BaaS) providers like [Firebase](https://firebase.google.com), [Supabase](https://supabase.com), and [Convex](https://www.convex.dev). BaaS providers enable developers to build applications quickly without worrying about the backend and infrastructure, similar to what Clerk does for auth. These providers abstract complex infrastructure logic and make it accessible to the front-end client through simple APIs and SDKs.

With that being said, any time you are accessing data from the client, there is a security concern; proper authentication and authorization is paramount, which is why these providers enable developers to configure security rules.

![How We Roll Jwt Sso guide illustration](./14852af290484142fa8bcd2f2d9db1925a8261cc-1346x344.png)

Clerk's officially supported integrations with these providers allow developers to seamlessly leverage Clerk for authentication in applications that depend on a BaaS. This is achieved through a concept called **JWT Single Sign-On**.

### Web APIs love JWTs

A JSON Web Token (JWT, pronounced *jot*) is a JSON string that is cryptographically signed and base64 encoded. Anyone can decode and read this key, but it can only be generated and verified using a secret key (or a private-public key pair). It’s like a passport that web apps create to give users access.

Claims are the credentials encoded in a JWT that are used to decide what resources the user has access to. If a JWT is a passport, claims are the contents of the passport, including your name, date of birth, passport number, and any stamps.

To learn more about JWTs, check out [this blog post](/blog/guide-JWT-authentication-JSON-Web-Tokens). Or implement JWT auth from scratch with [this blog post](/blog/adding-jwt-authentication-to-react).

### JWT Single Sign-On

JWT SSO is conceptually similar to OIDC or SAML Single Sign-On - it enables multiple services to use a single identity provider.

**SAML refresher:** Security Assertion Markup Language is an XML-based protocol that enables Single Sign-On, similar
to OIDC. SAML is used heavily in enterprise environments with a central auth system that issues XML tokens to
applications when a user needs to authenticate.

OIDC and SAML providers act as a source of identity for a user. They provide information about the user, like email address, name, avatar, and roles.

JWT SSO providers essentially share the logged-in session of the user with the services. They provide a token that represents not just the user, but the fact that the user *recently* signed in. If these tokens are short-lived, they can be regenerated, as long as the user is still logged in. JWT SSO allows the user to “be logged in” to multiple services (for example Clerk and Supabase) at the same time.

Clerk achieves this by generating a custom JWT for the signed-in user, which can then be used to make API requests to the provider. Developers can configure **JWT templates** in the Clerk Dashboard to generate these custom tokens.

```tsx
try {
  const token = await Clerk.session.getToken({ template: 'template_name' })
  // use token to call external service
} catch (e) {
  // handle error
}
```

### JWT Templates

To generate custom JWTs on the client, developers can configure a JWT template in the Clerk Dashboard. The template provides all the configurations necessary for JWT SSO to work with various providers.

These are some of the configuration options for JWT templates available on the Clerk Dashboard.

#### Token lifetime

Based on caching or security policies, developers can configure how long the token should be valid for. A long-lived token can be generated once, cached, and reused. A short-lived token has to be generated every time a request is made.

#### Custom signing key

Different providers have different ways to validate a JWT that they did not generate. For example, Firebase and Supabase require the tokens to be generated using their own secret. Developers can get this secret from the provider's dashboard, and configure the JWT template to use this secret key in the Clerk Dashboard.

When this token is generated and sent to the provider, the provider will be able to validate the token since it was generated using their key.

#### Issuer defined key

Other providers like Convex do not require using their own secrets. Instead, they can be configured with a public key or with **JSON Web Key Set** (JWKS).

A **JSON Web Key** is a public key that can be used to validate a token but cannot be used to generate one. An auth provider (issuer) like Clerk exposes its JSON Web Keys in the JWKS endpoint, which can be used by external providers to verify if a JWT was generated by Clerk. Similar to the JWT secret, the JWKS is unique to each application on Clerk.

Services like Convex allow developers to define an Issuer and a JWKS endpoint, which tells the service where to find the JWKS to validate the tokens. Both the Issuer URL and JWKS URL are available on the JWT Template configuration page on the Clerk DashboardTemplate page.

#### Custom Claims

Along with validating that the token was securely generated, data providers also need to know who the user is to check against security rules.

Most providers expect specific claims in the JWTs, optionally within a namespace. Clerk has pre-built JWT templates for popular services, and the templates are already configured with custom claims that the specific services look for. Within custom claims, handlebars-style templating can be used to include user information with shortcodes.

```json {{ title: 'JWT Template Custom Claims with Shortcodes' }}
{
  "aud": "convex",
  "name": "{{user.full_name}}",
  "nickname": "{{user.username}}",
  "picture": "{{user.image_url}}",
  "given_name": "{{user.first_name}}",
  "family_name": "{{user.last_name}}",
  "email": "{{user.primary_email_address}}",
  "phone_number": "{{user.primary_phone_number}}",
  "email_verified": "{{user.email_verified}}",
  "phone_number_verified": "{{user.phone_number_verified}}",
  "updated_at": "{{user.updated_at}}"
}
```

## Summary

Clerk strives to be a complete auth solution with an exceptional developer experience, and integrating with BaaS providers is an important part. Clerk’s JWT Single Sign-On enables this through session-sharing, or sharing the logged in state of the user with the BaaS providers, so developers can focus on building applications with minimal glue code or configuration required.

To learn more about the integrations supported by JWT SSO, check out our [Integration docs](/docs/integration/overview).

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# Migrating from Pages Router to App Router: An Incremental Guide
URL: https://clerk.com/blog/migrating-pages-router-to-app-router-an-incremental-guide.md
Date: 2023-07-03
Category: Guides
Description: Already know the /pages directory? Here's a simple way to migrate to the /app directory in Next.js 13.

The Next.js [App Router](https://nextjs.org/docs/app) (in the **/app** directory) is a new way to build React applications. If you're already familiar with the [Pages Router](https://nextjs.org/docs/pages) (in the **/pages** directory), Next.js has made it really easy to adopt the App Router incrementally, quite literally on a page-by-page basis. This guide explains how.

## How is this guide different?

The App Router already has a [migration guide](https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration), so how is this different?

We want to demonstrate a 1-to-1 mapping of Pages Router to App Router, but this is not a complete migration. In the snippets below you will see obvious potential refactors, and **that is on purpose.**

As an example, one App Router snippet below still has function called `getServerSideProps`. It doesn't make sense to keep that name, but we want to demonstrate how `getServerSideProps` can be expressed in the context of the App Router.

One more disclaimer: this will not explain how to migrate *everything*. We focused on the best practices for the Pages Router in Next.js 12.3, but left out older APIs like `getInitialProps`.

## One quick clarification

You probably know that the App Router supports both **Client Components** and the newly introduced **Server Components**.

Before the App Router, Client Components were just called Components. We want to clarify that after the App Router, **absolutely nothing has changed about them.**

Most importantly, within the App Router, **Client Components** are still rendered on the server, then hydrated on client. Search engine crawlers can still index their HTML.

Within this guide, the React code from your Pages Router will be copied to new files and labeled with `"use client"` at the top. This is expected, since we're doing a 1-to-1 mapping.

## Migrating from the Pages Router to the App Router

### 0. Create the /app directory

Before you can get started with the App Router, you will first need to create a **/app** directory as a sibling to your **/pages** directory.

### 1. Migrate /pages/\_document to the App Router

If you have a [Custom Document](https://nextjs.org/docs/pages/building-your-application/routing/custom-document) at **/pages/\_document.tsx**, it should look something like this, though likely with some customization:

```tsx {{ title: '/pages/_document.tsx' }}
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}
```

We need to convert this into a [Root Layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required), which can be treated as a 1-to-1 corollary to a Custom Document:

1. Clone **/pages/\_document.tsx** into **/app/layout.tsx**. We will keep both files since we're doing incremental adoption. (If you use .jsx that is no problem, you can use layout.jsx instead)
2. Remove the **next/document** import line entirely
3. Replace `<Html>` and `</Html>` with the lowercase, HTML equivalent `<html>` and `</html>`. For accessibility, it's best to add a language to your opening tag, like `<html lang="en">`
4. Replace `<Head>` and `</Head>` with the lowercase, HTML equivalent `<head>` and `</head>`. If you only have a self-closing `<Head />`, you can remove it entirely
5. Replace `<Main />` with `{children}`, and update the default function export to accept a `{children}` argument. For Typescript users, `children` is of type `React.ReactNode`
6. Remove `<NextScript />` entirely

When complete, **/app/layout.tsx** should look more like this, plus your customizations:

```tsx {{ title: '/app/layout.tsx' }}
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
```

**Important: /app/layout.tsx is required** in the **/app** directory. If you do not have a Custom Document, you can copy-paste the above sample directly into **/app/layout.tsx**.

### 2. Migrate /pages/\_app.tsx to the App Router

**Note:** If you do not have a file at **/pages/\_app.tsx** you can skip to Step 3.

If you have a [Custom App](https://nextjs.org/docs/pages/building-your-application/routing/custom-app) at **/pages/\_app.tsx**, it should look something like this, though likely with some customization:

```tsx {{ title: '/pages/_app.tsx' }}
import type { AppProps } from 'next/app'

export default function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}
```

The **/app** directory does not have a 1-to-1 corollary for a Custom App, but it can easily be expressed in the new structure:

1. Clone **/pages/\_app.tsx** into **/app/ClientLayout.tsx**. We will keep both files since we're doing incremental adoption. (If you use .jsx that is no problem, you can use ClientLayout.jsx instead)
2. Add a new line to the top of the file that reads `"use client"` (with the quotes)
3. Replace the default export's function signature. Instead of taking `Component` and `pageProps` arguments, it should only take a `children` argument. For Typescript users, `children` is of type `React.ReactNode`.
4. Replace `<Component {...pageProps} />` with `<>{children}</>`, or just `{children}` if you have another wrapping element
5. If there are any remaining references to `pageProps`, please comment them out for now, and revisit them on a page-by-page basis. Next.js has added a new [metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) that should normally be used in place of accessing `pageProps` here
6. We recommend changing the default export name from `MyApp` to `ClientLayout`. It is not strictly necessary, but it is more conventional

When complete, **/app/ClientLayout.tsx** should look more like this, plus your customizations:

```tsx {{ title: '/app/ClientLayout.tsx' }}
'use client'

export default function ClientLayout({ children }: { children: React.ReactNode }) {
  return <>{children}</>
}
```

Now, this is where things get a little different:

- In the Pages Router, **/pages/\_app.tsx** is a "magic" layout file that is automatically added to the React tree
- In the App Router, the only layout file automatically added to the React tree is the Root Layout from Step 1. So, we will need to manually import and mount our `ClientLayout` inside **/app/layout.tsx**

Open **/app/layout.tsx**, import ClientLayout, and use it to wrap `{children}`. When complete, your Root Layout should look like this, plus any customizations from Step 1:

```tsx {{ title: '/app/layout.tsx' }}
import ClientLayout from './ClientLayout'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ClientLayout>{children}</ClientLayout>
      </body>
    </html>
  )
}
```

### 3. Migrate each page to the App Router

Now that your layout has been copied into the App Router, it's time to start migrating your pages one-by-one. There will be a few steps for each page:

1. Create a directory for the page
2. Create one file to handle data fetching
3. Create one file to render the page
4. Remove the Pages Router page

For the avoidance of doubt: yes, we will be splitting your Pages Router page into two files: one for data fetching and one for rendering.

#### 3.1. Create a directory for the page

Both the Pages Router and the App Router are "filesystem routers," but they are organized slightly differently. In the App Router, each page gets its own directory. Here is how to determine the directory name:

- If your file is named **index.tsx**, create the same parent directory structure
  - For **/pages/foo/index.tsx**, create **/app/foo**
  - For **/pages/index.tsx**, you already have **/app**
- If your file is **not** named **index.tsx**, create a directory with that filename
  - For **/pages/bar.tsx**, create **/app/bar**
  - For **/pages/baz/\[slug].tsx**, create **/app/baz/\[slug]**
  - For **/pages/baz/\[\[...slug].tsx**, create **/app/baz/\[\[...slug]]**

#### 3.2. Create a file to handle data fetching

Inside your page directory, create a file called **page.tsx** to handle data fetching. Copy-paste the following snippet as the foundation of this file (**Note:** we will create **ClientPage.tsx** in 3.3.):

```tsx {{ title: 'page.tsx' }}
import ClientPage from './ClientPage'

export default async function Page() {
  return <ClientPage />
}
```

If your Pages Router file does not have any data fetching, you can continue on to the next step. Otherwise, find your data fetcher below to learn how it can be migrated:

**Migrating getStaticProps to the App Router**

Consider the following is your implementation of `getStaticProps`:

```typescript
export const getStaticProps: GetStaticProps<PageProps> = async () => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}
```

To migrate this with as little modification as possible, we will:

1. Copy-paste `getStaticProps` into **page.tsx**
2. Call `getStaticProps` from within our `Page` component
3. Add `export const dynamic = "force-static"` so the page data is fetched once and cached, not refetched on every load
4. Pass the result to our (not yet created) `ClientPage` component

Here is the end result:

```tsx {{ title: 'page.tsx' }}
import ClientPage from './ClientPage'

export const getStaticProps: GetStaticProps<PageProps> = async () => {
  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo = await res.json()
  return { props: { repo } }
}

export const dynamic = 'force-static'

export default async function Page() {
  const { props } = await getStaticProps()
  return <ClientPage {...props} />
}
```

**Migrating getServerSideProps to the App Router**

Consider the following implementation of `getServerSideProps`:

```typescript
import { getAuth } from '@clerk/nextjs/server'

export const getServerSideProps: GetServerSideProps<PageProps> = async ({ req }) => {
  const { userId } = getAuth(req)

  const res = await fetch('https://api.example.com/foo', {
    headers: {
      Authorization: `Bearer: ${process.env.API_KEY}`,
    },
  })
  const data = await res.json()
  return { props: { data } }
}
```

To migrate this with as little modification as possible, we will:

1. Copy-paste `getServerSideProps` into **page.tsx**
2. Add `export const dynamic = "force-dynamic"` so the page data is refetched on every load
3. Replace any usage of `req` with the App Router equivalent
4. Our example uses [Clerk for authentication](/nextjs-authentication), so the end result will replace this line with its App Router-compatible replacement
5. Replace `req.headers` with the new [headers() helper](https://nextjs.org/docs/app/api-reference/functions/headers)
6. Replace `req.cookies` with the new [cookies() helper](https://nextjs.org/docs/app/api-reference/functions/headers)
7. Replace `req.url.searchParams` with the new [searchParams helper](https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional)
8. Replace any Dynamic Route segment usage with the new [params helper](https://nextjs.org/docs/app/api-reference/file-conventions/page#params-optional)
9. Call `getServerSideProps` from within our `Page` component
10. Pass the result to our (not yet created) `ClientPage` component

Here is the end result:

```tsx {{ title: 'page.tsx' }}
import { auth } from '@clerk/nextjs'
import ClientPage from './ClientPage'

export const getServerSideProps: GetServerSideProps<PageProps> = async () => {
  const { userId } = auth()

  const res = await fetch('https://api.example.com/foo', {
    headers: {
      Authorization: `Bearer: ${process.env.API_KEY}`,
    },
  })
  const data = await res.json()
  return { props: { data } }
}

export const dynamic = 'force-dynamic'

export default async function Page() {
  const { props } = await getServerSideProps()
  return <ClientPage {...props} />
}
```

**Migrating getStaticPaths to the App Router**

Consider the following implementation of `getStaticPaths`:

```typescript
export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
  }
}
```

In the App Router, this implementation barely changes. It's simply given a new name (`generateStaticParams`) and the output is transformed to something simpler. That means you can use your old implementation directly, and simply transform the output.

Here is the end result – we included an example of how it can be used in tandem with `getStaticProps`:

```tsx {{ title: 'page.tsx' }}
import ClientPage from './ClientPage'

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
  }
}

export async function generateStaticParams() {
  const staticPaths = await getStaticPaths()
  return staticPaths.paths.map((x) => x.params)
}

export const getStaticProps: GetStaticProps<PageProps> = async ({ params }) => {
  const res = await fetch(`https://api.example.com/foo/${params.id}`)
  const data = await res.json()
  return { props: { data } }
}

export default async function Page({ params }) {
  const { props } = await getStaticProps({ params })
  return <ClientPage {...props} />
}
```

#### 3.3. Create a file to render the page

Now that data fetching is ready, we need to configure the rendering. To accomplish this:

- Copy your original Pages Router page into **ClientPage.tsx**
- Remove any data fetching code, since it now lives in **page.tsx**

That's it! We have already configured **page.tsx** to mount this file and pass props, so it should be working.

#### 3.4. Remove the Pages Router page

Now that your page is ready in the App Router, you can delete the old Pages Router variant.

## What's next?

Now that your Pages Router application is working in the App Router, it's time to start taking advantage of the App Router and React Server Components.

In particular, right now your **ClientPage.tsx** files are one big Client Component. Going forward, it's best to refactor this so `"use client"` is used as sparingly as possible, ideally only on small components. A big Server Component importing many small Client Components will lead to less Javascript sent to the client, and a faster experience for your end user.

---

# Social SSO in Next.js
URL: https://clerk.com/blog/social-sso-in-next-js.md
Date: 2023-06-24
Category: Guides
Description: In this article, we explore how to incorporate OAuth SSO into a Next.js project with JSON Web Tokens (JWTs) and the new app router from Next.js.

Authentication has always been a critical aspect of web development, with user data security being paramount.

As the digital world continues to evolve, so does the need for more efficient and secure authentication methods. However, users do not necessarily want to remember secure logins for every app they use.

This is where OAuth Single Sign-On (SSO) comes into play. OAuth simplifies the user authentication process by allowing users to log in with accounts they already have with other services like [Google](/blog/nextjs-google-authentication), Facebook, or Microsoft.

In this article, we will explore how to incorporate OAuth SSO into a Next.js project with [JSON Web Tokens (JWTs)](https://jwt.io/introduction) and Next.js’s new app router, and later see how Clerk, an [authentication service](/nextjs-authentication), can help simplify this and make it more secure. We will focus on GitHub SSO for simplicity, but most other OAuth providers can be implemented similarly.

Before we get started, if you ever need to look at the full source, code, the [example code is on GitHub](https://github.com/AsyncBanana/nextjs-sso).

## Set up the project

Before we start, make sure you are on Node v18+. You can check this by running `node -v` in a terminal.

### Scaffolding

We will use [create-next-app](https://nextjs.org/docs/getting-started/installation) to scaffold our project. First, you will want to open a terminal in the directory you want to put the project in and run:

```sh
npx create-next-app@latest
```

If you want to use a different package manager, use their own counterpart to `npx`, like `pnpx`. c`reate-next-app` will ask you some questions about your project. Name the project whatever you want; it doesn’t matter. For TypeScript, ESLint, Tailwind, and using `/src`, select no. For the app router, select yes. Finally, leave the import aliases unmodified. After, the output should look like this:

![Social Sso In Next Js guide illustration](./3831e3d2719600d436fc5a7f55c3528434e9b6e0-2000x1250.png)

Now, open that directory in a code editor of your choice. You will need to install jsonwebtoken, which we will use for verifying JWTs.

```sh
npm i jsonwebtoken
```

### Create a GitHub OAuth App

Next, we will need to create the OAuth app in GitHub. Go to [github.com/settings/applications/new](https://github.com/settings/applications/new) and fill out the information. Set the homepage URL to `http://localhost:3000` and the callback URL to `http://localhost:3000/callback` (if you are running the Next.js server on a port other than 3000, you can replace 3000 with that port). You can leave device flow unchecked. Click register and you will be sent to the new application page.

![Social Sso In Next Js guide illustration](./387ca25acbfc80693dda994683b9ed4a0ba430ae-2000x1250.png)

Now, let’s go back to the code for a minute. Create a new file called `.env.local`. This is where you will store your environment variables, which are variables that specify service keys or other configuration that is specific to each deployment and are specified by writing `VARIABLE_NAME=variable_value`, with each variable being on one line.

In the `.env.local` file, create a variable named `NEXT_PUBLIC_GITHUB_OAUTH_ID`. This will contain your GitHub OAuth application’s id. Note that the `NEXT_PUBLIC` prefix tells Next.js to allow the env variable to be accessed by replacing all calls to `process.env.NEXT_PUBLIC_GITHUB_OAUTH_ID` with the value of the variable at build time. If you did not include this prefix, only the server would be able to access the value, which is good for secrets. With that aside, we need to get the GitHub OAuth application id, which you can find on the application dashboard you were sent to earlier. Copy it and paste it into the .env file.

We also need to get a secret, which we will put with the variable `GITHUB_OAUTH_SECRET`. On the dashboard, you can click “Generate a client secret” to get a new secret. Copy it and paste it in the .env file under the new variable.

Finally, we need `JWT_SECRET`, which should be a random password we use for encrypting JWTs. You can use a [password generator](https://passwordsgenerator.net) for this.

That is it for set up! Remember not to give anyone any of the secrets unless you trust them, as they can be used to impersonate users and gain unauthorized access to the application.

## Implement sign in

Our next step is to allow people to sign in by adding a sign in button on the homepage that links to the GitHub OAuth verification page. Open `app/page.js` and insert the following below the Next.js logo (You should be able to find it by looking for an `<Image>` with `src=src="/next.svg"`).

```javascript
<a
  href={`https://github.com/login/oauth/authorize?scope=user:email&client_id=${process.env.NEXT_PUBLIC_GITHUB_OAUTH_ID}`}
  className={buttonStyle.button}
>
  Sign in with GitHub
</a>
```

The link will send people to a page where they can authorize the OAuth app’s access to their account information to allow the app to verify their identity. You might notice `className` contains a style that does not currently exist. We will fix this next. In `/app`, create a file named `button.module.css` and paste the following in.

```css
.button {
  z-index: 2;
  margin: 10px;
  transition-duration: 0.2s;
  display: inline-block;
  font-weight: 600;
  border: 1px solid transparent;
  border-radius: 6px;
  white-space: nowrap;
  padding: 5px 16px;
  font-size: 14px;
  line-height: 20px;
  vertical-align: middle;
  cursor: pointer;
  user-select: none;
  color: #24292e;
  background-color: #fafbfc;
}

.button:hover {
  background-color: #f3f4f6;
}

.button:focus {
  outline: none;
  border: 1px solid #c0d3eb;
  box-shadow: 0 0 0 1px black;
}

.button:active {
  background-color: #edeff2;
}
```

This just provides some simple styling for the button. In order to use it, we need to import it in `page.js`. Add this line to the top of `page.js`.

```jsx
import buttonStyle from './button.module.css'
```

To get the layout correct, we also have to add this to `.center` in `page.module.css`.

```css
flex-direction: column;
```

Now, run `npm run dev` and go to `http://localhost:3000`. You should see this:

![Social Sso In Next Js guide illustration](./247a788c35e5ea1c9dd8c876cbac8cded81fb1ff-3840x2160.png)

If you click “Sign in with GitHub,” you should be redirected to a GitHub page asking you to authorize your OAuth app. However, if you click authorize, you will get a 404 error. That is because we have not implemented a callback page yet, which we will do next.

## Creating a callback

Before we start this step, a bit of explaining is required. You might have seen OAuth flows implemented differently, and this is perfectly fine. There are multiple ways to handle OAuth codes and retrieve access tokens. In this case, we are using [Authorization Code Grant](https://oauth.net/2/grant-types/authorization-code), which is where the OAuth provider (GitHub in this case) sends a request to the specified callback URL (which we are implementing now) with a code. Then, the server sends a request to GitHub with the code, and GitHub responds with the access token. This is generally the most secure method, but for mobile apps or SPAs, it does not always work. With that explained, we will get started on implementing the callback.

You first want to create a folder named `callback` in app and a file named r`oute.js` inside callback. This tells Next.js’s filesystem router that `route.js` should handle API requests sent to `/callback`. Now, paste this into `route.js`:

```javascript
import jwt from 'jsonwebtoken'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(req) {
  const code = new URL(req.url).searchParams.get('code')

  if (!code) {
    return new Response('No code provided', { status: 400 })
  }

  try {
    const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
      method: 'POST',
      headers: {
        accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        client_id: process.env.NEXT_PUBLIC_GITHUB_OAUTH_ID,
        client_secret: process.env.GITHUB_OAUTH_SECRET,
        code,
      }),
    })

    const tokenData = await tokenResponse.json()

    const { access_token } = tokenData

    if (!access_token) {
      return new Response('GitHub login failed', { status: 400 })
    }

    const jwtToken = jwt.sign({ access_token }, process.env.JWT_SECRET, {
      expiresIn: '1h',
    })
    cookies().set('auth', jwtToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV !== 'development',
      sameSite: 'lax',
      maxAge: 3600, // 1 hour
      path: '/',
    })
  } catch (error) {
    console.error(error)
    return new Response('Internal server error', { status: 500 })
  }
  redirect('/dashboard')
}
```

We will go through this step by step. First, we import a few different things:

- `jsonwebtoken` is for creating JWTs, which we use to store the userdata and allow us to verify that bad actors did not change it (we will do this later)
- `cookies` from `next/headers` is for setting cookies on the client. This function is an abstraction over the `Set-Cookie` HTTP header, hence the import location.
- `redirect` from `next/navigation` is for redirecting users. Similar to the `cookies` function, this function abstracts over HTTP responses by automatically responding with an HTTP 3xx status code (read: a redirect) with the `Location` header set to the value passed.

After that, we export a new function named `GET`, which takes `req` as a parameter. This function handles all HTTP requests to `/callback` with the HTTP method `GET`, which is the method used for typical requests that do not contain a body. `req` contains all of the information about the request.

The next part retrieves the code from the `code` query parameter in the URL. You can refer to the explanation at the start of this section to learn how this is used. If there is no code, the function returns a 400 error.

Now that we have the code, we send it back to GitHub to get the access token. After getting the response, we parse the JSON body and extract the access token. If there is no access token, another error is returned.

We sign the token to allow us to store it in a cookie with confidence that it cannot change without the JWT secret. Using the signed token, we set the `auth` cookie for the user. The parameters we passed are very important. `httpOnly` makes sure JavaScript cannot access the cookie’s value, `secure` makes sure that when deployed in production, the cookie cannot be sent over unencrypted HTTP, and `sameSite` prevents the cookie from being sent to third party websites. `sameSite=lax` is the default in modern browsers, but it is still a good idea to make it explicit.

Finally, we redirect the user to `/dashboard`. If you run this code, you should end up at another 404 at `/dashboard`. We will create the dashboard next.

## Creating a protected dashboard

Once again, we need to create a file named `dashboard` and a file inside it named `page.js`. Because this is a page rather than an API route, the naming is different. Copy the following into `page.js`:

```javascript
import { cookies } from 'next/headers'
import jwt from 'jsonwebtoken'
import { redirect } from 'next/navigation'
import buttonStyle from '../button.module.css'

export default async function Page() {
  const auth = cookies().get('auth')
  if (!auth) {
    redirect(
      `https://github.com/login/oauth/authorize?scope=user:email&client_id=${process.env.NEXT_PUBLIC_GITHUB_OAUTH_ID}`,
    )
    return
  }
  const user = jwt.verify(auth.value, process.env.JWT_SECRET)
  const response = await fetch('https://api.github.com/user', {
    headers: {
      Authorization: `token ${user.access_token}`,
    },
    cache: 'no-store',
  })
  const userdata = await response.json()
  return (
    <div>
      <h1
        style={{
          'text-align': 'center',
          'margin-top': '100px',
        }}
      >
        Welcome {userdata.name} to the dashboard!
      </h1>
      <a href="/logout" className={buttonStyle.button}>
        Logout
      </a>
    </div>
  )
}
```

Once again, we will go through this step by step. The imports are largely the same as the callback. The only difference is the addition of `../button.module.css`, which is the CSS file we created earlier for button styling.

This time, instead of a function named `GET`, because this is a page, we export a function named `Page`. Of course, because it is a default export in this case, the naming doesn’t really matter.

Next, we get the auth cookie. If no auth cookie is found, the user is not signed in, so we redirect the user to the sign in.

If there is an auth cookie, we verify the JWT contained in it. This is how we make sure the value was not tampered with. JWTs have a header, payload, and signature. The payload’s value can be accessed without the JWT secret. However, the JWT cannot be modified, as the signature is generated using the JWT secret and payload value. Therefore you need both of those to generate a valid signature. What we are doing here is both decrypting the payload and making sure the signature is valid.

Now that we have verified the JWT and extracted the payload, we use the access token in the JWT to request the user’s data from the GitHub API. The `cache` parameter disables Next.js’s AOT caching/static generation. Normally we would do this inside a `useEffect()` call, but because we are using [React Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) with Next.js, this is the idiomatic way of using `fetch()` (remember that this renders once on the server and does not render on the client period). We then parse the response body to get the userdata.

After that, we just have some markup that renders a welcome message with the user’s name and a logout button. We use inline styles for the header because creating another CSS file just for two rules didn’t make sense. You might notice that the logout button links to `/logout`, which does not exist yet. We will fix that.

Tip: If you have lots of protected routes and want to avoid code repetition, you could implement the authentication as
middleware or just a function imported in the relevant routes

## Adding a logout route

This is pretty simple. The first thing to do is to create another route, just like what we did for `/callback`. Create a new folder in `app` called `logout` and a file called `route.js` in the new folder. Insert the following in the new file:

```javascript
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(req) {
  cookies().delete('auth')
  redirect('/')
}
```

As you can see, this script is pretty simple. We just intercept GET requests to `/logout`, delete the cookie named `auth`, and redirect the user to the homepage. Note that if the user tries to log in again, it might seem as if they are still signed in, as GitHub will silently send the access token and redirect the user when they click login. However, if you implement a more complete sign in solution, this will allow users to sign in to another account with a different method.

That is all! You now have a basic system for OAuth authentication. However, there are a few issues.

## Problems with this Implementation

While this works, there are some problems:

- There is no protection against CSRF (Cross Site Request Forgery)
- There are no refresh tokens, and access tokens can last indefinitely (this is a limitation of GitHub OAuth apps)
- GitHub is currently the only sign in method (non OAuth sign requires many more security measures due to the need to store information in a database)
- There is support if issues arise with authentication

We could solve most of these problems, but it would take a significant amount of time (this tutorial is already almost 3,000 words) and increase maintenance. However, there is a better solution: you can use a service like [Clerk](/). Clerk is a user management platform that allows you to easily and securely implement most of the popular OAuth providers along with standard email/password sign in, [magic link/passwordless](/blog/magic-links), and even Metamask.

## Using Clerk for Authentication

Once again, run:

```sh
npx create-next-app@latest
```

and select the same choices as previously (any name, everything “no” except for the app router). Then, run:

```sh
npm install @clerk/nextjs
```

This installs the Clerk SDK for Next.js. Now, we need to set up a Clerk app. Go to [Clerk’s app creation page](https://dashboard.clerk.com/apps/new) (create an account if you did not already) and go through the configuration. For authentication providers, you can stick to just GitHub again or add as many providers as you want. It doesn’t change the process, and you can change it later. After that, you should be redirected to a page where you see a snippet containing `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY`. Copy this and paste it into a new file named `.env.local`. This includes the environment variables that store your public and private Clerk keys.

To allow us to use Clerk React components in our project, we first need to insert the `<ClerkProvider>` component into `layout.js`. Replace all content in `layout.js` with the following:

```javascript
import './globals.css'
import { Inter } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'
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}>
        <ClerkProvider>{children}</ClerkProvider>
      </body>
    </html>
  )
}
```

`<ClerkProvider>` provides the context necessary to allow Clerk components to function properly, and due to it being `layout.js`, it is inserted into every page.

Next, we need to create the sign in button, just like in the first version. Open `page.js` in `app` and insert this below the Next.js logo (the image with `src="/next.svg"`):

```javascript
{
  auth().userId ? (
    <a href="/dashboard" className={buttonStyle.button}>
      Dashboard
    </a>
  ) : (
    <SignInButton mode="modal" redirectUrl="/dashboard">
      <button className={buttonStyle.button}>Sign In</button>
    </SignInButton>
  )
}
```

This checks if the user is signed in. If they are, it shows a link to the dashboard, and if not, it shows a button to sign in. To make this work, you will also need to import two things at the top of the file:

```javascript
import { SignInButton, auth } from '@clerk/nextjs'
import buttonStyle from './button.module.css'
```

The first line contains the functions and components needed from the Clerk SDK, and the second is for the styling. You will want the same styling as in the previous version, so create a new file named button.module.css and insert this into it:

```css
.button {
  z-index: 2;
  margin: 10px;
  transition-duration: 0.2s;
  display: inline-block;
  font-weight: 600;
  border: 1px solid transparent;
  border-radius: 6px;
  white-space: nowrap;
  padding: 5px 16px;
  font-size: 14px;
  line-height: 20px;
  vertical-align: middle;
  cursor: pointer;
  user-select: none;
  color: #24292e;
  background-color: #fafbfc;
}

.button:hover {
  background-color: #f3f4f6;
}

.button:focus {
  outline: none;
  border: 1px solid #c0d3eb;
  box-shadow: 0 0 0 1px black;
}

.button:active {
  background-color: #edeff2;
}
```

Then, add this to `.center` in `page.module.css`:

```css
flex-direction: column;
```

Now sign in should work, but we still need to implement the dashboard. First, we need to create a JavaScript file at the project root named middleware.js (make sure to put this at the project root, not/app). Paste the following in the new file:

```javascript
import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware()

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

This is a basic setup for the [authentication middleware](/nextjs-authentication). You can configure it more to exclude specific routes, but this should work in most cases.

The next step is to create a folder called `dashboard` and a file named `page.js` inside it, just like with the implementation we already made. Inside the file, paste this:

```javascript
import { currentUser, RedirectToSignIn, SignOutButton } from '@clerk/nextjs'
import buttonStyle from '../button.module.css'

export default async function Page() {
  const userdata = await currentUser()
  if (!userdata) {
    return <RedirectToSignIn />
  }
  return (
    <div>
      <h1
        style={{
          'text-align': 'center',
          'margin-top': '100px',
        }}
      >
        Welcome {userdata.emailAddresses[0].emailAddress} to the dashboard!
      </h1>
      <SignOutButton>
        <button className={buttonStyle.button}>Logout</button>
      </SignOutButton>
    </div>
  )
}
```

We will go through this step by step.

First, we import some packages.

- We import multiple different functions and components from the Clerk SDK. The first, `currentUser()`, helps us get the current user’s information. The second, `RedirectToSignIn`, not to be confused with the function `redirectToSignIn`, which is also exported, is a component that redirects the user to a sign in page. Finally, we have `SignOutButton`, which creates a button to allow the user to log out.
- `buttonStyle` is just CSS from our `button.module.css` file, which we use for the log out button.

Next, we export a function called `Page`, which has been explained previously.

After that, we use `currentUser()` to get the user’s information. If it does not exist (meaning the user is signed out), we redirect to a sign in page using a client side redirect.

Finally, using the userdata, we return markup containing a header with the user’s email and a button to log out.

Now, sign in, the dashboard, and sign out should all work.

## Easy SSO

That’s it! You now should have two implementations of OAuth authentication, one that is hand built and more limited and one that is built with Clerk and more feature rich. If you want to learn more about Clerk, check out the [Clerk Docs](/docs). I hoped you learned something, and thanks for reading!

Interested in exploring more? Look into [Next.js Magic Links](/blog/magic-links) for a seamless, passwordless authentication experience in your Next.js apps.

---

# How We Roll – Chapter 6: User Profile
URL: https://clerk.com/blog/how-we-roll-user-profile.md
Date: 2023-06-23
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter explores how we help developers ship a fully-featured account management UI.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 6: User Profile

Nothing is more frustrating than when a platform collects your information to create a profile for you, but doesn’t allow you to see what data you gave them, much less make any edits to your data without an extensive phone call or email chain. With that being said, building out an account management dashboard requires a good chunk of time to get right (think building out logic and UI components, in addition to the necessary security checks to protect users and their data), so it makes sense why some companies would push this feature off for later.

Our goal here at Clerk is to make it quick and easy for you to spin up a fully-authenticated, fully-featured application. This is why we make every effort to identify the most difficult, tedious, or time-consuming parts of standing up an authenticated app and package these features into components. One example of this is the `<UserProfile />` component, which allows you to stand up a fully featured account management UI just by dropping it into your code.

### Self service

Giving your users control over their accounts is great way to reduce support traffic and increase user satisfaction. Clerk’s `<UserProfile />` [component](/docs/component-reference/user-profile) gives your users autonomy over their data by allowing them to:

- Update their contact information, including name, email, and phone number
- Add additional login methods such as email, phone, and OAuth providers
- Change their password
- Add additional security measures like MFA or SMS OTP
- Delete their account

Implementing a self-serve feature may seem simple, but security must also be taken into consideration. For instance, a user should not be able to add an email without verifying that they own it. Additionally, the user should not be able to use the email until it has been verified. Finally, if the newly added email becomes the primary email address, the prior primary email should be alerted to the change.

We understand that this is a lot to build, which is why we baked these complexities into the `<UserProfile />` [component](/docs/component-reference/user-profile). It saves developers time and makes users happy, win/win!

### MFA / TOTP

Adding MFA to a user's account is a complex undertaking that involves the following steps:

1. Allow the user to choose which MFA option they want to use, such as application authentication
2. Enable the user to scan a QR code or manually type a code into their preferred MFA application
3. Require the user to provide a code provided by the MFA application or device
4. Provide backup codes in an easily accessible manner

When using Clerk’s `<UserProfile />` [component](/docs/component-reference/user-profile), developers can enable MFA / TOTP on with a single click in the dashboard. If you want to learn more about how Clerk handles MFA check out [chapter 3 of how we roll](/blog/how-we-roll-multifactor).

### Security

Our Clerk `<UserProfile/>` provides baked in security features such as listing all active devices, and the ability remotely log out a device if the user doesn’t recognize it.

![How We Roll User Profile guide illustration](./4258d0868dc77144b4d4cb9182f86801d31cd434-1826x1198.png)

This feature ensures that users can keep track of if there are any old or unknown devices with access to their account, but is also immensely helpful in keeping a user's data safe if one of their devices becomes lost or compromised.

### Summary

Account management is a critical and complex element of any application, and it doesn't have to be something you build from scratch. Clerk's `<UserProfile/>` component makes providing your users with a fully functional account management UI a matter of adding a few characters to your app, so you can focus on the core functionality of your application.

Check out the [Clerk Docs](/docs/component-reference/user-profile) to learn more about the `<UserProfile/>` component!

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How to Authenticate API Requests with Clerk & Express
URL: https://clerk.com/blog/how-to-authenticate-api-requests-with-clerk-express.md
Date: 2023-06-16
Category: Guides
Description: In this tutorial, we'll explore how to use Clerk with Express to authenticate API requests using middleware.

APIs are essential for building powerful applications that can communicate and share data with other systems.

However, with great power comes great responsibility: it's critical to ensure that only authorized users can access your API, and that requests are properly authenticated and verified. Failure to do so can lead to serious security breaches, data leaks, and other vulnerabilities that can compromise the integrity of your application and put your users at risk.

In this tutorial, we'll explore how to use Clerk with Express to authenticate API requests using `ClerkExpressWithAuth()` and `ClerkExpressRequireAuth()` middleware, and build a secure and robust backend for your application. Let's get started!

## Always authenticate

When developing an API, especially in Express.js or any other framework, it's important to authenticate requests for a number of reasons:

1. **Security**: The most important reason is to maintain the security of your application. By authenticating API requests, you ensure that only authorized clients or users can interact with your API. This reduces the potential for malicious actions such as data theft, unauthorized modification, or even denial of service attacks.
2. **Access Control**: It allows you to control who can access certain resources and operations in your API. For instance, certain resources might only be available to admin users, while others are available to all authenticated users.
3. **Rate Limiting**: When you authenticate a user, you can associate them with a specific usage quota. This can be used to implement rate limiting, preventing any single user from overloading the server with requests.
4. **Data Accuracy**: In many cases, your API's operations will be tied to a specific user's data. For example, a "get user profile" endpoint would need to know which user's profile to retrieve. Authentication provides a way to associate requests with users.
5. **Audit and Logs**: It allows you to keep track of who did what and when. This is very useful when you need to audit the usage of your system.

Implementing express authentication or Node.js authentication is vital for maintaining the integrity, security, and reliable operation of your APIs.

That being said, there might be some API routes that you intentionally leave unauthenticated for various reasons. For instance, a login or registration route needs to be unauthenticated so that users can authenticate or create an account. Similarly, you might provide some public data through your API that doesn't require authentication.

But you should default to authentication. Consider it a component of building as you plan out epics or sprints on APIs. But adding auth doesn’t have to be particularly challenging. You can build this out yourself with middleware functions, but like a lot of elements in authentication, it’s better to use specialized components.

Let’s go through two of these we have at Clerk, `ClerkExpressWithAuth()` and `ClerkExpressRequireAuth()` to see how we can set these up Express authentication and call these endpoints from a client.

You can check out all the code for this tutorial in this [repo](https://github.com/argotdev/express-authentication).

## Authenticating Express API endpoints with Clerk

Before we get to the Clerk specifics, it’s good to define two components of API methods in Express (and elsewhere) that are fundamental concepts to how authentication will work: *callbacks* and *middleware*.

A ***callback*** function is a function that is passed to another function as a parameter and then invoked by that function at a later time. Callbacks are heavily used in Node.js because it's designed to be asynchronous and non-blocking. When performing I/O operations like making HTTP requests, Node.js can start the operation and then continue executing other code without waiting for the operation to complete. When the operation is complete, the callback function is called with the result.

***Middleware*** functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. Middleware functions can perform tasks like modifying the request or response objects, ending the request-response cycle, or invoking the next middleware function in the stack.

In Express, middleware functions are often used as callbacks to handle HTTP requests. When you define a route in Express.js, you provide a callback function that's called whenever a client makes a request to that route. This callback function is also a middleware, because it has access to the `req`, `res`, and `next` objects.

```jsx
app.get('/example', function (req, res, next) {
  // This function is a middleware and a callback
})
```

So, in this sense, middleware functions are a specific type of callback. They're callbacks that are designed to be used in the context of an HTTP request to an Express.js server.

This is what we’re going to do with Clerk. We’re going to use one of two authentication middleware functions as a callback for the request to our API endpoint. Those two middleware functions are:

1. `ClerkExpressWithAuth()` is a lax authentication middleware that returns an empty auth object when an unauthenticated request is made.
2. `ClerkExpressRequireAuth()` is a strict authentication middleware that raises an error when an unauthenticated request is made.

There are subtle but important differences between these two. Let’s go through them.

### Using ClerkExpressWithAuth()

`ClerkExpressWithAuth()` is lax in that when it fails, it still returns an object, not an error.

Let’s get some code up and running to showcase this function. We’ll create a directory called ‘backend’ and make that the current directory:

```bash
mkdir backend && cd backend
```

With that done, we’ll start installing our dependencies for this code. If you don’t already have it, you’ll also need node as this is the runtime we’re building upon. You can grab the latest build from [here](https://nodejs.org/en).

Then you can run `npm init` to create a package.json in that directory. With that we can use npm to install:

- [Express](https://expressjs.com), which is the web framework for Node.js we’re going to use. Express is a great option for running node servers because it’s fast, minimal, and unopinionated.
- [dotenv](https://www.npmjs.com/package/dotenv), which is the node package you need to read environmental variables in node.
- [@clerk/clerk-sdk-node](https://www.npmjs.com/package/@clerk/clerk-sdk-node) is the Node.js SDK for the Clerk user management platform.
- [cors](https://www.npmjs.com/package/cors) allows us to easily call the endpoint from a client

```bash
npm install express dotenv cors @clerk/clerk-sdk-node
```

When they are installed, create a file in your `with-auth` directory called `app.js`:

```bash
touch app.js
```

Then create a `.env` file in the same directory:

```bash
touch .env
```

This is where you’re going to store your `CLERK_API_KEY`. You can find this in your dashboard. Because you are building these routes on the backend, you can use your secret key:

![Clerk Secret Key](./c5601e5fd15a61e893bfb2d1a7aea4df6eea06f5-2056x442.png)

Then open this directory with your IDE. If you are using VS Code, you can just type `code .` and you’ll get a window ready in that directory.

Add your secret key to your .env file after `CLERK_API_KEY=key-goes-here`. Add the following code to `app.js`:

```jsx
import 'dotenv/config' // To read CLERK_API_KEY
import { ClerkExpressWithAuth } from '@clerk/clerk-sdk-node'
import express from 'express'
import cors from 'cors'
const port = process.env.PORT || 3000

const app = express()
app.use(cors())
// Use the lax middleware that returns an empty auth object when unauthenticated
app.get('/protected-endpoint', ClerkExpressWithAuth(), (req, res) => {
  res.json(req.auth)
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

Let’s work through this line by line.

- Firstly we have all our imports:
  - `import "dotenv/config"` imports the "dotenv" package and automatically runs its `config` function. This package reads environmental variables from the `.env` file and adds them to `process.env`. Here we need it to access a `CLERK_API_KEY` environment variable.
  - We import `ClerkExpressWithAuth` from the "@clerk/clerk-sdk-node" package. This function is middleware for Express.js that handles authentication with Clerk.
  - We import `express` from the Express.js package
  - `import cors from "cors"` imports the "cors" package, a package used for enabling Cross Origin Resource Sharing (CORS). We’ll need this to aid calling the endpoint from our client.
- We then set a constant `port` to the value of the `PORT` environment variable if it's set, otherwise it defaults to `3000`.
- `const app = express()` creates a new Express application. The application is what is going to run our server.
- `app.use(cors())` adds the CORS middleware to the Express application, enabling CORS.
- `app.get(…)` defines a route for the path "/protected-endpoint" on the Express app. This route has two middleware functions:
  - `ClerkExpressWithAuth`: This is the function that checks the authorization of the incoming request. If the request is authenticated, it sets `req.auth` to an object representing the authenticated user.
  - `(req, res) => { res.json(req.auth); console.log(res.json); }`: This is an anonymous function that takes the incoming request and outgoing response as arguments. It sends a JSON response with the `auth` object from the request, and then it logs the JSON response function to the console.
- The final `app.listen(…)` part of the code starts the server and makes it listen for incoming connections on the specified port. It logs a message to the console indicating that the server is running and listening on that port.

Let’s run this:

```bash
node app.js
```

You should now see that message from in your `app.listen(…)` terminal:

```bash
Example app listening at http://localhost:3000
```

Great! You have a working endpoint. Let’s call that endpoint (`http://localhost:3000/protected-endpoint`) from Postman to see what it returns:

```json
{
  "sessionClaims": null,
  "sessionId": null,
  "session": null,
  "userId": null,
  "user": null,
  "actor": null,
  "orgId": null,
  "orgRole": null,
  "orgSlug": null,
  "organization": null,
  "claims": null
}
```

As we said above, `ClerkExpressWithAuth()` returns “an empty auth object when unauthenticated.” Now, you have a conundrum—you need an authenticated user to check this really works. To do that, we’ll create a quick React frontend client that calls `/protected-endpoint` after authenticating a user.

Keep that app running and open up another terminal. If it opens up in the same directory, make sure you cd .. up a level (you don’t want to create your frontend React app in a subdirectory of your backend—headaches will ensue).

We’ll first install create-react-app to help us (funnily enough) create a react app:

```bash
npm install create-react-app
```

Then run `npx create-react-app my-app` where my-app is the name of your app. Here we’ll go with auth-frontend:

```bash
npx create-react-app frontend
```

Then we’ll `cd frontend` to get into that directory and open with our IDE (again using `code .` if using VS Code).

Again, we’re going to add Clerk to this project, this time using`@clerk/clerk-react`, which is the Clerk React SDK.

We’ll also want to install `isomorphic-fetch` and `es6-promise` to polyfill the Fetch API for browsers that don't support it:

```bash
npm install @clerk/clerk-react isomorphic-fetch es6-promise
```

Like with the backend, you are going to need your Clerk API key. This time though you are going to use your public key as we’re authorizing a frontend client:

![Clerk Publishable Key](./60a107564b24f529282c1cda0eaebdfb50345894-2072x360.png)

Create a .env file and then add that key to it like this: `REACT_APP_CLERK_PUBLISHABLE_KEY=key-goes-here`.

Now go to the src/App.js file, remove the boilerplate entirely and add this code:

```jsx
import React from 'react'
import './App.css'
import { ClerkProvider, SignedIn, SignedOut, RedirectToSignIn } from '@clerk/clerk-react'
import Auth from './auth'

if (!process.env.REACT_APP_CLERK_PUBLISHABLE_KEY) {
  throw 'Missing Publishable Key'
}

const clerkPubKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY

function App() {
  return (
    <ClerkProvider publishableKey={clerkPubKey}>
      <SignedIn>
        <Auth />
      </SignedIn>
      <SignedOut>
        <RedirectToSignIn />
      </SignedOut>
    </ClerkProvider>
  )
}

export default App
```

We won’t go through this line by line because we’ve taken it entirely, with one exception, from our docs on [getting started with React](/docs/quickstarts/get-started-with-create-react-app). Head there to learn more about Clerk and React.

That one exception is that we’ve swapped out the `<Welcome />` component in the documentation within the `<SignedIn></SignedIn>` component for an `<Auth />` component. Within the src directory add `auth.js` and add this code:

```jsx
//src/auth.js

import fetch from 'isomorphic-fetch'
import React, { useState, useEffect } from 'react'

import { useAuth } from '@clerk/clerk-react'

function Auth() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  const { getToken } = useAuth()

  useEffect(() => {
    const fetchData = async () => {
      try {
        const token = await getToken()
        const response = await fetch('http://localhost:3000/protected-endpoint', {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${token}`,
            mode: 'cors',
          },
        })

        if (!response.ok) {
          throw new Error('Network response was not ok')
        }

        const result = await response.json()
        setData(result)
        setLoading(false)
      } catch (err) {
        setError(err)
        setLoading(false)
      }
    }

    fetchData()
  }, [getToken])

  if (loading) {
    return <div>Loading...</div>
  }

  if (error) {
    return <div>Error: {error.message}</div>
  }

  return (
    <div>
      <h1>Data from API:</h1>
      <p>{JSON.stringify(data, null, 2)}</p>
    </div>
  )
}

export default Auth
```

We will go through this line by line as it shows how you can pass that authentication token to the API endpoint.

- `import fetch from "isomorphic-fetch"` imports the `fetch` function from the `isomorphic-fetch` package.
- The next line imports the `React` default export and the `useState` and `useEffect` named exports from the `react` package. `useState` is a React Hook that lets you add React state to function components, and `useEffect` lets you perform side effects in function components.
- The final import is for the `useAuth` hook from the `@clerk/clerk-react` package. This hook provides access to Clerk's auth-related functionality.
- The `Auth` function component is then declared. Four pieces of state are created using the `useState` hook: `data`, `loading`, and `error` for storing API response data, the loading state, and any error messages, respectively. The `getToken` function is extracted from the `useAuth` hook to allow authentication token retrieval.
- We then create a `useEffect` hook to define a side effect that fetches data from an API when the component is first mounted and whenever the `getToken` function changes.
- Within the `useEffect` hook, the `fetchData` function tries to retrieve a token, then sends a GET request to our `/protected-endpoint`. If the request fails, it sets the error state and stops loading. If it succeeds, it sets the data state to the response data, and stops loading.
  - The critical part of this is the line `Authorization: Bearer ${token}`. Here, we’re passing the authorization token from our now-signed-in-user to our `/protected-endpoint` to use in its own authentication.
- In the rendering part of the `Auth` component, it checks if the `loading` state is true, and if so, returns a "Loading..." message. If there's an error, it displays the error message. Otherwise, it displays the data fetched from `/protected-endpoint`.
- Finally we export the `Auth` component as the default export of this module. This allows the `Auth` component to be imported and used in other parts of the application.

And thus we import that `Auth` and call it within the `SignedIn` function so we can use the authorization token and pass it to `/protected-endpoint`. Within `/protected-endpoint`, the ClerkExpressWithAuth middleware will check whether it is a valid token, and, if so, return a full auth object:

```json
{
  "sessionClaims": {
    "azp": "http://localhost:3000",
    "exp": 1686337111,
    "iat": 1686337051,
    "iss": "https://keen-moray-98.clerk.accounts.dev",
    "nbf": 1686337041,
    "sid": "sess_2QvzpU7hY6lF5GQSjQQHckp8wps",
    "sub": "user_2Qbkhxfu7VCmvM4Xguez0fmMg1c"
  },
  "sessionId": "sess_2QvzpU7hY6lF5GQSjQQHckp8wps",
  "userId": "user_2Qbkhxfu7VCmvM4Xguez0fmMg1c",
  "claims": {
    "azp": "http://localhost:3000",
    "exp": 1686337111,
    "iat": 1686337051,
    "iss": "https://keen-moray-98.clerk.accounts.dev",
    "nbf": 1686337041,
    "sid": "sess_2QvzpU7hY6lF5GQSjQQHckp8wps",
    "sub": "user_2Qbkhxfu7VCmvM4Xguez0fmMg1c"
  }
}
```

Now, on the backend we have our `sessionId` and `userId` and `claims` to use as needed and we know our user is authenticated and allowed to use this API endpoint!

### Using ClerkExpressRequireAuth()

`ClerkExpressRequireAuth` works slightly different when a user is unauthenticated. Whereas `ClerkExpressWithAuth` just returned an empty auth object, `ClerkExpressRequireAuth` returns an error.

Swap out your code in your express app.js for this below:

```jsx
import 'dotenv/config' // To read CLERK_API_KEY
import { ClerkExpressRequireAuth } from '@clerk/clerk-sdk-node'
import express from 'express'

const port = process.env.PORT || 3000
const app = express()

// Use the strict middleware that raises an error when unauthenticated
app.get('/protected-endpoint', ClerkExpressRequireAuth(), (req, res) => {
  res.json(req.auth)
})

app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(401).send('Unauthenticated!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

Most of this code is the same as the `ClerkExpressWithAuth` example above. The differences are:

- We’re importing the `ClerkExpressRequireAuth` from the Clerk Node.js SDK
- We’re calling that `ClerkExpressRequireAuth` middleware within our app.get() function.
- We now have an error-handling middleware function. If an error occurs in any middleware function that is run before this one (here the `ClerkExpressRequireAuth`), this function will be called. It logs the stack trace of the error and sends a response with the HTTP status code `401`, indicating that the client must authenticate to get the requested response, along with the message `Unauthenticated!`.

And lo and behold, if you call this in Postman you’ll get a 401 and this message:

```
Unauthenticated!
```

This is safer as we aren’t even sharing the structure of our auth object.

If we now call this within our React app though, we do get our auth object because we are authenticated:

```json
{
  "sessionClaims": {
    "azp": "http://localhost:3000",
    "exp": 1686337111,
    "iat": 1686337051,
    "iss": "https://keen-moray-98.clerk.accounts.dev",
    "nbf": 1686337041,
    "sid": "sess_2QvzpU7hY6lF5GQSjQQHckp8wps",
    "sub": "user_2Qbkhxfu7VCmvM4Xguez0fmMg1c"
  },
  "sessionId": "sess_2QvzpU7hY6lF5GQSjQQHckp8wps",
  "userId": "user_2Qbkhxfu7VCmvM4Xguez0fmMg1c",
  "claims": {
    "azp": "http://localhost:3000",
    "exp": 1686337111,
    "iat": 1686337051,
    "iss": "https://keen-moray-98.clerk.accounts.dev",
    "nbf": 1686337041,
    "sid": "sess_2QvzpU7hY6lF5GQSjQQHckp8wps",
    "sub": "user_2Qbkhxfu7VCmvM4Xguez0fmMg1c"
  }
}
```

And with that, you can now authenticate any API endpoint in your Express apps.

## Fast, authenticated endpoints

With Express and Clerk you can get authenticated, production-ready APIs up in a matter of just a few minutes. There is more code in our client that for our endpoint. Using `ClerkExpressWithAuth()` or `ClerkExpressRequireAuth()` you can protect any endpoint and add express authentication or node authentication without the hassle of building it yourself.

This is what you need with authentication—you want it done quickly. That way it’ll actually get done rather than sitting in your backlog queue for months until a dev can get to it. Or an attacker can get to your unprotected endpoints.

Next steps? Check out the code for this tutorial [here](https://github.com/argotdev/express-authentication). Check out Clerk [here](/) and more about using Clerk for express and node authentication [here](/docs/request-authentication/nodejs-express), and how to take these security elements even further with the [options](/docs/request-authentication/nodejs-express#middleware-options). Learn how to set up Clerk within your client to make user management super simple [here](/docs/quickstarts/overview).

---

# How We Roll – Chapter 5: Customization
URL: https://clerk.com/blog/how-we-roll-customization.md
Date: 2023-06-16
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. This chapter covers the various ways developers can customize Clerk's UI components.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, roll) authentication at Clerk.

## Chapter 5: Customization

When you build your brand, you spend hours making sure every color, font, and logo is exactly how you envisioned. When you integrate with a 3rd party product like Clerk, you want to make sure that your brand shines. Clerk empowers developers by offering:

- Highly customizable components
- Custom flows
- Themes for our hosted pages

In this chapter we are going to be talking about how to customize our drop-in components and why we wanted to treat it as a first class experience. Grab a drink and enjoy a deep dive into our `appearance` property.

### Clerk’s Appearance Property

When Clerk introduced customization, we wanted something that wasn’t an afterthought where only minimal items could be changed, like buttons or primary colors. We wanted developers to go as deep as required, and the appearance property allowed this by letting developers:

- Have access to entire themes, like dark mode, and the ability to create a custom theme
- Control the layout
- Have global control of the appearance
- Customize individual components
- Inherit from key branding like fonts, and element sizing
- Integrate with their current CSS method of choice

### Base Themes

The `baseTheme` allows a developer to create a theme for your brand, and can be reused across multiple projects. We provide a package called `@clerk/themes` that allows you to use prebuilt versions that were crafted by Clerk, like our dark theme or shades of purple:

```tsx
import { type AppType } from 'next/app'
import { ClerkProvider } from '@clerk/nextjs'

import { dark } from '@clerk/themes'

const MyApp: AppType = ({ Component, pageProps }) => {
  return (
    <ClerkProvider
      appearance={{
        baseTheme: dark,
      }}
      {...pageProps}
    >
      <Component {...pageProps} />
    </ClerkProvider>
  )
}
```

![How We Roll Customization guide illustration](./de3892f51d7ad303f57aea7dc00f088ab22cafbe-1200x630.png)

### Controlling the Layout

The default layouts for Clerk’s sign in and sign up components show the social buttons at the top, with other forms of authentication, like email, below. In similar fashion, when a user signs up for your application, by default, we show the optional fields. For some applications this makes sense, for others, not so much.

This is where our `layout` property can be used. Developers can move social providers, change the default look of the social buttons, hide optional fields, show links to help, privacy policy, and terms and conditions pages, and much more:

```jsx
<ClerkProvider
  appearance={{
    layout: {
      showOptionalFields: false,
      socialButtonsPlacement: 'bottom',
      socialButtonsVariant: 'iconButton',
      helpPageUrl: '/help',
      privacyPageUrl: '/privacy',
      termsPageUrl: '/terms',
    },
  }}
  {...pageProps}
>
  {/* ... */}
</ClerkProvider>
```

![How We Roll Customization guide illustration](./de36acd844d0309a5eb94a72f0916415763a7b51-1200x630.png)

### Global Customization

Not every application needs its own theme; it is possible to extend the Clerk `baseTheme` to fit with an application. Examples include globally setting some of the attributes, such as every `primaryButton` element having a green background and the text inside being yellow, or setting all text colors to the company’s brand color.

All of this can be done at the `ClerkProvider` level, allowing developers to speed up the customization of our components:

```jsx
<ClerkProvider
  appearance={{
    elements: {
      formButtonPrimary: {
        backgroundColor: 'green',
        color: 'yellow',
      },
    },
    variables: {
      colorText: 'red',
    },
  }}
  {...pageProps}
>
  {/* ... */}
</ClerkProvider>
```

![How We Roll Customization guide illustration](./799b8b17147919db39fba9c505f8a8b73847dec8-1200x630.png)

### Individual Components

Applications are built of components, and individual components may need to be styled in a specific way. For example the Clerk `<UserButton />` might be too small for your sidebar or navigation bar, or the `<UserProfile/>` may not need to show the sidebar.

You can apply the `appearance` property directly to individual Clerk components:

```jsx
import { UserProfile } from '@clerk/nextjs'

export default function Profile() {
  return (
    <div>
      <UserProfile
        appearance={{
          elements: {
            navbar: {
              display: 'none',
            },
          },
        }}
      />
    </div>
  )
}
```

![How We Roll Customization guide illustration](./12a76fb56aa7791c8bb545574e5ad0fdff4e9893-1200x630.png)

### Elements and Variables

We’ve talked a lot about the parts that make Clerk customization a first class experience, but the powerful parts are variables and elements. Whether you are globally styling or styling individual components, variables and elements are essential.

The `variables` property is used to adjust the general styles of the base theme, like colors, backgrounds, typography:

```tsx
import { ClerkProvider, SignIn } from '@clerk/nextjs'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ClerkProvider
      {...pageProps}
      appearance={{
        variables: {
          colorPrimary: 'red',
          colorText: 'white',
        },
      }}
    >
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default MyApp
```

The `elements` property is used for fine-grained theme overrides, and is useful when styling specific HTML elements. You can find the element to target by inspecting the HTML and looking for the class name.

## Summary

Clerk allows for extensive customization when integrating with third-party products, ensuring brand elements are upheld. Developers can customize individual components, control the layout, modify the global appearance, create custom themes, and adjust the style of base themes through the `appearance`, `baseTheme`, `layout`, `variables`, and `elements` properties, ensuring the brand's unique design and user experience are reflected in every interaction.

For an in-depth look at how to leverage the full power of customization with Clerk, check out the Component Customization [docs](/docs/component-customization/appearance-prop).

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 4: Email Verification
URL: https://clerk.com/blog/how-we-roll-email-verification.md
Date: 2023-06-09
Category: Company
Description: How We Roll is a deep dive into how Clerk implements authentication. From codes to links to SSO, this chapter is about when and how we verify emails.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 4: Email Verification

Email verification is a foundational and reusable piece of modern authentication:

- **It’s used during sign-up:** usually to prevent spam accounts, or as a requirement before enrolling a user on a mailing list.
- **It’s used during sign-in:** as a workaround to forgotten password, or as an alternative to passwords altogether.
- **It’s used during account management:** as a mechanism for adding new emails or changing the email for an account.

Although many think of verification links or one-time passcodes (OTPs) as the dominant verification mechanism, both approaches have fallen out-of-favor.

Today, the fastest and most common way to verify emails is actually single-sign on (SSO)! Social SSO (like “Sign in with Google”) and enterprise SSO (like Okta) are already responsible for over half of email verifications, and their adoption continues to grow every year.

This chapter discusses when and how to verify emails, and the tradeoffs of each option. Learn how Clerk handles email verification!

### When to verify

There are two options for when to require email verification: immediately at sign-up, or sometime after completing the signup process.

The most popular approach is to verify immediately at sign-up, which ensures that users will have a valid email, and helps mitigate spam or bot accounts.

In some cases, developers might want to allow signing up without email verification. This is usually done to give users access to some features immediately or for a limited time without jumping through an extra step, also called the foot-in-the-door strategy, while still keeping the bulk of the application behind an email verification check.

To achieve this, developers can turn off verify at sign-up through the Clerk dashboard, then check for email verification in the application to allow access to certain features. The email verification information is available on the [Clerk `User` object](/docs/reference/clerkjs/user) which can be fetched using [the `useUser` hook](/docs/reference/clerk-react/useuser) on the client, or [the `users.getUser` method](/docs/references/backend/user/get-user) on the node SDK.

### How to verify

The core verification mechanism is to send a unique token to the user’s email address. The user will then give this token to the application, verifying that the user owns that email address.

We offer both verification links and one-time passcodes. Developers can enable one of them to enforce that strategy, or enable both and let the users choose during verification.

### One time codes

The more straightforward way to implement verification is with a randomly generated 6-digit code that the user manually types into the application.

Code verification offers a benefit over links - users don’t need to be signed in to their email account to access the application on a device. Users could be using a different browser or a shared or public device that they don’t want to log into their email account on. This is also the reason why codes are seen a lot more for phone verification.

### Links

Clerk will generate a unique link with a signed JWT when using verification links. This link takes the user to the application where Clerk validates the JWT and signs in the user.

The JWT carries the signup attempt information and is only valid for 10 minutes. If the signup was successful, clicking the link again shows this message instead of signing the user in again.

![How We Roll Email Verification guide illustration](./aa54595c3d97f0d6a8c3076695da5ca706e21083-944x734.png)

Verification links offer a streamlined user experience, but it’s not without tradeoffs. Security-conscious individuals and email clients are suspicious of links in emails, as they could be malicious phishing links or download harmful files. Security policies and training often teach us to be careful with links in emails.

While we offer email verification links, for the aforementioned reasons, we recommend that developers use verification codes in most cases.

### Single Sign-on

As mentioned earlier, Single Sign-On with social providers like Google and enterprise providers like Okta is actually responsible for the majority of email verifications. We ensure that the user experience of Single Sign-On is seamless across various providers with no compromises to security.

Clerk’s SSO uses the Open ID Connect protocol (OIDC), which provides an `email_verified` claim. Identity Providers are supposed to set this to `true` if they have verified the user’s email. Almost every SSO provider verifies emails, so if a user signs up with a social provider, a second email verification step is unnecessary.

**OIDC refresher:** OpenID Connect is an identity protocol built on top of the OAuth 2.0 framework. If a request scope
of OIDC is defined when initiating an OAuth flow, identity providers present the application an ID token along with an
access token on a successful sign-in. The ID token is a JWT that contains some user information like name, email, and
avatar, in the form of “claims”.

Some providers do not verify emails immediately. So if a user signs up with SSO and their email is not verified by the provider, Clerk enforces an additional email verification step.

Some providers do not adhere to the OIDC spec. We audit all providers we support to ensure they are adhering to the spec, and if not, what their own format looks like. For example, the ID token from one of the social providers contains a `verified` claim instead of `email_verified` as defined in the spec, so we check for that. If the provider does not tell us whether the email is verified or not, we assume it’s not verified and force an additional email verification step.

Ensuring SSO providers have verified the user’s email is critical. Clerk provides account linking out of the box, which means users can sign in to the same account using multiple SSO providers as long as the email is the same. However, unless every SSO login verifies the emails, the account could be left vulnerable to takeovers. A malicious user could create a new account on a service that does not verify emails immediately, and use it to log in to an application as someone else. We are very careful with auditing every provider we work with to make sure developers don’t have to worry about the nuances across various providers.

### Summary

Email verification may seem like a straightforward task, but it is a common authentication process on the web that significantly affects both the user experience and application security. Clerk provides multiple options out of the box for developers to customize when and how they enforce email verification.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# Secure Authentication in Next.js with Email Magic Links
URL: https://clerk.com/blog/secure-authentication-nextjs-email-magic-links.md
Date: 2023-06-06
Category: Guides
Description: In this guide, you will learn how to implement email magic links in Next.js.

Traditional username and password systems are ubiquitous but mostly suck. Every user needs a complicated random 8-20 character password that they can’t possibly remember for every site. Password managers make this easier, but they are ultimately just papering over the cracks.

Added to this cognitive load for users is the cognitive load for developers, who have to implement these systems securely or constantly worry about their sites becoming targets for malicious activity.

Imagine if you could simplify the user experience, decrease your system’s attack surface, and reduce your workload when it comes to user authentication. This is the promise of [magic links](/blog/magic-links), a powerful approach to passwordless authentication that is rapidly gaining popularity in the world of web development.

Here we want to show you not just the benefits of magic links but how exactly they work, going through an implementation process in Next.js. We’ll also show you why, like most authentication patterns, you don’t want to do this all yourself and how Clerk can help you, just like magic!

## Magical magic links

So, why use magic links? Magic links can greatly improve user experience in several ways:

1. **Ease of use**: Magic links simplify the login process, as users just need to click a link sent to their email to authenticate themselves. They don’t have to remember any usernames or passwords.
2. **Security**: Since there are no passwords to guess, magic links can increase security. This can be especially beneficial for users who often reuse passwords or use weak passwords.
3. **Speed**: Magic links streamline the registration and login process. Rather than having to fill out forms, users just need to enter their email address, then check their inbox and click a link.
4. **Reduced friction**: Magic links eliminate the common frustration of forgotten passwords and the need for password reset processes. 5.**Mobile-friendly**: Typing passwords on mobile devices can be challenging, especially for complex or long passwords. With magic links, users simply click a link in their email, which is much easier on a mobile device.
5. **Trust**: When used properly, magic links can build trust, as they demonstrate a commitment to both user convenience and security.

There are downsides. For one, the user obviously needs to have access to their email. Plus, there is the slight friction element of the user having to leap from the app to their email and back again. In theory, if the user’s email is compromised, an attacker can gain easy access to the app (you can get past this by using [two-factor authentication](/docs/authentication/custom-flows/multifactor) in your authentication flow).

Creating and validating time-sensitive tokens, like those used in magic links, typically involves the following steps:

1. **User Identification**: When the user requests a magic link, your application should first ensure that the email provided is valid and linked to a user account in your system. If the account exists, the server generates a unique, temporary token.
2. **Token Generation**: Generate a unique token using a secure method. This might involve using a secure random number generator or a library or function designed for generating secure tokens, such as JWT (JSON Web Tokens). The token should be associated with the user’s account in your database, along with a timestamp indicating when it was created.
3. **Time-Sensitive Mechanism**: Attach an expiry time to the token. This could be a specific expiry date/time or a duration after which the token will expire. This is usually stored along with the token in the database.
4. **Email Delivery**: Send an email to the user containing the magic link. The link should point to your website or app and include the token as a parameter.
5. **Token Validation**: When the user clicks the magic link, your application should validate the token. This involves checking that the token exists in your database, is linked to a user, and has not expired. If all these checks pass, the user is authenticated.
6. **Token Deletion/Invalidation**: Once a magic link has been used or has expired, it’s important to invalidate it so that it cannot be used again. This can be done by deleting the token from your database or marking it as invalid.

OK, so that’s magic links at a high level. How do you implement them in an application? Let’s go through two ways to do this. Firstly, we’ll show how you can do this with minimal additional libraries in Next.js to show what is required to generate, send, and validate time-sensitive tokens such as magic links. Then we’ll go through how you can implement them quicker and more securely with Clerk.

## Build your own magic links in Next.js

We need to expand the high-level flow above into more granular detail for what we need from our application:

1. **The user enters their email**: The user will enter their email address, which you’ll send to your backend.
2. **Generate a unique token**: On your server, generate a unique token tied to the user’s email. This can be a JWT, a UUID, or some other kind of unique identifier.
3. **Send an email with the magic link**: Include the unique token in the magic link and email it to the user.
4. **User clicks the link**: The user will click the link, which sends the token back to your server.
5. **Verify the token**: On your server, verify that the token is valid and matches the user’s email.
6. **Create a session**: If the token is valid, create a new session for the user and send it back to the client.
7. **Store the session on the client**: On the client, store the session (usually in a cookie or local storage) so that the user remains logged in.
8. **Authorize the user**: Whenever the user makes a request, check that they have a valid session.

So beyond Next.js, for this to work, we need:

- A way to generate and verify our tokens. In this instance, we’re going to use JWT for our tokens and use `jsonwebtoken` to generate and verify them.
- A way to send emails. We’ll use `nodemailer`. You’ll also need SMTP information for your email provider.
- A way to create cookies to store session data. We’ll use the `cookie` library here.

That’s all you need. Let’s first spin up a Next.js app called ‘magic-links’ as the bare bones of what we’ll create:

```bash
npx create-next-app@latest magic-links
```

You’ll be asked a bunch of questions. Here we’re not using TypeScript and neither are we using the App directory (which is the new, better way of using Next.js that we’ll use later. But it doesn’t play as nicely with some of the API routes we’re creating here).

After that we’ll install the necessary packages:

```bash
npm install jsonwebtoken nodemailer cookie
```

You can then run npm run dev to start the development server. At the moment all you’ll get at [http://localhost:3000](http://localhost:3000) is the regular Next.js start page:

![image1.png](./e31247c8534a6bc92acfb54d8be11422ff7c074e-1999x1264.png)

Obviously we need a login component as the first step. We’ll create a /components directory and add a login.js file:

```jsx
// components/login.js

import { useState } from 'react'

function Login() {
  const [email, setEmail] = useState('')

  const handleSubmit = async (e) => {
    e.preventDefault()
    const res = await fetch('/api/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ email }),
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
      <button type="submit">Send Magic Link</button>
    </form>
  )
}

export default Login
```

So there are two main parts to this component. At the bottom we have the actual form a user will fill in with their email address. As they type their email in, the state of the `email` variable will change to contain their email address.

When the press ‘Send Magic Link,’ The `handleSubmit` function will be called. This function will call a backend api route, `/api/login`. We’re going to send a POST request to this endpoint with the email address in the body.

Let’s add this to our index page. Delete all the boilerplate code within that file and replace it with:

```jsx
// pages/index.js

import { Inter } from 'next/font/google'
import Login from '../components/login'

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

export default function Home() {
  return (
    <main
      className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
    >
      <div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
        <Login />
      </div>
    </main>
  )
}
```

We’re importing the `Login` component and adding it to this page. Now a user will see this at [http://localhost:3000](http://localhost:3000):

![image8.png](./f92c06581c57df46dfbf7dbd31ce2dafcb0dc877-1999x1264.png)

If they were to add their email into that field and press ‘Send Magic Link,’ guess what would happen?

Absolutely nothing! Let’s make something happen. What we need is that `/api/login` the Login component calls. We’ll create a `login.js` file in the api directory. Though this is within the `pages` directory, it won’t be treated as a page by Next.js, it will act like an API endpoint:

```jsx
// pages/api/login.js

import jwt from 'jsonwebtoken'
import nodemailer from 'nodemailer'

export default async function handler(req, res) {
  const { email } = req.body

  // Create a magic link token
  const token = jwt.sign({ email }, process.env.JWT_SECRET, {
    expiresIn: '15m',
  })

  const transporter = nodemailer.createTransport({
    host: 'mail.example.com',
    port: 587,
    secure: false,
    auth: {
      user: 'username',
      pass: 'password',
    },
  })

  // Generate magic link
  const magicLink = `${req.headers.origin}/api/verify?token=${token}`

  await transporter.sendMail({
    from: '"Your Name" <your-email@example.com>', // sender address
    to: email, // list of receivers
    subject: 'Your Magic Link', // Subject line
    text: `Click on this link to log in: ${magicLink}`, // plain text body
  })

  res.status(200).json({ success: true })
}
```

This is where the first part of the magic of magic links happens. Let’s step through this to see what’s happening:

- First we’re importing the libraries we need, `jsonwebtoken` and `nodemailer`.
- Within our `handler` function, we’ll get the `email` from the body of the request.
- With that email as the payload, we’ll use the sign method from `jsonwebtoken` to create our token. There are two extra parameters we need to pass to sign:
  - A `JWT_SECRET`. You can create a secret (for these purposes, not for production) by running `openssl rand -base64 32` in the terminal to generate a key. You then store that key in a .env file in the root of the project for the project to read.
  - An expiresIn time. Here, we’ve set this to 15 minutes.
- Now we have our token, we need to send it to the user’s email address. Here we’re using the `createTransport` method from `nodemailer` to create an object called a `transporter` that contains all our (i.e. the sender) SMTP email information.
- We’ll then create the actual magic link, which will be the origin URL (our URL) with the token appended as a query.
- Then we’ll call `sendMail` on the `transporter` object to send the email to the recipient

Now if the user presses ‘Send Magic Link,’ they should get an email like this:

![image5.png](./e6395766363d25d1f351680920e1e2f63485d730-1999x221.png)

Now if they click on that guess what would happen?

Absolutely nothing!

We need an endpoint to verify the token and set a cookie on the client with the user's email. First, let’s create another file in the api directory, this one called `verify.js`:

```jsx
// pages/api/verify.js

import jwt from 'jsonwebtoken'
import { serialize } from 'cookie'

export default async function handler(req, res) {
  const { token } = req.query

  try {
    // Verify the token - this throws if the token is invalid
    const { email } = jwt.verify(token, process.env.JWT_SECRET)

    // The token is valid, so we create a session
    const sessionToken = jwt.sign({ email }, process.env.JWT_SECRET, {
      expiresIn: '1h',
    })

    res.setHeader(
      'Set-Cookie',
      serialize('auth', sessionToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV !== 'development', // Use secure cookies in production
        sameSite: 'strict',
        maxAge: 3600, // Expires after 1 hour
        path: '/',
      }),
    )

    // Redirect the user to the homepage
    res.writeHead(302, { Location: '/secrets' })
    res.end()
  } catch (err) {
    // The token was invalid, return an error
    res.status(401).json({ error: 'Invalid token' })
  }
}
```

The top part of this is similar to the API route, but let’s step through the entire thing for clarity:

- First we’re importing the libraries we need, `jsonwebtoken` again and cookie for session management.
- Within our `handler` function, we’ll get the token from the query of the request.
- With that token as the payload, we’ll use the verify method from `jsonwebtoken` to verify our token. This again uses our `JWT_SECRET` that we signed it with to verify.
- If that’s a valid token, we’ll then create a session for the user, again using the `sign` method from `jsonwebtoken`. We set an expiresIn time of an hour. After that, our user will be logged out.
- We add the token to our cookie using `serialize` from the `cookie` library, then add the cookie to the headers of our result.
- We’ll then redirect the user to a logged-in-only page

That page, `/secrets` doesn’t yet exist. Let’s create it in the `/pages` directory:

```jsx
// pages/secrets.js

import { useEffect, useState } from 'react'
import { Inter } from 'next/font/google'

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

export default function Secrets() {
  const [data, setData] = useState(null)

  useEffect(() => {
    // Fetch data from our API route
    fetch('/api/secure-endpoint')
      .then((res) => {
        // If the response was not ok, throw an error
        if (!res.ok) {
          throw new Error('Failed to fetch')
        }
        return res.json()
      })
      .then((data) => {
        setData(data)
      })
      .catch((err) => {
        console.error('An error occurred: ', err.message)
      })
  }, [])

  // Render data or loading message
  return (
    <main
      className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
    >
      <div className="z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex">
        {data ? (
          <>
            <h1>{data.secret}</h1>
            <h2>{data.email}</h2>
          </>
        ) : (
          <p>This isn&apos;t a secret </p>
        )}
      </div>
    </main>
  )
}
```

This will conditionally render either a data object with the fields `secret` and `email` if the user is authenticated, or the text ‘This isn’t a secret’ if they aren’t. The data object comes from a call to our final api endpoint that we need to create, `secure-endpoint.js`:

```jsx
// pages/api/secure-endpoint.js

import jwt from 'jsonwebtoken'

export default function handler(req, res) {
  const { auth } = req.cookies

  if (!auth) {
    return res.status(401).json({ message: 'Unauthorized' })
  }

  try {
    const { email } = jwt.verify(auth, process.env.JWT_SECRET)

    // Now you have the authenticated user's email
    // Do your secure stuff here...

    res.json({ secret: 'This is a secret!', email })
  } catch (error) {
    res.status(401).json({ message: 'Unauthorized' })
  }
}
```

This authorizes the user by verifying with `verify` from `jsonwebtoken` the session token and, if the user is authenticated, passing back an object with a `secret` (‘This is a secret!’) and an `email`. Now if a user fills in their email and clicks on the link, they’ll be sent to the secret page with the `secret` and their `email` showing:

![image4.png](./ca4c4a91d3010cf0e8f4e308548af46cb6233c91-1999x1264.png)

That's it! With these routes, you now have a basic magic link authentication system.

## Using Clerk for your magic links

The above code works. It’s also a terrible idea.

We aren’t doing any error handling. We aren’t checking for any edge cases. We’re not overly protective of our JWT token. If an attacker learns your JWT secret, they can create valid tokens and impersonate any user. There’s no rate limiting. The cookie storage isn’t ideal.

Plus we have to have our own email server that is configured to send out a lot of transactional emails (which regular email providers don’t really like as they get punished for spam). And, yeah, we know, our login and authenticated pages aren’t exactly winning any design awards.

Basically, there are a number of issues with rolling your own magic links.

That’s why services like Clerk’s [Next.js authentication](/nextjs-authentication) exist – to deal with all of the above and make it much easier for developers to implement strong authentication frameworks such as magic links.

We’ll spin up a new Next.js app:

```bash
npx create-next-app@latest magic-links
```

Again, you’ll get the questions. Clerk is written for the latest developments in Next.js, so you can use the App Router.

After that all we need to install is Clerk:

```bash
npm install @clerk/nextjs
```

If we npm run dev now and go to [http://localhost:3000](http://localhost:3000) we again get a regular Next.js start page:

![image3.png](./4bb435f2579af449d2b3fd21d8e9fef9552cc92f-1999x1264.png)

Before we get to the code, we’ll also want to do some other set up. First, we’ll need our NEXT\_PUBLIC\_CLERK\_PUBLISHABLE\_KEY and our CLERK\_SECRET\_KEY. You can get both of these from your [Dashboard](https://dashboard.clerk.com/last-active?path=api-keys).

Add these to an .env file in the root of your project:

```sh
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = pk_test_xxxx;
CLERK_SECRET_KEY = sk_test_xxxx;
```

We’ll also want to configure our Clerk set up to use magic links. We need to change two settings from the defaults in our dashboard. The first is to choose Email verification link as the authentication factor. To do that, go to **User & Authentication > Email, Phone, Username** in your dashboard:

![CleanShot 2023-06-05 at 15.57.39@2x.png](./540c62b637a385ef8cd77f9cdca5044807fbfe6b-3346x2116.png)

Then go to **Contact information** in that subsection and toggle on **Email address** and check **Email Verification Link**:

![CleanShot 2023-06-05 at 15.56.18 2@2x.png](./cc5d1162222b94bf1c5be87853f1e248bbdfb9d9-3346x2116.png)

Now we can start with the code. The first step is to wrap our entire Next.js application in the `<ClerkProvider>`:

```tsx
// app/layout.tsx
import './globals.css'
import { Inter } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'

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

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={inter.className}>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

This is going to provide all we need to pass session and user information to Clerk for the authentication logic. This step is the same for any authentication pattern you are going to use with Clerk in Next.js.

The next step is to use Clerk to protect pages within our application. To do this, we’ll create a file in the `/src` directory. This will use regular expressions to pattern match against the pages you want to protect. Here, we’re going to protect all our pages:

```javascript
//middleware.ts

import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware()
export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
}
```

Now if you load up [http://localhost:3000](http://localhost:3000), you’ll be redirected to a login/signup page:

![image9.png](./8d71d81b4d43e75cc90dd58eb82a086bbcfac4d8-1999x1264.png)

A user enters their email address here and gets sent a link to click:

![image7.png](./b9be271aeb2174a5c010fa2b21b2cfc74dac7d07-1999x965.png)

Clicking on that link sends them back to [http://localhost:3000](http://localhost:3000), but now they can access the site again:

![image3.png](./4bb435f2579af449d2b3fd21d8e9fef9552cc92f-1999x1264.png)

Much easier (and much better looking signup pages and emails as well!)

## Better security and a user experience with magic links

Magic links can really help you streamline your sign up and sign in processes. They lessen the burden on your users while providing strong security for them and your application.

But implementing them manually puts the burden on your developers. Managing creation and validation of the tokens, sending emails (and maintaining the system to do so), then coding up the right modals, pages, error states, and edge cases is a huge hassle.

It’s worth developers going through and trying this manually just to see how magic links are implemented. But if you are looking for a production-ready option that you can incorporate into your app today, you can use [Clerk](/).

---

# How We Roll – Chapter 3: Multifactor
URL: https://clerk.com/blog/how-we-roll-multifactor.md
Date: 2023-06-02
Category: Company
Description: How We Roll is a deep-dive into how Clerk implements authentication. This chapter is on something you know and something you have: multifactor authentication!

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 3: Multifactor

Multifactor authentication, or MFA, is the process of authenticating users in multiple different ways before granting access to their account.

It goes by many names. Technologists seem to have settled on \_multi\_factor authentication because, well, why limit ourselves to two? But *two* factor authentication is still common and the phrase is used interchangeably.

Within consumer products, though, we've seen the rise of two-*step* *verification:*

![Chapter 3: Multifactor screenshot](./53eba8b30ad84278ea863251368d8d3995ca372d-1458x430.png "Google calls the feature \"2-Step Verification\" or 2SV")

Both word substitutions make the concept a little easier to digest, so Clerk has also adopted this language for `<UserProfile/>` component.

Enough about names, though... How We Roll is for technical details, and naming is the easy part! (Hah.)

Just like [passwords](/blog/how-we-roll-passwords), best practices for multifactor authentication have been learned through a history trial-and-error. Let's see how Clerk handles it all!

## Self-serve, out of the box

Clerk provides multifactor authentication out of the box, which means it requires **zero** additional work. How so? The `<UserProfile/>` component includes a self-serve flow for users to configure multifactor authentication:

Users can choose between one-time passwords sent by SMS (SMS OTP) or time-based one-time passwords (TOTP). Once configured, they also receive backup codes in the event that access to their other factor is lost.

And `<UserProfile/>` works in tandem with `<SignIn/>`, so the next time a user signs in, they will be prompted to authenticate with their second factor. No custom code required!

If preferred, Clerk also provides `useUser()` and `useSignIn()` hooks, which enable developers to build custom flows for configuring multifactor authentication.

## Something you know, something you have

For multifactor authentication, Clerk mandates that each step satisfies a different *factor*, or type of evidence, for authentication. The two factors we offer are:

1. **Something you know –** also called a knowledge factor, is proof that you know something that only you should know, like passwords, security questions, and pins. By extension, it also includes access to another account which is presumed to require a password (like an email account to receive a one-time password, or a Google account).
2. **Something you have –** also called a possession factor, is proof that you are holding a physical device. SMS and time-based one-time passwords are both examples, as are newer strategies like passkeys.

It is critical for Clerk to use one example from each bucket. A one-time password sent by email (email OTP) can be a replacement for a password, but a SMS OTP cannot.

Similarly, if a user with multifactor authentication forgets their password, the "forgot password" flow cannot be used to bypass the second factor. Instead, after the user verifies their email, they will still be asked to provide a possession factor.

If a user with multifactor authentication completely loses access to either factor, Clerk cannot directly help that user regain access to their account. Instead, an application administrator can help facilitate account recovery through the Clerk dashboard.

## Mitigate SIM swaps at the application level or user level

SMS OTP is a controversial topic among security professions, and rightfully so. SMS is susceptible to a social engineering attack called a "SIM swap," where a telephone company is persuaded to issue a SIM card to an attacker for a victim's phone number.

The general consensus is that SMS OTPs add some degree of security, but a targeted attack has a high chance of success. Jack Dorsey [infamously fell victim](https://www.wired.com/story/jack-dorsey-twitter-hacked) to a SIM swap which helped an attacker gain access to his Twitter account.

Many argue that SMS OTP should be discontinued entirely. The challenge is that SMS is ubiquitous and convenient, so users are more likely to configure multifactor when SMS can be used.

At Clerk, we don't believe the answer is not one-size-fits all, so our solution is two-fold:

1. Developers can choose to disable SMS OTP at the application level.
2. Individual users can choose to disable SMS OTP at the user level. Even if a phone number is added to an account, SMS OTP must be explicitly enabled for it to be used as a possession factor.

![Mitigate SIM swaps at the application level or user level screenshot](./3fe377763fc8afcbdee8eee118573562d27bd121-2022x954.png "Clerk's dashboard allows developers to choose if SMS OTP is enabled")

## Summary

Multifactor authentication is an essential security feature that every application should offer, but it is essential that best practices are followed for knowledge and possession factors. Clerk's components and hooks provide those best practices for multifactor authentication out of the box.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# Announcing A New Password Experience
URL: https://clerk.com/blog/a-new-password-experience.md
Date: 2023-05-19
Category: Company
Description: The team has been focused on making a first in class experience for your end users when it comes to passwords. Let's talk about the new features we introduced. 

It's time for an exciting announcement from our end, one that's been brewing for a while. While it may seem like a small step, we believe it's a giant leap towards an unparalleled user experience, especially when it comes to password security.

Our team has been hard at work, making your end users' experience first-in-class. We're thrilled to introduce a suite of new features that will take password management to the next level. Let's dive into the details!

## Password Reset Flow

Who hasn't forgotten a password at least once? Now, with our traditional password reset flow, users can reset their password with a single click. The best part? It ships with our [sign in component](/docs/authentication/sign-in) and syncs perfectly with our [Multi-Factor Authentication (MFA) products](/docs/authentication/custom-flows/multifactor), ensuring all necessary verification steps are complete before users are automatically signed back into your application.

## **Strong Passwords Verification**

In addition to our [HaveIBeenPwned](https://haveibeenpwned.com) integration, we now enable you to set a minimum strength requirement for all new passwords in your application. This added security layer is powered by the password strength estimator [zxcvbn-ts](https://github.com/zxcvbn-ts/zxcvbn).

With its ability to detect commonly used passwords and patterns such as dates, names, and common phrases, zxcvbn-ts ensures that your end users are protected from using weak passwords in your application. Now, stronger password security is not just an option, but a standard.

## **Additional Complexity Requirements**

For our [Business plan](/pricing) users and beyond, we're introducing the ability to enforce specific password requirements like special characters, numbers, and a mix of uppercase and lowercase letters. This will help your users to not just meet, but exceed your standards.

![Clerk dashboard password complexity policy options](./b7ea47b66fa674b5ccf0537b86492ccb73b2ca36-1696x1698.png)

## **Password Completion Guidance**

To take password requirements one step further, once you set password policies in your Clerk dashboard, you can even opt for users to be alerted to your length or complexity requirements via guidance cues. When users are setting their password, friendly messaging will appear below the input box that indicates the password policies you selected to help them create passwords that are robust and compliant.

We're excited about these new additions and can't wait for you to experience them. Here's to a better user experience for all! Want to learn more about "how we roll" passwords? Check out our [post](/blog/how-we-roll-passwords) that goes in depth about passwords at Clerk.

Let us know your thoughts on X [@clerk](https://x.com/clerk) or in the [Clerk Community](https://clerk.com/discord) on Discord.

---

# How We Roll – Chapter 2: Avatars
URL: https://clerk.com/blog/how-we-roll-avatars.md
Date: 2023-05-19
Category: Company
Description: How We Roll is a deep-dive into how Clerk implements authentication. In this chapter, we discuss why avatars should never be an afterthought.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 2: Avatars

We're focusing on avatars early in the series because too many applications treat them as an afterthought. But at the biggest companies in the world – the ones with full-time identity teams working to streamline every aspect of authentication – it's common to see avatars used to "level-up" the user experience.

![Chapter 2: Avatars screenshot](./fbba56b5223a378e591a77fe17f8fa2ee8df4062-3860x2056.png "Google uses avatars within their sign-in flow and as a button throughout their applications.")

At Clerk, we operate like an outsourced identity team, and we *love* bringing details like these to our customers. We obsess over authentication so our customers can focus on what truly differentiates their business.

Let's dive in to the full feature!

## Choosing avatars

There's an unfortunate reality when dealing with avatars: users don't like uploading their avatar directly. Forcing avatar upload during the sign-up flow would dramatically reduce conversion rates.

Thankfully, "social sign-in" providers like Google and Facebook provide access to the user's avatar through the [OpenID](https://en.wikipedia.org/wiki/OpenID) protocol. Moreover, when social sign-in is available, it's preferred by over 50% of users.

As a result, simply enabling *Sign in with Google* leads to most users automatically having their choice of avatar.

Of course, should they choose, users can always modify their avatar through Clerk's `<UserProfile/>` component, or developers can build their own flow with the `useUser()` hook.

### Beautiful default avatars

In the event that users do not provide an avatar, directly or indirectly, Clerk generates beautiful default avatars for every user.

We offers a variety of options, with initials or silhouettes in the foreground, and marbling or a solid color in the background. The marbling effect is built with the fantastic open source project [Boring Avatars](https://boringavatars.com).

## Avatars for the `<UserButton/>`

The most common place avatars are seen today is the top-right of an application. Clicking it opens a menu to manage account settings or sign out.

Clerk provides this functionality through a component called `<UserButton/>`.

![How We Roll Avatars guide illustration](./d67e359f62393528c1ab5bd353aa8f45d2dc5499-1506x880.png)

To better indicate this is a button without modifying the avatar itself, we allow developers to add a "shimmer" effect on mouseover:

## Avatars in the sign-in flow

Like Google, Clerk's `<SignIn/>` component displays the user's avatar during the sign-in flow.

This is primarily seen as a benefit to user experience, but it also has benefits in security. The idea is that users will grow accustomed to seeing their avatar during the sign-in process. If they ever see the wrong avatar, it will raise suspicion of a potential phishing attack.

![How We Roll Avatars guide illustration](./db878c278a10709c17dd3cfdd0f4ca7b0b5cf3a7-1298x1084.png)

## Summary

Though avatars may feel like a small feature, they bring a high degree of polish and professionalism to modern applications. Clerk's social sign-in integrations, beautiful default avatars, and prebuilt components all work together to make avatars easier than ever before.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- [How We Roll – Chapter 10: Roundup](/blog/how-we-roll-roundup)

---

# How We Roll – Chapter 1: Passwords
URL: https://clerk.com/blog/how-we-roll-passwords.md
Date: 2023-05-19
Category: Company
Description: How We Roll is a deep-dive into how Clerk implements authentication. In this first chapter, we discuss passwords – the original form of authentication.

Welcome to How We Roll! This series is meant to help product owners, developers, and security professionals understand exactly how we implement (or, *roll*) authentication at Clerk.

## Chapter 1: Passwords

Many consider passwords to be the "original" form of authentication. They have been used in software since long before the internet existed and are championed for their ease-of-use.

Despite being such a common authentication strategy, implementing passwords is a surprisingly complex task. Decades of trial-and-error have taught us best practices for:

1. Choosing passwords
2. Changing passwords
3. Storing passwords
4. Migrating passwords

Let's learn how Clerk handles it all!

## Choosing passwords

Users can choose passwords in three different scenarios: signing up, changing their password, and resetting their password.

In each scenario, Clerk presents the password field:

This field is designed to provide feedback as the user types, and offers three variants of feedback:

### 1. Password is too short

As with every authentication factor, we first referenced [NIST 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) to determine our password requirements. The National Institute of Standards and Technology (NIST) maintains this document specifically to help authentication professionals maintain the security of digital accounts.

There, we learned that 8 characters is the optimal minimum password length, and so we apply this requirement across every Clerk application.

### 2. Password has been breached

NIST guidance also states that passwords which have been breached should not be used again. Password database leaks are unfortunately all-too-common, and many of those databases store passwords in an unsafe manner.

As a result, billions of passwords have been breached and cannot be used again. To determine whether a password can be used, Clerk leverages the well-maintained corpus from [Have I Been Pwned](https://haveibeenpwned.com).

### 3. Password is too weak

Though it's not suggested by NIST, we implement [zxcvbn](https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/wheeler) to measure password strength as a user types. zxcvbn is a battle-tested complexity estimation algorithm, originally developed within Dropbox.

We allow developers to enforce a minimum level of complexity (low, medium, or high), as determined by zxcvbn.

### Customizing requirements

Within Clerk's dashboard, developers can customize the requirements for users choosing a password:

![How We Roll Passwords guide illustration](./b7ea47b66fa674b5ccf0537b86492ccb73b2ca36-1696x1698.png)

Although not recommended because they frustrate users and because NIST advises against them, this screenshot shows how Clerk also allows LUDS-based password strength requirements (lowercase, uppercase, digits, symbols).

## Changing passwords

When changing or resetting passwords, we help users keep their accounts secure with two requirements:

1. The user must authenticate first, including with multifactor authentication if it is enabled on their account. The helps prevent attackers from claiming an account they discover is signed-in on a public device.
2. The user must be given the option to sign out of other devices. This stops attackers who may already have access to an account.

Here's an example in Clerk's forgot password flow:

## Storage

We use the industry-standard, one-way password hashing function called [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) to hash passwords before they are saved to our database. This is essential to prevent passwords from being leaked, *even in the unlikely event that Clerk's database leaks.*

Counterintuitively, bcrypt adds security by being computationally expensive. Trying to break a bcrypt hash by brute-force is impractical, because it would cost too much money (e.g. trying every possible combination of characters).

Although bcrypt is the standard today, it's important to recognize that the standard changes with some regularity. We monitor those changes and will update our preferred strategy as needed.

## Migrations

Although bcrypt is our preferred hashing algorithm, we support importing passwords that previously used a wide variety of alternative hashing algorithms, like argon2, scrypt, and pbkdf2\_sha256.

These hashes will stay in their original format until the user signs in again with their password. At that time, we will replace the hash to use bcrypt going forward. This ensures that even if passwords are migrated from a weaker format, they are continually upgraded to a modern best-practice.

## Summary

Though simple in concept, implementing passwords for authentication is a complex task. Clerk's components and APIs offer best practices in security and user experience out-of-the-box, so developers can stop worrying about authentication and focus on what truly differentiates their business.

## How We Roll Series Index

- [How We Roll – Chapter 1: Passwords](/blog/how-we-roll-passwords)
- [How We Roll – Chapter 2: Avatars](/blog/how-we-roll-avatars)
- [How We Roll – Chapter 3: Multifactor](/blog/how-we-roll-multifactor)
- [How We Roll – Chapter 4: Email Verification](/blog/how-we-roll-email-verification)
- [How We Roll – Chapter 5: Customization](/blog/how-we-roll-customization)
- [How We Roll – Chapter 6: User Profile](/blog/how-we-roll-user-profile)
- [How We Roll – Chapter 7: JWT Single Sign-On](/blog/how-we-roll-jwt-sso)
- [How We Roll – Chapter 8: Sessions](/blog/how-we-roll-sessions)
- [How We Roll – Chapter 9: Infrastructure](/blog/how-we-roll-infrastructure)
- *How We Roll – Chapter 10: Coming Soon!*

---

# Seamless Integration: How Clerk Streamlined OpusFlow's User Authentication
URL: https://clerk.com/blog/opusflow.md
Date: 2023-05-15
Category: Insights
Description: Learn how Clerk's user-friendly authentication system streamlined OpusFlow's SaaS integration, enhancing productivity and performance.

\_"We were blown away by the user-friendly authentication system that Clerk.com provides, allowing our SaaS to easily integrate with multiple teams of different skill levels. Not only did it offer MFA capabilities and organizations for a more efficient team, but its seamless integration into our tech stack resulted in an unbelievably productive experience! As CTO of OpusFlow, I'm extremely pleased with the 'just work' concept delivered via Clerk."

– **Joey Teunissen, CTO OpusFlow**\_

## About OpusFlow

[OpusFlow](https://opusflow.io) is the perfect ERP system for businesses dealing with installations, like solar panel systems, heat pumps and charging stations. OpusFlow's mission is to streamline efficiency within installation companies through automation of time-consuming processes. With this one-stop solution at your fingertips, you can rest assured that tedious manual tasks are no longer taking up precious hours in your day!

## **Tech Stack**

We have crafted an innovative tech stack to ensure the smooth operation of our platform. On the frontend, Next.js is utilized while PostgreSQL and Node.js run on a serverless system with Typescript and GraphQL compatibility. Hosting solutions are provided by Netlify in combination with AWS services for maximum performance and scalability.

Frontend: Next.js\
Backend: Node.js, Typescript, GraphQL, PostgreSQL, Serverless\
Hosting: Netlify, AWS

## **Before Clerk**

After many attempts at various authentication methods for our projects – from custom-made to Auth0 and others – OpusFlow soon realized the complexity that is often associated with these approaches. To simplify this process, we conducted extensive research until Clerk.com, a developer-friendly authentication platform, was discovered. After conducting a Proof of Concept test to ascertain its performance and reliability, Clerk became our obvious choice for internal projects. We quickly made the switch! Following the successful use of these projects, we decided to take it one step further and implemented Clerk into our largest enterprise SaaS ERP system yet.

## **After Clerk**

After using Clerk for an extended period, with numerous groups that consisted of anywhere from four to ten developers at a time, we are thrilled about the possibilities in store. It is already sufficient enough for our needs as it stands now and its support system provided by Clerk.com is remarkable – it's far more than mere assistance. The shared eagerness between both parties to continue progressing their platform further has been amazing!

---

# Stable Support for the Next.js App Router, plus a Middleware Update!
URL: https://clerk.com/blog/nextjs-13-4.md
Date: 2023-05-04
Category: Engineering
Description: App Router support is out of beta, plus we've launched a major middleware update.

## @clerk/nextjs\@4.17.0 is released

Clerk has updated our SDK to support Next.js version 13.4.0. We've added stable support for the App Router, which is the default for new applications. We've also improved the ergonomics of our middleware helper for more composability and to mitigate common pitfalls.

[See the updates in our new Next.js documentation](/docs/nextjs/get-started-with-nextjs), or read on for more details.

### Stable App Router Support

Next.js version 13.4.0 launched today and the App Router is now stable. In turn, Clerk's support for the App Router is also now stable in `@clerk/nextjs` version 4.17.0.

If you previously adopted `@clerk/nextjs/app-beta`, there have been very few changes:

- The `auth()` and `currentUser()` helpers have been moved to a permanent home in `@clerk/nextjs`
- React imports from `@clerk/nextjs/app-beta/client` are no longer necessary, and you can import from `@clerk/nextjs` intstead. The package now automatically detects if components are being used from a client component or a server component, and loads the proper form.

That's all! Our documentation has been updated to [reflect the changes](/docs/nextjs/get-started-with-nextjs).

### Middleware Update

Along with today's launch, we've also released a new middleware helper to make things a little easier and more composable.

In its simplest form, these few lines are all you need to [integrate Clerk](/nextjs-authentication):

```typescript
import { authMiddleware } from '@clerk/nextjs'

export default authMiddleware()

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

This sets your application in a protected mode – if users are signed out the will be automatically redirected to a sign in. Unprotected pages need to be explicitly listed in the `publicRoutes` argument:

```typescript
export default authMiddleware({
  // Make the homepage accessible while signed out
  publicRoutes: ['/'],
})
```

This new helper also has `beforeAuth` and `afterAuth` arguments to enable more composability with libraries like `next-intl`. For more details, checkout the updated [middleware documentation](/docs/nextjs/middleware).

---

# Generating and Using UUIDs in React
URL: https://clerk.com/blog/generating-and-using-uuids-in-react.md
Date: 2023-04-20
Category: Guides
Description: Learn the significance of UUIDs in full-stack apps, their optimal usage, and how to implement them in React apps.

When working with full-stack apps, you will often come across identifiers. Also known as IDs, identifiers are used to identify data records related to apps. Based on the sensitivity and scope of the data in question, the identifier can be set as locally or globally unique. A popular globally unique identifier is UUID, which stands for *universally unique identifier*.

In this article, you will learn what UUIDs are, when to use them, and how to get started with implementing them in your React app.

## What Are UUIDs and When Are They Used?

UUIDs are a popular and safe method of generating globally unique identifiers for data records. It's said that there's a [one-in-a-billion chance of two randomly generated UUIDs matching exactly](https://en.wikipedia.org/wiki/Universally_unique_identifier#:~:text=Thus%2C%20the%20probability%20to%20find,is%20one%20in%20a%20billion) in a set of 103 trillion UUIDs, which is small enough to say that UUIDs are practically unique.

UUIDs are also better than sequential identifiers when it comes to parallel data insertion in databases as you don't need to adhere to a sequence to create keys and insert records.

A UUID is formatted like this: `2a6db6e1-8967-4511-9839-a7cb3c895710`.

There are a total of thirty-two hexadecimal characters separated into sets of 8-4-4-4-12 characters, which are themselves separated by hyphens. However, these thirty-two characters cannot be randomly generated and put together to create a UUID. In order for an ID to be universally unique—and therefore be called a UUID—it needs to be [compliant with the RFC 4122 protocol](https://www.cryptosys.net/pki/uuid-rfc4122.html).

UUIDs have multiple use cases, the most prominent of which include the following:

- To identify records uniquely across tables
- When two tables are merged, as UUIDs remain unique to avoid confusion
- When parallel insertion is needed, as sequential IDs can be created in parallel

A number of third-party libraries and functions are available to make it simpler for developers to implement UUIDs in apps. In the following section, you'll see multiple ways to implement UUIDs in a React app.

## Generating UUIDs in Your React Application

Now that you understand what UUIDs are (and when you might use them), let's move on to how you can implement them in your React apps. First, you will create a boilerplate form, then learn how to set up a UUID on that form in four different ways.

Each of these methods will use the same protocol to generate UUIDs that are globally unique. Which one you use, however, will depend on your use case and preferences.

The first two will require external dependencies that may make developer experience better but add a performance overhead and/or weight to the app size. The third method will use an inbuilt function from the `crypto` package (which may or may not be available depending on the JavaScript environment), and the last method will use a raw algorithm to create UUIDs manually in app, adding no weight or performance overhead to your app but requiring manual setup and maintenance.

You can find the complete source code of the demo app in this [GitHub repo](https://github.com/krharsh17/react-uuid-demo).

### Prerequisites

To proceed with this tutorial, you need to have [npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) on your system.

Next, you'll need to set up a new React project. Run the following command:

```sh
npx create-react-app react-uuid-demo
```

Once the new React project is created, run the following commands to start the development server:

```sh
cd react-uuid-demo
npm start
```

This is what the output should look like.

![Generating And Using Uuids In React guide illustration](./cb0f1846dd3b84dbe27c108ff7ed6c7420992d7a-2000x1310.png)

You will install the dependencies as required later on.

### Create a Simple Page with User Information

To ease your familiarity with the concepts, you'll create a basic boilerplate form that asks for a username and email and allows users to generate a UUID for themselves. Here's what the form will look like.

![Generating And Using Uuids In React guide illustration](./5878f82d4a51af6fbbd163e2b42d20f13fd7b8fc-2000x1089.png)

The **Generate UUID** button will be enabled once the user fills in the form data.

To set it up, you'll need to paste the following code snippet into your **App.js** file:

```jsx
import './App.css'
import { useState } from 'react'

export default function App() {
  // define state containers
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')
  const [id, setId] = useState('')

  // Define listener for button click event. You will use this function to generate UUIDs later
  const onGenerateButtonClick = () => {
    console.log('Button clicked')
  }

  return (
    <div className={'mainContainer'}>
      <div className={'titleContainer'}>
        <div>Create your profile</div>
      </div>

      {/* Name input field */}
      <div className={'inputContainer'}>
        <input
          value={name}
          placeholder="Enter your full name"
          onChange={(ev) => setName(ev.target.value)}
          className={'inputBox'}
        />
        <br />
      </div>

      {/* Email input field */}
      <div className={'inputContainer'}>
        <input
          value={email}
          placeholder="Enter your email"
          onChange={(ev) => setEmail(ev.target.value)}
          className={'inputBox'}
        />
        <br />
      </div>

      {/* Button to generate UUIDs */}
      <div className={'buttonContainer'}>
        <input
          type={'button'}
          disabled={!(name !== '' && email !== '')}
          value={'Generate UUID'}
          onClick={onGenerateButtonClick}
          className={'inputBox'}
        />
        <br />
      </div>

      {/* UUID box */}
      <div className={'inputContainer'}>
        <input
          value={id}
          placeholder="UUID"
          disabled={true}
          onChange={(ev) => setId(ev.target.value)}
          className={'inputBox'}
        />
        <br />
      </div>
    </div>
  )
}
```

To add some basic styling so that the form looks like the one shown in the image above, you'll need to paste the following code snippet into your `App.css` file:

```css
.mainContainer {
  flex-direction: column;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

.titleContainer {
  display: flex;
  flex-direction: column;
  font-size: 64px;
  margin-bottom: 32px;
  font-weight: bolder;
  align-items: center;
  justify-content: center;
}

.inputContainer {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.buttonContainer {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 40px;
}

.inputBox {
  height: 48px;
  width: 400px;
  font-size: large;
  border-radius: 8px;
  border: 1px solid grey;
  padding-left: 8px;
}
```

You'll also need to update the styles in `index.css` with the following code to complete the styling of the app:

```css
html,
body {
  padding: 0;
  margin: 0;
  font-family:
    -apple-system,
    BlinkMacSystemFont,
    Segoe UI,
    Roboto,
    Oxygen,
    Ubuntu,
    Cantarell,
    Fira Sans,
    Droid Sans,
    Helvetica Neue,
    sans-serif;
}

* {
  box-sizing: border-box;
}

main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

code {
  background: #fafafa;
  border-radius: 5px;
  padding: 0.75rem;
  font-family:
    Menlo,
    Monaco,
    Lucida Console,
    Courier New,
    monospace;
}

input[type='button'] {
  border: none;
  background: cornflowerblue;
  color: white;
  padding: 4px 12px;
  border-radius: 12px;
  cursor: pointer;
}

input[type='button']:disabled,
input[type='button'][disabled] {
  border: 1px solid #999999;
  background-color: #cccccc;
  color: #666666;
}
```

If you run the app now, it should look like the one in the image shared above.

### Create UUIDs Using Different Methods

Now that your base app is ready, you'll see how to create UUIDs using `uuidv4`, `react-uuid`, `crypto.randomUUID()`, and finally, through code.

### Using uuidv4

The first and quite popular method of generating UUIDs in JavaScript-based apps is with the [uuid](https://www.npmjs.com/package/uuid) npm package. You can install it in your React app by running the following command:

```sh
npm i uuid
```

You'll need to import it into your app by adding the following line of code below the existing imports in your `App.js` file:

```jsx
import { v4 as uuid } from 'uuid'
```

You can now use the following function to generate UUIDs using this package:

```jsx
const uuidFromUuidV4 = () => {
  const newUuid = uuid()
  setId(newUuid)
}
```

*Note:* You'll need to paste this function in your `App` component right below the `onGenerateButtonClick()` function.

Finally, you'll need to replace the `onGenerateButtonClick` function in your `App` component with the code below to call this method when the **Generate UUID** button is clicked:

```jsx
const onGenerateButtonClick = () => {
  uuidFromUuidV4()
}
```

Once you've done this, you can see it in action by going to `http://localhost:3000` and filling out the form.

### Using react-uuid

Another popular method for React apps to implement UUID is by using the npm package [react-uuid](https://www.npmjs.com/package/react-uuid). Both the `react-uuid` and `uuidv4` packages can be implemented seamlessly in React; their main difference is that `react-uuid` was designed specifically for React apps while `uuidv4` was primarily meant for Node.js apps.

You can install the package in your project by running the following command:

```sh
npm i react-uuid
```

Next, you'll need to import the package in your source code by adding the following import line at the top of your `App.js `file (below the existing imports):

```jsx
import uuid from 'react-uuid'
```

You can now use the following function to generate UUIDs using this package:

```jsx
const uuidFromReactUUID = () => {
  const newUuid = uuid()
  setId(newUuid)
}
```

*Note:* You'll need to paste this function in your `App` component.

To call this function when **Generate UUID** is clicked, you'll need to update the code for the `onGenerateButtonClick` function with the following:

```jsx
const onGenerateButtonClick = () => {
  uuidFromReactUUID()
}
```

Once you're done with the steps above, you can see this function in action.

### Using crypto.randomUUID()

The inbuilt `crypto` package in JavaScript runtimes can be used to generate UUIDs. Here's a function that uses `crypto.randomUUID()` to generate a UUID:

```jsx
const uuidFromCrypto = () => {
  const newUuid = crypto.randomUUID()
  setId(newUuid)
}
```

You don't need to include any imports for this to work. However, this method only works over secure contexts ([local or HTTPS](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API)). Here's how you can update your `onGenerateButtonClick` function to use this method in your app:

```jsx
const onGenerateButtonClick = () => {
  uuidFromCrypto()
}
```

The result is similar to the other methods seen so far.

### Through Code

Perhaps you prefer not to install any third-party libraries to implement UUID and the `crypto.randomUUID()` doesn't suit your use case. If so, you can use a method that relies on another function from the `crypto` package to generate RFC 4122–compliant UUIDs in your source code:

```javascript
const uuidFromCode = () => {
  const newUuid = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
    (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16),
  )
  setId(newUuid)
}
```

Based on a [Stack Overflow answer](https://stackoverflow.com/a/2117523), this method fits most UUID-related use cases perfectly. Since it uses `crypto.getRandomValues()` instead of `Math.random()`, you can rest assured that the UUIDs generated using this method will be practically globally unique.

Here's how your `onGenerateButtonClick` function should look like using this method:

```javascript
const onGenerateButtonClick = () => {
  uuidFromCode()
}
```

The result will again look similar to the methods discussed above. Once you've filled in the form, you can keep clicking **Generate UUID** in order to generate new UUIDs.

As mentioned, you can find the complete source code of the demo app used in the tutorial [here](https://github.com/krharsh17/react-uuid-demo).

## Conclusion

UUIDs are handy when it comes to identifying records uniquely across tables globally. With an infinitesimal chance of collision, UUIDs present you with a robust solution to the global identification problem. In this article, you saw four different methods of implementing a UUID in your React app.

UUIDs are used in many third-party tools internally too. It's essential that the third-party dependencies you include in your app create UUIDs reliably to ensure the proper functioning of your app.

[Clerk](/) is an auth provider that makes it easy to add authentication and user management to your application. As for its data, Clerk uses [K-Sortable Globally Unique IDs, or KSUIDs,](https://github.com/segmentio/ksuid) for generating unique identifiers. These extend UUIDs to add time-based ordering and friendlier representation formats for simplicity. If you're looking for an auth solution for your app, make sure to [check out Clerk](https://dashboard.clerk.com/sign-up)!

---

# Setting and Using Cookies in React
URL: https://clerk.com/blog/setting-and-using-cookies-in-react.md
Date: 2023-04-14
Category: Guides
Description: Learn how to set up cookies in React with this guide! You'll create a login page and store user information using cookies.

Web cookies consist of data generated by a server and sent to a user's web browser (such as Chrome or Firefox). These small pieces of information are then stored by the website on the user's device. Cookies can be used to keep track of things like items in a shopping cart. If the user leaves the website and comes back later, the items will still be there waiting for them.

They can also be used for security purposes, such as user authentication, and for tracking a user's behavior, which allows websites to personalize the user's experience. For instance, if a user logs out of your site, they can simply log back in without needing to enter their username and password again. It's important to note, however, that cookies can be stolen by hackers who can use them to impersonate the user on your website.

In this article, you'll explore how to set up cookies in a ReactJS application. You'll create a [simple login page](/blog/building-a-react-login-page-template) and use cookies to store information about the user's logged-in session.

> For secure cookie management and session handling, [learn more about our React support](/react-authentication).

## What Are Web Cookies?

As mentioned, web cookies are small pieces of data that are stored on a user's computer by a website. They're typically used to store information about the user, such as their preferences, login information, and other data. Cookies are created and stored by the website when the user visits the site. The website can then retrieve the cookies from the user's computer when they visit the site again, allowing the website to "remember" the user and their preferences.

Cookies are typically stored in plain text, which means that they can be read and modified by anyone who has access to the user's computer. Therefore, it's important to use cookies carefully and securely to protect the user's data.

Before you start the tutorial below, make sure you have [npm and Node.js installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) on your machine. Once this is done, you can begin working on your React application.

## Set Up Your React Application

Open a terminal and create a new React application using the following command: `npx create-react-app my-app`. Once the application is created, open the directory with your favorite code editor (such as VS Code) and start working on your application. You'll see a bunch of files and folders that look like the image below.

![Setting And Using Cookies In React guide illustration](./d8c90582f6e552dc9d106a689e443d62fa2ad78a-2000x1140.png)

To test that your React application is properly set up, open a terminal and navigate to the `my-app` project folder. Start the server by running `npm start`; open your web browser and enter the specified port in the terminal (`3000`) to view your React application.

By completing these steps, you can verify that your React application is running as expected. You can then proceed with writing code and building your application without any issues. You should see a web page like the image below.

![Setting And Using Cookies In React guide illustration](./8670f2974e4e75421a43abce7a396b50109abe56-2000x917.png)

## Create a Simple Welcome Page

In your React project, create a new file called `WelcomePage.js` inside the `src` folder and add the following code inside:

```jsx
import React from 'react'

export default function WelcomePage() {
  return <div>WelcomePage</div>
}
```

To render the welcome page, first navigate to the `App.js` file inside the `src` folder. Import the `WelcomePage.js` file using the following code:

```jsx
import WelcomePage from './WelcomePage.js'
```

Then, inside the return statement, remove everything and add this code:

```jsx
<WelcomePage />
```

In the above instructions, you imported the `WelcomePage.js` file and used the `<WelcomePage />` tag inside the return statement of the `App.js` file. This allows the `App.js` file to render the welcome page in a web browser. When the page is rendered, you should see a white page with the word "WelcomePage" on it.

## Create a Login Page

Create another file inside the src folder called `LoginPage.js` and add the following block of code:

```jsx
import React, { useState } from 'react'

function LoginPage({ onLogin }) {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')

  function handleSubmit(event) {
    event.preventDefault()
    onLogin({ username, password })
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
      </label>
      <br />
      <label>
        Password:
        <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
      </label>
      <br />
      <input type="submit" value="Submit" />
    </form>
  )
}

export default LoginPage
```

This `LoginPage` component has fields for a username and a password as well as a submit button. When the user fills out the form and clicks Submit, the `onLogin` callback function is called with their username and password. The parent component (`App` in this case) can then use this information to set the user cookie and render the `WelcomePage` component.

## Set Up Cookie Handling

To use cookies in your app, you'll need to import the react-cookie library from the React library. This library allows you to set, get, and delete cookies in your app.

First, install the library by running the command `npm install react-cookie` in the root folder of your app in the terminal.

Now change your **App.js** file to match the following code:

```jsx
import React from 'react'
import WelcomePage from './WelcomePage.js'
import LoginPage from './LoginPage.js'
import { CookiesProvider, useCookies } from 'react-cookie'

function App() {
  const [cookies, setCookie] = useCookies(['user'])

  function handleLogin(user) {
    setCookie('user', user, { path: '/' })
  }

  return (
    <CookiesProvider>
      <div>
        {cookies.user ? <WelcomePage user={cookies.user} /> : <LoginPage onLogin={handleLogin} />}
      </div>
    </CookiesProvider>
  )
}

export default App
```

In the code above, the `App` component uses the `useCookies` hook to manage cookies. The `handleLogin` function is called when a user logs in, and it sets a cookie named "user" with the user's information.

The `LoginPage` and `WelcomePage` components are wrapped in the `CookiesProvider` component, which provides a global context for cookies. This allows the `WelcomePage` component to access the user cookie and display the user's information.

The `App` component uses a ternary operator to decide which component to render based on the presence of the user cookie. If the cookie exists, the `WelcomePage` component is rendered. If the cookie does not exist, the `LoginPage` component is rendered instead.

Now, if you check your page on your browser, you should see a login form that looks like the image below.

![Setting And Using Cookies In React guide illustration](./c4cf9f020742bf3b2413a6b041d8a5f11e5ff057-2000x929.png)

Before you test your application, first change the `WelcomePage.js` file to match the following block of code:

```jsx
import React from 'react'

function WelcomePage({ user }) {
  return <h1>Welcome, {user.username}!</h1>
}

export default WelcomePage
```

This `WelcomePage` component simply displays a welcome message with the user's username. It receives the user object as a prop, which contains the user's username and password. The parent component (`App` in this case) passes the user object to the `WelcomePage` component when `WelcomePage` is rendered.

## Test Your Application

To test your application, go to the browser and fill in the login form with your username and password. When you click **Submit**, the welcome page should be displayed with the words "Welcome" followed by your username. This indicates that the login was successful and the `WelcomePage` component was rendered. It should look like the image below.

![Setting And Using Cookies In React guide illustration](./d5d11e71535fcdefecf7fc0c2a5e362a4cbfcade-2000x654.png)

To check if a cookie has been set in your Chrome browser, you can use the developer tools. Open your browser, and if you're on Chrome, go to the page where the cookie is set by pressing F12 on your keyboard to open the developer tools.

Click on the **Application** tab in the developer tools. In the left panel, expand the **Cookies** section and click on the domain where the cookie is set—in this case, it's `http://localhost:3000`. In the right panel, you should see a cookie that has been set for the domain like in the image above.

You can find the complete code for this tutorial on [GitHub](https://github.com/gitnyasha/using-cookies-in-react).

## Conclusion

In this tutorial, you learned how to create a login page and a welcome page in a React app and how to store the user's login information in a cookie. You also learned how to only display the welcome page if the user is logged in and how to check if a cookie has been set in the Chrome browser.

Using cookies in a React app can make the login process more user-friendly as it allows users to access the welcome page without having to enter their login information every time they visit the page.

Clerk provides an easy way to add authentication and user management to your application. Clerk handles session management, including setting cookies on your behalf, saving you time and effort. This allows you to focus on building the features of your app without having to worry about implementing the authentication and session management features from scratch. [Give Clerk a try by signing up today](https://dashboard.clerk.com/sign-up).

---

# Adding JWT Authentication to React
URL: https://clerk.com/blog/adding-jwt-authentication-to-react.md
Date: 2023-04-14
Category: Guides
Description: Learn how to implement JSON Web Token (JWT) authentication in a React app using a standard flow, and how Clerk can make the process even easier.

JSON Web Token (JWT) authentication is a method of securely authenticating users and allowing them to access protected resources on a website or application. It's a popular and widely used method of web authentication as it allows for easy and secure user authentication without the need for the server to maintain a session state.

In this process, the server generates a signed JWT and sends it to the client. The client then includes this token in subsequent requests to the server to authenticate themselves. The JWT is usually stored in the browser's localStorage and sent as part of the request's headers.

However, the JWT mechanism can be arduous and error-prone, especially if you're building it from scratch. In this article, you'll learn how to implement JWT in a React application using a standard flow, and then you'll see how much easier it gets when repeating the exercise using [Clerk's React authentication](/react-authentication).

## What Is JWT Authentication?

Before we discuss how a user is authenticated with JWT, let's take a closer look at what it contains:

1. `The header:` consists of two parts—the token type, which is JWT, and a signing algorithm, such as HMAC-SHA256 or RSA
2. `The payload:` contains the claims—in other words, the statements about an entity (typically, the user) and additional data
3. `The signature:` used to verify the JWT's integrity

To authenticate a client using JWT, the server first generates a signed JWT and sends it to the client. The client then includes the JWT in the header (usually the [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)) of subsequent requests to the server.

The server then decodes the JWT and verifies the signature to ensure that a trusted party sent it. If the signature is valid, the server can then use the information contained in the JWT to authenticate the client and authorize their access to specific resources. The diagram below shows a standard JWT authentication flow.

![JWT authentication flow](./ce92a5f67e79db690f9dffa38d625decebf8da3e-2025x1178.png)

### Advantages and Downsides of Using JWT

Using JWT authentication offers the following advantages:

1. `JWT authentication is stateless:` A JWT contains all the information regarding the user's identity and authentication, including the claims. This can be more efficient than storing session information on the server as it reduces the amount of data that needs to be stored and retrieved for each request.

2. `Create anywhere:` Another advantage of JWT authentication is that the token can be generated from anywhere, including external services or third-party applications. This allows for flexibility in terms of where and how the token is generated, which can be useful in a microservices architecture where different services may need to authenticate users.

3. `Fine-grained access control:` JWT can contain information about the user's role and permissions in the form of claims. This gives the application developers a lot of control over what actions a user is allowed to take.

However, there are also some disadvantages to using JWT authentication:

1. `Hard to invalidate:` Invalidating JWTs is only possible if you maintain a list on a shared database, which introduces additional overhead. The database is necessary because if you need to revoke a token or if a user's permissions change, the server won't otherwise be able to determine the status of the token and might give access when it shouldn't. If the JWTs you're using are long-lived—in other words, they have a very long (or no) expiration time specified—it becomes even more important that they're stored in an accessible database.

2. `Size and security concerns:` JWTs can sometimes contain unnecessary information that might be useless for the application and, at the same time, make the token larger and more cumbersome to work with. If the JWT is unencrypted, it can also end up revealing too much about the user.

Given these challenges, some would say that using cookies over JWT works better in some instances as a method of authentication; for example, when the application needs to keep track of the user's activity across multiple pages, as cookies can be easily read and written on the server side. Let's compare the two in detail.

### Are Cookies Better Than JWT?

To start with, you can create session-based cookies, which automatically expire after the user session is closed, or you can easily set an expiration time for a cookie, which gives more control over session invalidation. You can also use HttpOnly cookies to prevent JavaScript from accessing the cookie information.

However, it's important to note that cookies come with their own flaws. Specifically, as the cookie data is stored on the server and the cookie identifier is stored on the client, they're not entirely stateless like JWTs. This means that the server needs to store and retrieve the cookie data for each request, which would be additional overhead to the authentication process and slow down the application's performance, especially if the number of concurrent users increases.

They're also not ideal for non-browser-based applications, such as mobile and desktop applications. Additionally, cookies can be more vulnerable to certain attacks, such as [cross-site scripting (XSS)](https://owasp.org/www-community/attacks/xss) and [cross-site request forgery (CSRF)](https://owasp.org/www-community/attacks/csrf).

Now that we've covered the advantages and some of the potential challenges of JWT authentication, let's see the process in action. In the following section, you'll see how to implement JWT authentication in your React application.

## Implementing JWT Authentication in Your React Application

In this tutorial, you'll build a simple full-stack application with [authentication in Next.js](/nextjs-authentication). Next.js allows you to implement frontend applications using React and a backend API server without setting up another Node.js project. You'll also understand the pitfalls of creating a JWT authentication from scratch and learn to overcome those limitations using the Clerk SDK.

> For pure React applications (without Next.js), check out our [React authentication solution](/react-authentication) which handles JWT and other auth methods seamlessly.

The application stores the key to the user's safehouse (a protected resource) and uses JWT authentication to verify their identity. The application shows the user a welcome page, where they can sign in with a username and password. It generates a JWT for the user, which they can use to verify their identity. Once signed in, users will see their safehouse's secret key by exchanging the JWT with the server.

Before you begin, you'll need a code editor like [Visual Studio Code](https://code.visualstudio.com/download). You'll also need Node.js (version 16 or newer) and npm installed. If you want to check out the completed application, you can clone this [GitHub repository](https://github.com/Anshuman71/clerk-jwt-example).

### Setting Up the Project

To set up a Next.js project, run the following command:

```bash
npx create-next-app clerk-jwt-example
```

You'll be prompted on whether you'd like to use TypeScript and ESLint. For simplicity, choose `No` for TypeScript and then `Yes` for ESLint.

After you complete the npm installation, open the project in your code editor and change the directory to the project by running `cd clerk-jwt-example` in your terminal.

To use the browser's default styling, remove all existing styles from `styles/globals.css` and `styles/Home.module.css`.

### JWT Authentication Using a Standard Flow

In this example, you'll create two pages: `/jwt-home` and `jwt-safehouse`. The former will be the [login page](/blog/building-a-react-login-page-template) to collect credentials, and the latter will be the secured page showing secret information.

More specifically, the `/jwt-home` page accepts the user credentials and requests the `/api/auth` API endpoint to generate the signed JWT. The application stores the returned JWT in localStorage as the `jwt-token` key. The `/jwt-safehouse` acts as the secured page and requests the secret information from the `/api/safehouse` API endpoint in exchange for the signed JWT. The `/jwt-safehouse` page then displays the secret information to the signed-in user.

In Next.js, you can create a new application route by creating a new file with the route name under the `pages/` folder. Similarly, to create a new API endpoint, you need to create a new file under the `pages/api/` folder.

#### Update the Application Home Page

To access different parts of your application, update the application home page (`pages/index.js`) to show links to other pages in the application. The code below uses the `Link` component from `next/link`, which is the Next.js version of the `<a>` tag:

```jsx
import Link from 'next/link'

export default function Home() {
  return (
    <div>
      Home
      <br />
      <ol>
        <li>
          <Link href={'/jwt-home'}>JWT Home</Link>
        </li>
        <li>
          <Link href={'/jwt-safehouse'}>JWT Safe house</Link>
        </li>
      </ol>
    </div>
  )
}
```

Now start the application by running `npm run dev` in the terminal. Open `http://localhost:3000` in a web browser to see the application. You'll see the page, as shown below.

![Initial layout](./16fed00772a7f2c1491216696a6e9303ee7a7121-1606x900.png)

#### Create a Signed JWT

To create a signed JWT, you first need to install the `jsonwebtoken` package. `jsonwebtoken` provides utilities to sign and verify JWTs. Run `npm i jsonwebtoken` to install the package in your project.

You'll need a JWT signing secret to use with `jsonwebtoken`. For this, create a new file, `.env.local`, to store the application's secret credentials. In this file, add a new environment variable, `DIY_JWT_SECRET`, with a random hash string as a value:

```txt
DIY_JWT_SECRET=2182312c81187ab82bbe053df6b7aa55
```

To generate the signed JWT with the user's `signInTime` and `username`, create an API route `/api/auth` by creating the new file `pages/api/auth.js`. The API route accepts the user credentials, and if the provided password is `pikachu`, it returns a `200` response with the signed JWT. Otherwise, it returns a `401` response with an error message:

```js
import jwt from 'jsonwebtoken'

export default function handler(req, res) {
  const jwtSecretKey = process.env.DIY_JWT_SECRET
  const { username, password } = req.body
  // confirm if password is valid
  if (password !== 'pikachu') {
    return res.status(401).json({ message: 'Invalid password' })
  }
  let data = {
    signInTime: Date.now(),
    username,
  }

  const token = jwt.sign(data, jwtSecretKey)
  res.status(200).json({ message: 'success', token })
}
```

#### Create a Login with JWT

Now that the `/api/auth` API endpoint is ready, create the new file `pages/jwt-home.jsx` and implement a login form component to send user credentials to `/api/auth`.

The code below implements a React component, `Home`, that displays a form to collect the user credentials and, on form submission, makes an HTTP POST request to the `/api/auth` endpoint with the collected credentials.

If the response message is `success`, it saves the received JWT in localStorage under the `jwt-token` key. Otherwise, it shows a browser alert with the response message:

```jsx
import { useState } from 'react'
import { useRouter } from 'next/router'

export default function Home() {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const router = useRouter()

  function submitUser(event) {
    event.preventDefault()
    fetch('/api/auth', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify({ username, password }),
    })
      .then((res) => res.json())
      .then((data) => {
        if (data.message === 'success') {
          localStorage.setItem('jwt-token', data.token)
          setUsername('')
          setPassword('')
          router.push('/jwt-safehouse')
        } else {
          alert(data.message)
        }
      })
  }
  return (
    <>
      <main style={{ padding: '50px' }}>
        <h1>Login </h1>
        <br />

        <form onSubmit={submitUser}>
          <input
            value={username}
            type="text"
            placeholder="Username"
            onChange={(e) => setUsername(e.target.value)}
          />
          <br />
          <br />

          <input
            value={password}
            type="password"
            placeholder="Password"
            onChange={(e) => setPassword(e.target.value)}
          />
          <br />
          <br />

          <button type="submit">Login</button>
        </form>
      </main>
    </>
  )
}
```

![JWT flow login page](./63ecc4b12a117163fbd6f89f7b2eaba5c5cd44e9-1604x900.png)

#### Exchange the JWT for the Secret Data

The next step is to implement an API endpoint to verify the JWT from the incoming request header. If it's valid, the endpoint should return a `200` response with the secret data; otherwise, it will return a `401` response with an error message.

Create a new file, `pages/api/safehouse.js`. In this file, copy and paste the following code to verify the incoming JWT from the `jwt-token` request header:

```js
import jwt from 'jsonwebtoken'

export default function handler(req, res) {
  const tokenHeaderKey = 'jwt-token'
  const jwtSecretKey = process.env.DIY_JWT_SECRET
  const token = req.headers[tokenHeaderKey]
  try {
    const verified = jwt.verify(token, jwtSecretKey)
    if (verified) {
      return res.status(200).json({ safehouseKey: 'under-the-doormat', message: 'success' })
    } else {
      // Access Denied
      return res.status(401).json({ message: 'error' })
    }
  } catch (error) {
    // Access Denied
    return res.status(401).json({ message: 'error' })
  }
}
```

#### Display the Safehouse Secret Data

The final step in the flow is to request the secret data from the `/api/safehouse` API endpoint and display it if the JWT is valid.

To show the secret safehouse data, create the new file `pages/jwt-safehouse.jsx` with the following code:

```jsx
import { useEffect, useState } from 'react'
import Link from 'next/link'

export default function SafeHouse() {
  const [token, setToken] = useState('')
  const [userData, setUserData] = useState({})

  useEffect(() => {
    const token = localStorage.getItem('jwt-token')
    setToken(token)
    fetch('/api/safehouse', {
      headers: {
        'jwt-token': token,
      },
    })
      .then((res) => res.json())
      .then((data) => setUserData(data))
  }, [])

  function logout() {
    setToken('')
    localStorage.removeItem('jwt-token')
  }

  if (!token) {
    return (
      <>
        <main style={{ padding: '50px' }}>
          <p>You&apos;re not logged in.</p>
          <Link href={'/jwt-home'}>Home</Link>
        </main>
      </>
    )
  }

  return (
    <>
      <main style={{ padding: '50px' }}>
        <h1>Safehouse </h1>
        <p>
          You Safehouse key is <strong>{userData?.safehouseKey || 'Loading...'}</strong>
        </p>
        <button onClick={logout}>Logout</button>
      </main>
    </>
  )
}
```

The above code implements a `SafeHouse` component that renders the secret data if the JWT is available in localStorage. Otherwise, it prompts the user to log in with a link to the `/jwt-home` page.

The component gets the `jwt-token` from localStorage and makes a `fetch` request to the `/api/safehouse` in the `useEffect` hook that runs on the initial render in the browser.

The `Logout` button triggers the `logout()` function that clears the `token` state variable and removes the localStorage item.

The standard JWT authentication flow is ready.

Note that the solution you implemented above is very naive for a number of reasons. First, to make this system work, you'll need to implement and maintain additional code to track any updates to the JWT and pass the JWT in the request headers.

Next, you can't invalidate the stored JWT from outside the user's browser, which is a critical security issue—if a user's account is suspended or deleted, a JWT issued before that action would still be valid and could be used to authenticate as that user.

Further, if a user's password is changed, a JWT that was issued before the password change would still be valid and could be used to authenticate as the user with the old password.

### Authentication Using Clerk

By using the [Clerk](/) SDK, you can overcome the limitations discussed above. In the following section, you'll find the steps to implement a more secure and scalable solution for your JWT authentication while retaining the same functionality.

#### Set Up the Clerk SDK

Below are the steps to setting up the Clerk SDK:

1. Sign up for a [free account on Clerk.com](https://dashboard.clerk.com/sign-up).

2. On your [Clerk dashboard](https://dashboard.clerk.com), click `Add application` to create a new application.

3. In the Application name field, type in "JWT Example" and click `Finish`.

   ![Create new Clerk application](./9bc39e68dcb1236d17db7bf4602b43485385313c-2520x1816.png)

4. On the application dashboard, click on `API Keys` in the left navigation. Then copy the `Frontend API key`, `Backend API key`, and `JWT verification key`.

   ![Copy Clerk credentials](./5eb8d5a378ea0897a7c10b8581d0b9b8a4d7db8b-3154x1628.png)

5. Save the keys in the file `.env.local` inside your project:

   ```
   NEXT_PUBLIC_CLERK_FRONTEND_API=<frontend-key>
   CLERK_API_KEY=<backend-api-key>
   CLERK_JWT_KEY=<jwt-verification-key>
   ```

6. Install the Clerk SDK by running `npm i @clerk/nextjs` inside your project.

7. Add the `ClerkProvider` in the `pages/_app.js` file to use the authentication state throughout the application:

   ```jsx
   import { ClerkProvider } from '@clerk/nextjs'

   export default function App({ Component, pageProps }) {
     return (
       <ClerkProvider {...pageProps}>
         <Component {...pageProps} />
       </ClerkProvider>
     )
   }
   ```

#### Implement Sign In and Sign Up

With the Clerk SDK installed, you can easily set up your sign-in and sign-up pages.

*Note:* In Next.js, files named `pages/sign-in/[[...<anything>]].jsx` create a catch-all route that will match /sign-in, /sign-in/a, /sign-in/a/b, and so on.

For the sign-in page, create the new file `pages/sign-in/[[...index]].jsx` and use the prebuilt `<SignIn>` component from `@clerk/nextjs`:

```jsx
import { SignIn } from '@clerk/nextjs'

export default function SignInPage() {
  return <SignIn path="/sign-in" routing="path" signUpUrl="/sign-up" />
}
```

For the sign-up page, create the new file `pages/sign-up/[[...index]].jsx` and use the prebuilt `<SignUp>` component from `@clerk/nextjs`:

```jsx
import { SignUp } from '@clerk/nextjs'

export default function SignUpPage() {
  return <SignUp path="/sign-up" routing="path" signInUrl="/sign-in" />
}
```

#### Fetch the Secret Data from the API

To use the Clerk SDK with the API endpoints, you must create the file `middleware.js` at the project root with the following code:

```js
import { withClerkMiddleware } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export default withClerkMiddleware((req) => {
  return NextResponse.next()
})

// Stop Middleware running on static files
export const config = { matcher: '/((?!.*\\.).*)' }
```

To create the API endpoint `/api/clerk-safehouse`, create a new file, `pages/api/clerk-safehouse.js`. If the user is signed in, the API handler returns a `200` response with the `safehouseKey`. Otherwise, it returns a `401` response with an error message.

This API handler function uses the `getAuth` utility function from `@clerk/nextjs/server` to get the user's authentication state on the server:

```jsx
import { getAuth } from '@clerk/nextjs/server'

export default async function handler(req, res) {
  try {
    const { userId } = getAuth(req)
    if (!userId) {
      return res.status(401).json({ message: 'error' })
    }
    return res.status(200).json({ safehouseKey: 'under-the-doormat', message: 'success' })
  } catch (err) {
    return res.status(401).json({ message: 'error' })
  }
}
```

#### Display the Secret Data

To display the data from the `/api/clerk-safehouse` API endpoint, create the new file `pages/safehouse.jsx`.

In this file, create a `SafeHouse` component that uses the `useUser` hook from `@clerk/nextjs` to get the authentication state. If the user isn't signed in, it returns the prebuilt component `<RedirectToSignIn>` from `@clerk/nextjs` that redirects the user to the `/sign-in` page.

However, if the user is signed in, it'll display the `safehouseKey` fetched from the API call to the `/api/clerk-safehouse` endpoint. It also returns the `<SignOutButton>` that the user can click to sign out of the application:

```jsx
import { useEffect, useState } from 'react'

import { SignOutButton, RedirectToSignIn, useUser } from '@clerk/nextjs'

export default function SafeHouse() {
  const { isSignedIn } = useUser()
  const [userData, setUserData] = useState({})

  useEffect(() => {
    fetch('/api/clerk-safehouse')
      .then((res) => res.json())
      .then((data) => setUserData(data))
  }, [])

  if (!isSignedIn) {
    return <RedirectToSignIn />
  }

  return (
    <>
      <main style={{ padding: '50px' }}>
        <h1>Safehouse </h1>
        <p>
          You Safehouse key is <strong>{userData?.safehouseKey || 'Loading...'}</strong>
        </p>
        <SignOutButton />
      </main>
    </>
  )
}
```

#### Update Application Home Page

Finally, update the application home page (`pages/index.js`) to include the new `/safehouse` link in the list:

```jsx
import Link from 'next/link'

export default function Home() {
  return (
    <div>
      Home
      <br />
      <ol>
        <li>
          <Link href={'/jwt-home'}>JWT Home</Link>
        </li>
        <li>
          <Link href={'/jwt-safehouse'}>JWT Safe house</Link>
        </li>
        <li>
          <Link href={'/safehouse'}>Clerk Safe house</Link>
        </li>
      </ol>
    </div>
  )
}
```

Your React application is ready with end-to-end authentication.

### Traditional JWT Authentication vs. Clerk

Now that you've implemented authentication in your React application using the traditional JWT flow and with Clerk, you can see how easy it is to implement a full-fledged authentication using the latter approach.

In the do-it-yourself JWT approach, all responsibilities regarding authentication—such as storing the password, verifying user identity, and crafting a beautiful user experience—fall on your shoulders.

Clerk lifts this burden by offering a full-stack solution for managing user authentication. It not only provides easy integrations on the frontend with prebuilt components but also authentication utilities for the backend API routes. With Clerk, you don't have to worry about password management, user session management, or signing and storing the JWT. It's all managed for you automatically.

Apart from its simplicity, the Clerk SDK also uses short-lived JWTs and HttpOnly cookies to provide an additional layer of security for your application. While short-lived JWTs help to protect against replay attacks and limit the window of opportunity for an attacker to use a compromised token, HttpOnly cookies help to protect against XSS attacks.

## Conclusion

In this article, you've successfully set up JWT authentication in a React application. While doing so, you learned more about JWT authentication and how to overcome some of its challenges. In particular, you saw how using a solution like Clerk can tremendously simplify JWT authentication in React and make the process more secure at the same time.

Clerk is a one-stop solution for authentication and customer identity management. It can help you build a flawless user authentication flow that supports login with password, multifactor authentication, and social logins with providers like Google, LinkedIn, Facebook, GitHub, and many more.

Clerk provides beautiful components ready to plug into your application and build the authentication flow in no time. Sign up to try [Clerk](https://dashboard.clerk.com/sign-up) today.

---

# Understanding and Properly Using React Global State
URL: https://clerk.com/blog/understanding-and-properly-using-react-global-state.md
Date: 2023-04-14
Category: Guides
Description: Explore the benefits of global state and discover two methods to implement it: the React context API and the Clerk React context API component.

React global state refers to the data or state that all components in a React application share. This data is typically stored in a global object, such as a state manager like Redux or the React context API, and it can be accessed by any component in the application that needs it.

By using global state, React components can communicate with each other and share data even if they're not directly connected in the component hierarchy. This allows data in a React application to be better organized and managed.

This article starts by explaining the benefits of using global state in React and when it's best to use it. It then shows you how to implement global state in React using two methods: a custom implementation of a React context using the [context API](https://reactjs.org/docs/context.html) and an implementation of the [Clerk React context API component](/docs/reference/clerk-react/clerkprovider). Lastly, it considers when each of these methods is most useful.

> For comprehensive React authentication with built-in state management, [learn more about our React support](/react-authentication).

The code for the tutorial can be found in [this GitHub repository](https://github.com/pjcjonas/clerk-dev-global-state-with-context).

## Why Use React Global State

As mentioned, using global state in a React application can help to make your code more organized, manageable, and performant.

Global state makes managing shared data easier. Storing all of an application's state in a global object makes it easier to manage from a single location rather than having to pass data down through the component hierarchy. It can make code easier to understand and maintain.

It also enables communication between components that are not directly connected. Any component in an application can access and update the shared data even if it's not directly connected to the component that initially stored the data. This can be useful for triggering updates or changes in other parts of the application.

Lastly, using global state improves performance. Because the global state is stored in a centralized location, components that need the same data can access it from the global state as opposed to each component having to fetch the data separately. This can improve the performance of an application by reducing the amount of data that needs to be fetched and processed.

## When to Use React Global State

Using React global state is not a must, but it can be a useful tool in certain situations.

It's most useful when data is needed by multiple components in an application because it ensures that these components all have access to the latest and most up-to-date version of the data. For example, for a [login form](/blog/building-a-react-login-page-template) that's used by multiple components, the global state could be used to store the user's authentication status and other information, which could then be accessed by any component that needs it.

Another common use case for React global state is to allow components to update the data in the global state. For example, a shopping cart application could use the global state to store a list of items in the cart and then allow any component that displays the cart items to update the list when a user adds or removes an item. It allows any component that displays the cart items to always have the latest version of the data.

## Prerequisites

To follow along with this tutorial, you'll need the following prerequisites installed on your system:

- Node.js and npm: Vite is built on top of Node.js and npm, so you must have these installed to use the npm create command.
- TypeScript: Vite supports TypeScript out of the box, but you must have TypeScript installed to create a React TypeScript project with Vite.

## Method 1: Using the React Context API to Implement Global State

In this section, you'll learn the basic structure for creating a globally managed state object using a context provider to get and update information without creating a dependency chain of properties.

### Cloning and Setting Up the Project

Clone the project from [GitHub](https://github.com/pjcjonas/clerk-dev-global-state-with-context).

Below is a directory tree of the cloned project. Make sure that you are on the `main` branch but running `git checkout main` in the project root.

```bash
|..
├── index.html
├── package.json
├── README.md
├── src                                     # Main application source code
|  ├── app-context                          # Application context
|  |   └── user-context.tsx                 # Main application user context
|  |   └── user-context-provider.tsx        # Application context provider
|  ├── components                           # UI Components
|  |   └── AdminPage                        # Admin page locked by a user object
|  |       └── index.tsx
|  |   └── LoginStatus                      # Login status display component
|  |       └── index.tsx
|  |   └── SignInSignOut                    # Log in and log out buttons
|  |       └── index.tsx
|  |   └── WelcomePage                      # Landing page
|  |       └── index.tsx
|  ├── App.tsx
|  ├── assets
|  ├── main.tsx
|  └── vite-env.d.ts                        # ViteJs env type declarations
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts                          # ViteJs configuration
```

Once the project has been cloned, you need to install the project's node modules using `npm i` or `npm install`. To test the React app, use `npm run dev` to start the server at [http://localhost:5173](http://localhost:5173).

Once the server is running, the following should appear in the terminal window to indicate that the server is running correctly:

```bash
VITE v4.0.3  ready in 3140 ms

➜  Local:   http://localhost:5173/
➜  Network: use --host to expose
➜  press h to show help
```

If your web browser does not open automatically, open the browser and enter the following URL in the address bar: `http://localhost:5173/welcome`. Here is what will be visible in the browser:

![Basic welcome page with sign-in button](./d8ab2c29394a05c65abf4d9bcbc70c8a8f2eaea7-1082x722.png)

### Declaring the Context API and Setting the Initial Application

Let's have a look how this first example is configured in `~./src/app-context`. This directory has two files. The first is **user-context.tsx**, which is where the React context state is first declared, then initialized with default values, and lastly exported for global use when the context provider wraps the global application node:

```typescript
// FILE - ./src/app-context/user-context.tsx
// ----------------------------------

import React from 'react'

export interface UserContract {
  id?: number
  username?: string
  firstName?: string
  email?: string
}

// The dummy user object used for this example
export const DummyUser: UserContract = {
  id: 1,
  username: 'MyUserName',
  firstName: 'John',
  email: 'john@doe.com',
}

/**
 * Application state interface
 */
export interface AppState {
  user?: UserContract
  updateState: (newState: Partial<AppState>) => void
}

/**
 * Default application state
 */
const defaultState: AppState = {
  user: {},
  updateState: (newState?: Partial<AppState>) => {},
}

/**
 * Creating the Application state context for the provider
 */
export const UserContext = React.createContext<AppState>(defaultState)
```

First, the `AppState` interface is declared containing the `user` object type and `updateState` function. Included on the `AppState` interface is the `updateState` method, which will accept a partial state object that allows specific sections of the user object to be updated.

After the interface has been declared, the default state is created and set to the type of the `AppState` and then defaulted. In this case, it's an empty object.

Finally, the React context is created and exported as `UserContext` with the [React.createContext](https://beta.reactjs.org/reference/react/createContext) SDK API.

### Adding React Global State Using the Context Provider

Inside **./src/app-context/user-context-provider.tsx** is `UserContextProvider`. This is where the global state and provider methods like `updateState` get assigned.

```typescript
// FILE - ./src/app-context/user-context-provider.tsx
// ----------------------------------

import React, { useState } from 'react'
import { AppState, UserContext } from './user-context'

interface Props {
  children: React.ReactNode
}

/**
 * The main context provider
 */
export const UserContextProvider: React.FunctionComponent<Props> = (props: Props): JSX.Element => {
  /**
   * Using react hooks, set the default state
   */
  const [state, setState] = useState({})

  /**
   * Declare the update state method that will handle the state values
   */
  const updateState = (newState: Partial<AppState>) => {
    setState({ ...state, ...newState })
  }

  /**
   * Context wrapper that will provider the state values to all its children nodes
   */
  return (
    <UserContext.Provider value={{ ...state, updateState }}>{props.children}</UserContext.Provider>
  )
}
```

To make the global state available to the entire application, you need to provide it as the main parent wrapper to all the child nodes. This is done by setting the `<App />` component as the child node of `UserContextProvider`:

```typescript
// FILE - ./src/main.tsx
// ----------------------------------

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { UserContextProvider } from './app-context/user-context-provider'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    // Context provider wrapper
    <UserContextProvider>
      // Every child node will have access to the UserContext state.
      <App />
    </UserContextProvider>
  </React.StrictMode>,
)
```

### Making Use of the Global State Using the Context API

At this stage, the context can be included in the component using the [useContext](https://beta.reactjs.org/reference/react/useContext) React hook whenever it needs to be used:

```typescript
// FILE - ./src/App.tsx
// ----------------------------------

const App: React.FunctionComponent = (): JSX.Element => {
  /*
   * Using react's useContext to tell the component which context to use,
   * the relevant state properties can be extracted. In the example below,
   * the user object, clerk object and the updateState method is made available.
   */
  const { user, clerk, updateState } = useContext(UserContext)

  /* Application logic ... */

  return (
    <Container maxWidth="sm">
      <BrowserRouter>{/* Application components */}</BrowserRouter>
    </Container>
  )
}
```

## Method 2: Using Clerk's Global State Manager to Implement Global State

Now, let's consider another method that you can use to achieve global state. Clerk offers its own context provider called [ClerkProvider](/docs/reference/clerk-react/clerkprovider). It provides a wrapper for a ReactJS application that handles the user session and state as well as a list of child components and hooks for use in your application.

### Setting Up Clerk

First, you need to create a Clerk application.

Clerk handles authentication requests when users sign in. For Clerk to know which sign-in session belongs to which client account, there needs to be a unique reference between the React application and the Clerk API. This is achieved by setting up an application on the Clerk platform to get a unique application key against which the sign in will occur.

1. [Sign up](https://dashboard.clerk.com/sign-up) for a free trial account.

![Sign up for a Clerk account](./76635162bbe2076eff6a14cc9c8de347dea9c1c2-1042x717.png)

2. [Create](https://dashboard.clerk.com/apps/new) a Clerk application from the dashboard.

![Create a Clerk application](./e3ce61718a7c64b1f6e058c92f3d982677c1ec18-1069x932.png)

3. On the application settings page, go to **API Keys** and select the **React** framework from the dropdown list.

![Select React from the dropdown list](./fb4af61da9e756b05d13dccc9837d0456a3934fb-1382x873.png)

4. Copy the API key shown on the page as this will be used in the project.

![Copy the API key from the key field listed on the page](./1f0d428805d0dbf681c9c7997874df9a68718d48-1372x887.png)

### Clone the Repo

Now, return to the repository you cloned for the previous example and check out the Clerk branch by running the following commands:

1. `~/$: cd ./clerk-dev-global-state-with-context`
2. `~/clerk-dev-global-state-with-context/$: git checkout feat/main-clerk_devi`

Here's the file structure for the project once the `main-clerk_dev` branch has been checked out:

```bash
.
├── index.html
├── package.json
├── README.md
├── src                 # Main application source code
|  ├── App.tsx          # Application main component
|  ├── components       # Application components
|  |  ├── AdminPage     # Admin page requires login session
|  |  ├── common        # Holds the application routes
|  |  ├── LoginStatus   # Uses ClerkProvider context to show login status
|  |  └── WelcomePage   # Welcome page that incorporates login status component
|  ├── main.tsx         # Application render component
|  └── vite-env.d.ts    # ViteJs env type declarations
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts      # ViteJs configuration
```

## Implement Clerk React

In this section, you'll implement the Clerk context provider as well as the Clerk React components. These components help make it simple to manage content based on login status. Wrapping elements with the node list `<SingedIn />` means that they will only show when the user is signed in, and conversely, wrapping them with `<SignedOut />` will only display child nodes and components when the user is out.

First, you need to open the **main.tsx** file and wrap the `<App />` component with the `<ClerkProvider />`, as shown below:

```typescript
// FILE - ./src/main.tsx
// ----------------------------------

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { ClerkProvider } from '@clerk/clerk-react'

const clerkApiKey = import.meta.env.VITE_CLERK_API_URI

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    // The clerk provider wrapper for React Applications.
    <ClerkProvider frontendApi={clerkApiKey}>
      <App />
    </ClerkProvider>
  </React.StrictMode>,
)
```

In the code sample above, the `VITE_CLERK_API_URI` environment variable is assigned to the `clerkApiKey`, which is the key that you got in the section on setting up Clerk. You then add it to the Clerk provider context wrapper (`<ClerkProvider frontendApi={clerkApiKey}>`) used by the Clerk React components.

The code block below shows the `main.tsx` component, which is where you would have the main entry into your applications via the `<App />` component. Using the ClerkProvider React component, wrap your app component with it. This will set the application component as a child element. Next, add the `clerkApiKey` to the `ClerkProvider component using the `publishableKey\` property.

Now with the application component as the child of the Clerk provider component, all the user components will be passed down into each child component.

```typescript
// FILE - ./src/main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { ClerkProvider } from '@clerk/clerk-react'
import { BrowserRouter } from 'react-router-dom'

const clerkApiKey = import.meta.env.VITE_CLERK_API_URI
console.log(clerkApiKey)

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <BrowserRouter>
      <ClerkProvider publishableKey={clerkApiKey}>
        <App />
      </ClerkProvider>
    </BrowserRouter>
  </React.StrictMode>,
)
```

## Clerk React Components and Hooks

Clerk has [numerous components and functions](/docs/authentication/using-clerk-components) that can be used inside child components of the `<ClerkProvider />` context provider component, allowing for simple implementation and secure authentication for React applications.

As long as all the components that utilize the Clerk hooks and components are child nodes of the `<ClerkProvider />`, those components and hooks will interact with the user session and state.

### Clerk useUser Hook

You can use the [Clerk `useUser()` hook](/docs/nextjs/useuser) to get the current user state in the form of an object `{ isLoaded, isSignedIn, user }`. The `isLoaded` property checks to see if Clerk has finished requesting the user session, `isSignedIn` determines if the current user has signed in, and `user` contains the user info for the current user session.

```typescript
// FILE - ./src/components/LoginStatus/index.tsx

import React from 'react'
import { Typography } from '@mui/material'
import { useUser } from '@clerk/clerk-react'

export const LoginStatus: React.FunctionComponent = (): JSX.Element => {
  const { user } = useUser() // << Implementation
  return (
    <React.Fragment>
      <Typography variant="body1">
        Login status: // Get the user's first name
        {user ? `Signed in as ${user.firstName}` : 'You are signed out'}
      </Typography>
    </React.Fragment>
  )
}
```

### Clerk SignIn, SignedIn, and SignedOut Components

The routes file below shows the use of three Clerk react components. [`SignIn`](/docs/nextjs/signin), [`SignedIn`](/docs/component-reference/signed-in), and [`SignedOut`](/docs/component-reference/signed-out):

```tsx {{ prettier: false }}
// FILE - ./src/components/common/routes.tsx

import { Route, Routes } from 'react-router-dom'
// ...

export const MainRoutes: React.FunctionComponent = (): JSX.Element => {
  //...
  <Route
    path="/admin"
    element={
      <React.Fragment>
        <SignedIn>
          <AdminPage />
        </SignedIn>
        <SignedOut>
          <SignIn afterSignInUrl="/admin" />
        </SignedOut>
      </React.Fragment>
    }
  />
  // ...
}
```

The Clerk components are placed as children to the ClerkProvider to allow you to determine which content to show based on login status.

When the `SignIn` component (`<SignIn afterSignInUrl="/admin" />`) is rendered, it displays the Clerk user sign-in modal on the React application. The optional parameter `afterSignInUrl` means the application will update the URL of the page once login is successful.

![Clerk sign in modal rendered](./9bfbf4436f6dbbb77ae43cc3c754919ee25f3b4a-849x661.png)

`<SignedIn>` and `<SignedOut>` act as conditional wrappers. The current sign-in status will determine whether child components will or will not render. So if a component is wrapped with `<SignedOut> {... Child Components} </SignedOut>`, those components will only show if the user is signed out. The same applies to the `SignedIn` component.

### Clerk User Metadata

Clerk lets you store and manage [user metadata](/docs/users/user-metadata) in a central location. This data can include information such as user preferences, settings, and usage statistics. Using Clerk lets you easily access and update this information as needed without constantly having to ask users for it, resulting in a better user experience.

By having all of this information in one place, developers can tailor their app or website to individual users' needs and preferences. This can help increase engagement and retention as users are more likely to use a personalized app or website.

Three types of user metadata can be configured: private, public, and unsafe.

[Private metadata](/docs/users/user-metadata#private-metadata) is set and configured on the backend. It's not accessible via the ClerkJS [frontend API](https://clerk.com/docs/reference/frontend-api) to ensure that no sensitive information is visible on the client side of the application that could potentially compromise personal information. If any private data needs to be accessible from the frontend API, the user to whom the data belongs must give consent.

[Public metadata](/docs/users/user-metadata#public-metadata) is also set on the backend but is visible in the ClerkJS frontend API as read-only properties for use on the client side. None of these values can be changed directly from the client side though. The only way to change these values is to update the metadata using the `users.updateUsers` method.

[Unsafe metadata](/docs/users/user-metadata#unsafe-metadata) is set by both the frontend and backend APIs. For example, when a user signs up via the frontend API, custom fields can be set and saved to the user object on the backend, and the same can be done the other way around. These attributes and values can also be set after the user has signed in to the application and then be persisted on the user object with `users.updateUsers`.

See a code sample of this below:

```javascript
// Source: https://clerk.com/docs/users/user-metadata#unsafe-metadata

import { useUser } from '@clerk/nextjs'

const updateMetadata = async () => {
  const { user } = useUser()
  const data = 'Data to store unsafely'

  try {
    const response = await user.update({
      unsafeMetadata: { data },
    })
    if (response) {
      console.log('res', response)
    }
  } catch (err) {
    console.error('error', err)
  }
}
```

## React Context API vs. Clerk Context Provider

You've seen how to use both methods to handle global state in React, so how do they compare?

Clerk's state management solution is more suited for larger, more complex applications that need a more powerful and flexible state management solution, while the React Context API is better for smaller applications that don't need as much state management functionality.

So, use the React Context API if:

- you need to share a small amount of state between a few components;
- you don't need to manage complex state dependencies or updates; or
- you want a lightweight solution for state management that doesn't add too much overhead to an application.

In contrast, use Clerk if:

- you need a centralized store for the application's state;
- you need to handle complex state management scenarios that involve nested data structures or multiple dependencies;
- you want to easily manage state updates and subscriptions across different components in an application; or
- you want a simple, intuitive API for state management that's easy to understand and use.

Are you interested in trying Clerk? You can [sign up](https://dashboard.clerk.com/sign-up) for a free tier for POCs as well as individual and private use, or you could look into the [paid solutions](/pricing) for large-scale client bases and enterprise-level tools.

---

# Implementing reCAPTCHA in React
URL: https://clerk.com/blog/implementing-recaptcha-in-react.md
Date: 2023-04-14
Category: Guides
Description: Learn how to protect your React app from spam and abuse using reCAPTCHA. Follow this tutorial to create a sign-up form with reCAPTCHA validation.

reCAPTCHA is a free service provided by Google that protects websites from spam and abuse by using risk analysis techniques to differentiate between humans and bots.

Developers can use reCAPTCHA to detect and block malicious user sign-ups, usually automated bots that attempt to create fake accounts. Additionally, reCAPTCHA acts as a guardrail against data scraping bots accessing and collecting information from a website, ensuring the protection of sensitive information.

In this article, you'll learn about reCAPTCHA and how to implement it on your own site. More specifically, you'll create a sign-up form in a React application and use reCAPTCHA to verify that the user is a human before they can submit that form.

## What Is reCAPTCHA?

Before you implement reCAPTCHA on your website, let's take a look at how it works and its common use cases.

### How reCAPTCHA Works

When users visit a website with reCAPTCHA enabled, they're presented with a challenge they must complete to prove that they're human. This challenge can take several forms:

- **An image challenge:** The user is presented with a distorted image of letters and numbers and asked to type the characters they see into a text box.
- **An audio challenge:** Instead of images, an audio recording of letters and numbers is used and the user is asked to type the characters they hear into a text box.
- **A No CAPTCHA reCAPTCHA:** The user is asked to click on a checkbox to indicate that they're not a robot. In some cases, Google might also ask the user to complete a separate image or audio challenge if it needs further proof that the user is human. You'll use this type of reCAPTCHA in the tutorial later in this article.

Once the user completes the challenge, the website will receive a response from the reCAPTCHA server indicating whether the user is human or not. Based on this response, the website can decide whether to allow the user to access certain pages or forms or deny access if it determines that the user is likely a bot.

Note that there's also a form of reCAPTCHA that doesn't actually present a challenge to the user. Instead, the system uses advanced risk analysis techniques to determine whether the user is human. This method is called *invisible reCAPTCHA*.

### reCAPTCHA Use Cases

These are some prominent use cases for reCAPTCHA:

- **Preventing spam and bots on online forms:** This is a top use case. reCAPTCHA can protect online forms such as survey forms, blog post comment forms, and customer feedback from spamming bots.

- **Protecting login pages:** reCAPTCHA can be used to protect [login pages](/blog/building-a-react-login-page-template) from automated bots trying to gain unauthorized access by brute-force attacks. Requiring a user to solve a reCAPTCHA challenge before completing the login process makes it much more difficult for bots to gain access to an account.

- **Preventing scraping and data harvesting:** Adding reCAPTCHA verification stops automated bots from accessing the website, which stops attempts at data scraping and harvesting at an early stage. For example, [Cloudflare](https://www.cloudflare.com) runs bot checks as a measure to prevent bots from opening websites and scraping data.

## Implementing reCAPTCHA in Your React Application

In this tutorial, you'll create a newsletter sign-up page to collect users' email addresses and names using React. You'll then add a reCAPTCHA to the sign-up form on this page to verify if the user is a human and filter out bots and spam.

Before getting started, ensure that you have the following installed on your computer:

- Node.js (version 16 or above)
- npm (version 8 or above)
- A code editor ([Visual Studio Code](https://code.visualstudio.com) is recommended)

You can also clone this [GitHub repository](https://github.com/Anshuman71/react-recaptcha-example) to try the example application.

### Set Up a React Project

Open your terminal and run the following command to initialize a React project using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html):

```bash
npx create-react-app react-recaptcha-example -y
```

Now change the directory to the project and open it in your code editor:

```bash
cd react-recaptcha-example
```

First, update the title on the `public/index.html` page to "reCAPTCHA example".

```html
<!doctype html>
<html lang="en">
  <head>
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!-- Rest meta tags -->
    <title>reCAPTCHA example</title>
  </head>
  <body>
    <!-- Rest of the body -->
  </body>
</html>
```

Then, open the **src/App.js** file to update the application to show a simple `<h1>` tag with "Sign up for Newsletter" as the content.

```jsx
function App() {
  return (
    <div>
      <h1>Sign up for Newsletter</h1>
    </div>
  )
}

export default App
```

Run `npm start` in the terminal to start the application. This command will run the React application on `http://localhost:3000` and open it in your default browser.

![Initial layout](./610f211ff2e472d8f091bd98b0182008114f42ae-1277x723.png)

The initial setup is now ready. Next, you'll create the sign-up form for the newsletter.

### Create a Sign-Up Form

To create a sign-up form, open the **App.js** file in the code editor and add a `form` element in the `App` component. In the `form` element, add two input boxes—`Email` and `Name`—to collect the user's email address and name. To allow users to submit the form, add a `button` in the `form`.

To track the input value of the two input fields, create two state variables: `email` and `name`. Add the `onChange` event handler on the `input` elements to update the input value using `setEmail` and `setName` functions:

```jsx
import { useState } from 'react'
import './App.css'

function App() {
  const [email, setEmail] = useState('')
  const [name, setName] = useState('')
  return (
    <div>
      <h1>Sign up for Newsletter</h1>
      <form>
        <input
          name="Email"
          type={'email'}
          value={email}
          required
          placeholder="joe@example.com"
          onChange={(event) => setEmail(event.target.value)}
        />
        <input
          name="Name"
          type={'name'}
          value={name}
          required
          placeholder="Joe"
          onChange={(event) => setName(event.target.value)}
        />
        <button type="submit">Sign up</button>
      </form>
    </div>
  )
}

export default App
```

You'll see a basic sign-up form, as shown below:

![Basic sign-up form](./0eaa635f3245e18383e213bced5d1f88cf6ac835-1605x903.png)

While functional, this form doesn't look very stylish. To make it look nicer, open the **App.css** file, remove all existing styles, and paste the CSS rules from below to align the form in the center and add some padding:

```css
h1 {
  text-align: center;
}

form {
  margin: 40px auto;
  width: 300px;
  display: flex;
  flex-direction: column;
}

input {
  margin-bottom: 20px;
  padding: 5px;
  border-radius: 5px;
}

button {
  padding: 5px;
  border-radius: 5px;
  margin-bottom: 20px;
}
```

After you add the above styles, the sign-up form should look more welcoming.

![Sign-up form with styles](./138673dd082c392bd8ebf8e90e1116571014db13-1616x909.png)

### Configure reCAPTCHA

Now that the sign-up form is ready, it's time to configure the reCAPTCHA verification. To use Google's reCAPTCHA, you must register a site on the [reCAPTCHA admin console](https://www.google.com/recaptcha/admin/create).

In the admin console, use `react-example` as the **Label**. Then, select **reCAPTCHA v2 > "I'm not a robot" Checkbox** as the reCAPTCHA type.

Add the domains where you want to use the reCAPTCHA, starting with `localhost`.

![Register form](./a2992ff2a2c6689c9c2663e62705ce4a3b9e98f1-2802x1817.png)

Select **Accept the reCAPTCHA Terms of Service** and click **Submit**.

On the next page, click on **COPY SITE KEY** and **COPY SECRET KEY** to copy the values.

![Copy site and secret key](./77b912977987b70663d35378cce3d349c477eb45-2394x1507.png)

Now create a new `.env` file in your project and paste these values, as shown below:

```bash
REACT_APP_SITE_KEY=<from-admin-dashboard>
SITE_SECRET=<from-admin-dashboard>
```

*Note:* You must append environment variables used in Create React App with `REACT_APP_`, as per the [documentation](https://create-react-app.dev/docs/adding-custom-environment-variables).

To use the reCAPTCHA in your React application, install the [react-google-recaptcha](https://www.npmjs.com/package/react-google-recaptcha) npm package by running the following command:

```bash
npm i react-google-recaptcha
```

In the **App.js** file, import the `ReCAPTCHA` component and place it below the `<button>`. Pass the `siteKey` prop and assign it to the `process.env.REACT_APP_SITE_KEY` so Google can recognize your reCAPTCHA:

```jsx
import { useState } from 'react'
import './App.css'
import ReCAPTCHA from 'react-google-recaptcha'

function App() {
  const [email, setEmail] = useState('')
  const [name, setName] = useState('')
  return (
    <div>
      <h1>Sign up for Newsletter</h1>
      <form>
        <input
          name="Email"
          type={'email'}
          value={email}
          required
          placeholder="joe@example.com"
          onChange={(event) => setEmail(event.target.value)}
        />
        <input
          name="Name"
          type={'name'}
          value={name}
          required
          placeholder="Joe"
          onChange={(event) => setName(event.target.value)}
        />
        <button type="submit">Sign up</button>
        <ReCAPTCHA sitekey={process.env.REACT_APP_SITE_KEY} />
      </form>
    </div>
  )
}

export default App
```

You'll now see the reCAPTCHA rendering inside the `form`:

![reCAPTCHA rendering](./ce25dd5373b69f840506c07894e1ee3b0152dc3a-1606x900.png)

### Test reCAPTCHA

To test the reCAPTCHA on form submission, you need to call the `getValue` method on the reCAPTCHA instance and verify its value. For this, you need to keep a reference to the reCAPTCHA component, which you can do using the `useRef` hook.

Create a `recaptcha` reference using the `useRef` hook and assign it as the value for the `ref` prop on the `ReCAPTCHA` component:

```jsx
import { useRef, useState } from 'react'
import './App.css'
import ReCAPTCHA from 'react-google-recaptcha'

function App() {
  const recaptcha = useRef()
  const [email, setEmail] = useState('')
  const [name, setName] = useState('')
  return (
    <div>
      <h1>Sign up for Newsletter</h1>
      <form>
        {/* input elements*/}
        <button type="submit">Sign up</button>
        <ReCAPTCHA ref={recaptcha} sitekey={process.env.REACT_APP_SITE_KEY} />
      </form>
    </div>
  )
}

export default App
```

To handle form submission, create a `submitForm` function that'll execute on the form `submit` event and verify the reCAPTCHA before form submission.

To verify the reCAPTCHA, the `submitForm` function calls the `getValue` method on the `recaptcha` ref object. The `getValue` method returns a token or `undefined`. If it's a token, the user has verified themselves. Otherwise, you show them an alert. Add the `submitForm` to the `onChange` event handler on the `form` element:

```jsx
import { useRef, useState } from 'react'
import './App.css'
import ReCAPTCHA from 'react-google-recaptcha'

function App() {
  const recaptcha = useRef()
  const [email, setEmail] = useState('')
  const [name, setName] = useState('')

  async function submitForm(event) {
    event.preventDefault()
    const captchaValue = recaptcha.current.getValue()
    if (!captchaValue) {
      alert('Please verify the reCAPTCHA!')
    } else {
      // make form submission
      alert('Form submission successful!')
    }
  }

  return (
    <div>
      <h1>Sign up for Newsletter</h1>
      <form onSubmit={submitForm}>{/* other elements */}</form>
    </div>
  )
}

export default App
```

### Server-Side Validation

So far, you've only checked if the user verified themselves using the reCAPTCHA, but you have yet to determine whether the token value is valid. If you open your reCAPTCHA admin console, you'll see the following message.

![Warning message on reCAPTCHA console](./d908b6f75bd8d468972b947f6d30a7825dc2e2aa-3587x1447.png)

To rectify this, you can request Google's reCAPTCHA verify API to confirm whether the provided token is valid. As this operation requires you to share the `SITE_SECRET` with Google, you must handle this operation on the server side for security reasons.

You can create a simple HTTP server using Node.js to implement the server-side validation. To quickly create a Node.js server, you need the following dependencies:

- `express`: to create the server application
- `axios`: to make network requests on the server side
- `cors`: to allow requests coming from a different origin (localhost: 3000)
- `dotenv`: to read the environment variables from the `.env` file

Run the following command to install all these dependencies:

```bash
npm i express axios dotenv cors
```

Now create the new file **server.js**, and set up a server that listens on port `8000`. Use the `cors` middleware to allow incoming requests from all origins and the `express.json` middleware to parse the JSON payload in the request body. Retrieve the `SITE_SECRET` from the environment variables.

The server accepts POST requests on the `/verify` route. In the POST request handler, you use the Google reCAPTCHA API to verify the `captchaValue` sent in the request body. You then send the response back to the user with the data returned from the reCAPTCHA API:

```js
require('dotenv').config()
const express = require('express')
const cors = require('cors')
const axios = require('axios')
const app = express()
const port = 8000

const SITE_SECRET = process.env.SITE_SECRET

app.use(cors())
app.use(express.json())

app.post('/verify', async (request, response) => {
  const { captchaValue } = request.body
  const { data } = await axios.post(
    `https://www.google.com/recaptcha/api/siteverify?secret=${SITE_SECRET}&response=${captchaValue}`,
  )
  response.send(data)
})

app.listen(port, () => {
  console.log(`Server listening at ${port}`)
})
```

Run **node server.js** to start the server application.

On the frontend, you need to update the `submitForm` method to send the `captchaValue` to the `/verify` endpoint in a POST request. In the **App.js** file, update the `submitForm` function to request the `http://localhost:8000/verify` endpoint for server-side validation and show the user an alert based on whether the validation was successful using the returned `success` property on the response data:

```js
async function submitForm(event) {
  event.preventDefault()
  const captchaValue = recaptcha.current.getValue()
  if (!captchaValue) {
    alert('Please verify the reCAPTCHA!')
  } else {
    const res = await fetch('http://localhost:8000/verify', {
      method: 'POST',
      body: JSON.stringify({ captchaValue }),
      headers: {
        'content-type': 'application/json',
      },
    })
    const data = await res.json()
    if (data.success) {
      // make form submission
      alert('Form submission successful!')
    } else {
      alert('reCAPTCHA validation failed!')
    }
  }
}
```

The reCAPTCHA validation flow is now complete. See it in action below.

## Conclusion

In this article, you learned the importance of reCAPTCHA validation and how to use it to prevent malicious users from misusing your application. By following this tutorial, you successfully created a sign-up form with reCAPTCHA validation and you also created a Node.js server to further validate the reCAPTCHA value for better security.

Clerk provides an easy way to add authentication and user management to your application. It offers beautiful components ready to plug into your application and build the authentication flow in no time. If you're looking for a complete authentication and identity platform, sign up to try [Clerk](https://dashboard.clerk.com/sign-up) today.

---

# Implementing OAuth 2.0 to React for User Authorization
URL: https://clerk.com/blog/oauth2-react-user-authorization.md
Date: 2023-04-13
Category: Guides
Description: Learn how to implement OAuth 2.0 in a React app for user authorization. OAuth 2.0 lets users share information securely without passwords.

In today's digital age, applications are used for everything. From social media to online banking, so much can be done online. However, as society's reliance on online services grows, so does the need to keep personal information secure. This is where [OAuth 2.0](https://oauth.net/2) comes in.

OAuth 2.0 is a popular protocol for both developers and users that lets you share certain information with a third-party app without providing a password. It's become the standard for many online businesses because it's secure and easy to use.

In this article, you'll learn more about OAuth 2.0 and the different ways it can be used. You'll also learn how to implement OAuth 2.0 in a React application for user authorization.

> Looking for a simpler way to add OAuth to React? [Learn more about our React support](/react-authentication) with pre-built flows for Google, GitHub, and other providers.

## Authentication and Authorization

The first step in understanding OAuth 2.0 is understanding that while there are similarities between authentication and authorization, the two concepts are different.

*Authentication* is the process of verifying the identity of a user or system. Using a set of credentials, such as a username or password, authentication makes sure that users are who they say they are.

Meanwhile, *authorization* is the process of figuring out if an authenticated user has the right privileges to access certain resources or do certain things. It's mainly focused on figuring out what a user can do and what resources they can use based on their role or other characteristics.

Authentication and authorization are both important parts of web security. They work together to make sure that only people who are allowed to can use a system or its resources.

Now that you know how authentication and authorization work, let's dig into what OAuth 2.0 is.

## What Is OAuth 2.0

OAuth 2.0, also referred to as the [OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749), is an open standard for authorization that lets users give third-party applications limited access to their web server resources without giving the applications their private credentials. OAuth 2.0 gives users more control over their data; they can selectively grant access to the applications they want to use.

For instance, OAuth 2.0 can be utilized to help users sign in to third-party applications with their Google account. This is often referred to as social login and is so common you've probably used it.

Here is a brief explanation of what occurs when you use a social login:

1. A Google sign-up button will pop up on the screen of the third-party app.
2. When you click the button, you'll be taken to Google's authorization server, where you'll be asked to sign into your Google account (if you aren't currently).
3. Google displays a consent screen after you log in, listing the permissions the third-party application is requesting (*ie* your public information, such as email and name).
4. Once you give your approval, Google will send you back to the app and provide it with an access token that will allow the app to access your public information.
5. The third-party app can then use the access token to make API calls to Google on your behalf as long as the requests are within the scope of the permissions you granted.

This five-step process defines the OAuth 2.0 flow or grant type. An OAuth 2.0 flow (*ie* an OAuth 2.0 grant type) typically defines the steps involved in getting an access token, as seen here:

![Sample OAuth 2.0 flow, courtesy of Gideon Idoko](./df567d4006d298abd0a27f3997a08d1bf903c842-1000x732.png)

By following this implicit OAuth 2.0 flow (more on this later), you can use a third-party application without giving it access to your Google credentials. You can also stop the application from using your account at any time.

### Types of OAuth 2.0 Flows

OAuth 2.0 identifies a number of different grant types, or OAuth 2.0 flows, that can be used to get an access token (which is used to access a protected resource). The OAuth 2.0 flow chosen usually depends on the client application, the protected resource, and the trust between the client and the server that does the authorization.

The following are some of the most popular types of OAuth 2.0 flows:

#### Authorization Code Flow

In an [authorization code flow](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1), the resource owner is sent from the client application to the authorization server's authorization endpoint. Then the resource owner logs in and grants consent to the client, and is sent back to the client with an authorization code. Then the client exchanges this code for an access token.

Web applications commonly use this flow; however, it can also be used by mobile apps using the [Proof Key for Code Exchange (PKCE) technique](https://datatracker.ietf.org/doc/html/rfc7636).

#### Implicit Flow with Form Post

The [implicit flow with form post](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.2) is similar to the authorization code flow but is used for browser-based client applications, especially single-page apps. In this case, instead of trading an authorization code for an access token, the authorization server gives the access token directly to the client application, as in the example flow discussed previously.

#### Resource Owner Password Flow

In a [resource owner password flow](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.3), the resource owner already knows the client and feels comfortable giving them access to their credentials. The owner of the resource gives the client their private credentials directly, which the client then trades for an access token.

#### Client Credentials Flow

In the case of a [client credentials flow](https://www.rfc-editor.org/rfc/rfc6749#section-1.3.4), the client uses their own credentials to access protected resources instead of using the credentials of the resource owner. Afterward, the authorization server gives the client an access token.

### OAuth 2.0 Roles

OAuth 2.0 roles define the entities in an OAuth 2.0 flow, and there are four major roles:

1. **Resource owner:** The user who owns the protected resource that the client application needs to access. The resource owner may be a person, a system, or a device.
2. **Resource server:** The server where the client wants to access a protected resource. The resource server is in charge of enforcing access controls on the protected resource.
3. **Client:** The third-party app that wants to access the protected resource on behalf of the resource owner. The client could be a server-based, mobile, or web application.
4. **Authorization server:** The server that verifies the resource owner's identity and gives access tokens to the client after the resource owner gives permission. The authorization server also checks the client's access tokens before letting them use protected resources.

Now that you have a better understanding of OAuth 2.0 and the different roles and flows, let's implement OAuth 2.0 in a React application.

## Implementing OAuth 2.0 in a React Application

In this section, you'll learn how to implement an OAuth 2.0 authorization code flow in your React application. Additionally, you'll learn a less complicated approach to implementing user authorization with [Clerk](/).

All the code for this tutorial can be found in this [GitHub repo](https://github.com/iamgideonidoko/user-auth).

### Prerequisites

Before you begin, you'll need [Node.js version 16 or later](https://nodejs.org/en/download) installed on your machine. Node.js ships with npm, which you'll use to install the necessary packages.

In addition, it's recommended that you have a basic knowledge of React and JavaScript to help you complete this tutorial with ease.

#### OAuth 2.0 with the Standard Flow

So far, you've only learned about the various OAuth 2.0 flow types. Here, you'll implement one.

The authorization code flow [is the most secure flow for web server applications](https://developer.constantcontact.com/api_guide/server_flow.html), and this is how it works:

![Authorization code OAuth 2.0 flow courtesy of Gideon Idoko](./3b201d8285ee3e6a5ab107338c2312a64e20e75d-5320x3236.png)

There are three main components: the authorization server, the frontend, and the backend. The final component is the resource (which is part of the backend) that only authorized users can access. In order for a user to be authorized, they must complete the authorization code flow:

1. A user goes to the authorization server from the frontend to get an authorization code.
2. The authorization code is then sent to the backend.
3. The backend verifies the authorization code by getting tokens from the authorization server.
4. The backend then authorizes the user to have access to the resource.

In this scenario, the frontend is a React application, and the backend/resource is a Node.js application. Building the authorization server is beyond the scope of this article, so instead, you'll leverage [Google OAuth 2.0](https://developers.google.com/identity/protocols/oauth2/web-server).

Alright, let's get started!

### Get Your Google Client ID and Secret

Interacting with the authorization server to get the authorization code is the first step in the OAuth 2.0 flow. You'll need a Google client ID and secret to be able to achieve that.

Follow these steps to obtain the client ID and secret:

1. Create a [new Google Cloud project](https://console.cloud.google.com/projectcreate).

   ![New Google Cloud project](./da8e6340b83862ba6c2339dca589b4cea85afd1c-1302x904.png)

2. Name it. In this example, it's "standard-auth".

3. Locate the **More Products** section on the sidebar, and click on **APIs & Services** and then the **OAuth consent screen**. Select **External** user type and click on **CREATE**. This will create a new OAuth consent screen. The OAuth consent screen tells users what app is asking for access to their information and what information the app can access:

   ![Create a new OAuth consent screen](./4100f9724d464df3968b86ba0b40bc712c7e4f3d-825x524.png)

4. Enter a name for your OAuth consent and the required email addresses; then click **SAVE AND CONTINUE**. At this point, you'll be shown the scope tab. Select the scopes indicated in the following image and click on the **Update** button to save it:

   ![Select scopes](./bdee261ca223628d250a732f2d4348eaa1bd5647-2348x1146.png)

5. Add the email that you want to use to test the consent screen in the **Test users** section:

   ![Test users section](./ec3ed297053c78b639430b516a0c2a6bdbb77956-775x485.png)

6. Click on **APIs & Services** on the sidebar again, and then select **Credentials**. Click on the **CREATE CREDENTIALS** button and then select **OAuth client ID**:

   ![CREATE CREDENTIALS](./98d544bb353feecf40c43daded6ee9f900bc6bba-900x501.png)

7. Complete the form and click on **CREATE** when you're done:

   ![OAuth Credentials screen](./79bd74261e7a902f86be11478439387cded923d1-1722x2024.png)

   The redirect URI specifies where Google should navigate after the consent screen. This means that the redirect or callback has to be created on the React frontend.

8. Copy your newly created client ID and secret, and save them somewhere safe. You'll need them later when requesting an authorization code and tokens.

### Implement the Node.js Backend

Now that you have your client ID and secret, it's time to implement the backend with Node.js and the [Express](https://expressjs.com) framework.

To do so, create a new directory (*ie* `standard-auth`) to house the backend project:

```bash
mkdir standard-auth
```

Then create another directory inside it called `server`:

```bash
cd standard-auth && mkdir server
```

Initialize a new project within the `server` directory:

```bash
npm init esnext -y
```

And install the necessary packages by running this command:

```bash
npm install axios@1.3.4 cookie-parser@1.4.6 cors@2.8.5 dotenv@16.0.3 express@4.18.2 jsonwebtoken@9.0.0 query-string@8.1.0
```

Create a new `.env` file and add the following to it:

```text
GOOGLE_CLIENT_ID=<the client ID you created earlier>
GOOGLE_CLIENT_SECRET=<the client secret you created earlier>
REDIRECT_URL=http://localhost:3000/auth/callback
CLIENT_URL=http://localhost:3000
TOKEN_SECRET=<any random string>
```

Update the environment variables accordingly.

Create a new `server.js` file, which is where the server-side code will be stored:

```bash
touch server.js
```

Add the following code to `server.js` to import all the necessary dependencies and create a config object:

```javascript
import 'dotenv/config'
import express from 'express'
import cors from 'cors'
import axios from 'axios'
import queryString from 'query-string'
import jwt from 'jsonwebtoken'
import cookieParser from 'cookie-parser'

const config = {
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
  tokenUrl: 'https://oauth2.googleapis.com/token',
  redirectUrl: process.env.REDIRECT_URL,
  clientUrl: process.env.CLIENT_URL,
  tokenSecret: process.env.TOKEN_SECRET,
  tokenExpiration: 36000,
  postUrl: 'https://jsonplaceholder.typicode.com/posts',
}
```

Here, `config.authUrl` is a link to the OAuth consent screen. It is sent along with [some query parameters](https://developers.google.com/identity/protocols/oauth2/web-server#creatingclient) to the frontend. Then a request is made to `config.tokenUrl`, along with [some query parameters](https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code) to verify an authorization code.

You need to define the parameters for both URLs by adding the following code to `server.js`:

```javascript
const authParams = queryString.stringify({
  client_id: config.clientId,
  redirect_uri: config.redirectUrl,
  response_type: 'code',
  scope: 'openid profile email',
  access_type: 'offline',
  state: 'standard_oauth',
  prompt: 'consent',
})
const getTokenParams = (code) =>
  queryString.stringify({
    client_id: config.clientId,
    client_secret: config.clientSecret,
    code,
    grant_type: 'authorization_code',
    redirect_uri: config.redirectUrl,
  })
```

Initialize a new Express app:

```javascript
const app = express()
```

And add the following middlewares to resolve CORS, parse cookies, and verify authorization, respectively:

```javascript
// Resolve CORS
app.use(
  cors({
    origin: [config.clientUrl],
    credentials: true,
  }),
)

// Parse Cookie
app.use(cookieParser())

// Verify auth
const auth = (req, res, next) => {
  try {
    const token = req.cookies.token
    if (!token) return res.status(401).json({ message: 'Unauthorized' })
    jwt.verify(token, config.tokenSecret)
    return next()
  } catch (err) {
    console.error('Error: ', err)
    res.status(401).json({ message: 'Unauthorized' })
  }
}
```

At this point, you can start adding endpoints for different tasks.

Add the following endpoint (`/auth/url`) to return the authorization URL to the frontend:

```javascript
app.get('/auth/url', (_, res) => {
  res.json({
    url: `${config.authUrl}?${authParams}`,
  })
})
```

Then add another endpoint (`/auth/token`) that will get an authorization code from the frontend and verify it:

```javascript
app.get('/auth/token', async (req, res) => {
  const { code } = req.query
  if (!code) return res.status(400).json({ message: 'Authorization code must be provided' })
  try {
    // Get all parameters needed to hit authorization server
    const tokenParam = getTokenParams(code)
    // Exchange authorization code for access token (id token is returned here too)
    const {
      data: { id_token },
    } = await axios.post(`${config.tokenUrl}?${tokenParam}`)
    if (!id_token) return res.status(400).json({ message: 'Auth error' })
    // Get user info from id token
    const { email, name, picture } = jwt.decode(id_token)
    const user = { name, email, picture }
    // Sign a new token
    const token = jwt.sign({ user }, config.tokenSecret, {
      expiresIn: config.tokenExpiration,
    })
    // Set cookies for user
    res.cookie('token', token, {
      maxAge: config.tokenExpiration,
      httpOnly: true,
    })
    // You can choose to store user in a DB instead
    res.json({
      user,
    })
  } catch (err) {
    console.error('Error: ', err)
    res.status(500).json({ message: err.message || 'Server error' })
  }
})
```

If the authorization code is verified (*ie* exchanged for an access token successfully), a new token is signed for the current user. The signed token will then be set as a cookie that will expire based on the `tokenExpiration` key in the `config` object.

Add the `/auth/logged_in` endpoint to check the logged-in state of a user:

```javascript
app.get('/auth/logged_in', (req, res) => {
  try {
    // Get token from cookie
    const token = req.cookies.token
    if (!token) return res.json({ loggedIn: false })
    const { user } = jwt.verify(token, config.tokenSecret)
    const newToken = jwt.sign({ user }, config.tokenSecret, {
      expiresIn: config.tokenExpiration,
    })
    // Reset token in cookie
    res.cookie('token', newToken, {
      maxAge: config.tokenExpiration,
      httpOnly: true,
    })
    res.json({ loggedIn: true, user })
  } catch (err) {
    res.json({ loggedIn: false })
  }
})
```

Then add the `/auth/logout` endpoint to log out a user in session:

```javascript
app.post('/auth/logout', (_, res) => {
  // clear cookie
  res.clearCookie('token').json({ message: 'Logged out' })
})
```

This will clear the `token` cookie, which invalidates a logged-in user's session.

Finally, add the resource endpoint:

```javascript
app.get('/user/posts', auth, async (_, res) => {
  try {
    const { data } = await axios.get(config.postUrl)
    res.json({ posts: data?.slice(0, 5) })
  } catch (err) {
    console.error('Error: ', err)
  }
})
```

This basically fetches posts (resources) and returns them as a response. The middleware for validating user authorization (`auth`) is used here because only authorized users are allowed to access this endpoint.

Now you need to make the Express app listen on port `5000`:

```javascript
const PORT = process.env.PORT || 5000

app.listen(PORT, () => console.log(`🚀 Server listening on port ${PORT}`))
```

### Implement the React Frontend

The easiest way to start a React project is to use boilerplate code. Go to the `standard-auth` directory and bootstrap a new React project using [Create React App](https://create-react-app.dev):

```bash
npx create-react-app client
```

This will create a `client` directory with your React project inside it.

Change to the `client` directory using `cd client` and install the following packages:

```bash
npm install axios@1.3.4 react-router-dom@6.9.0
```

Next, create a `.env` file in the root directory of your React project:

```bash
echo REACT_APP_SERVER_URL = http://localhost:5000 > .env
```

Open the `App.css` file in the `src` directory of your React project and add the following CSS code:

```css
.btn {
  border-radius: 100rem;
  padding: 12px 16px;
  background: linear-gradient(170deg, #61dbfb, #2e849b) !important;
  width: 10rem;
  border: none;
  color: white;
  font-weight: 600;
  margin: 1rem 0;
  cursor: pointer;
}
```

This will help style a button using the `btn` class.

Update your `App.js` file to import the necessary method and hooks by adding the following code just above the `App` component:

```javascript
import { RouterProvider, createBrowserRouter, useNavigate } from 'react-router-dom'
import axios from 'axios'
import { useEffect, useRef, useState, createContext, useContext, useCallback } from 'react'

// Ensures cookie is sent
axios.defaults.withCredentials = true

const serverUrl = process.env.REACT_APP_SERVER_URL
```

After updating the `App.js` file, you need to create a new React context to hold the logged-in and user states so they can be shared globally. To do so, add the following code just above the `App` component:

```javascript
const AuthContext = createContext()

const AuthContextProvider = ({ children }) => {
  const [loggedIn, setLoggedIn] = useState(null)
  const [user, setUser] = useState(null)

  const checkLoginState = useCallback(async () => {
    try {
      const {
        data: { loggedIn: logged_in, user },
      } = await axios.get(`${serverUrl}/auth/logged_in`)
      setLoggedIn(logged_in)
      user && setUser(user)
    } catch (err) {
      console.error(err)
    }
  }, [])

  useEffect(() => {
    checkLoginState()
  }, [checkLoginState])

  return (
    <AuthContext.Provider value={{ loggedIn, checkLoginState, user }}>
      {children}
    </AuthContext.Provider>
  )
}
```

The `checkLoginState` function in this React context makes a call to your backend's `/auth/logged_in` endpoint. Here, it's called in a `useEffect` block to run every time the app initially renders.

Next, add the following `Dashboard` component just before the `App` component:

```javascript
const Dashboard = () => {
  const { user, loggedIn, checkLoginState } = useContext(AuthContext)
  const [posts, setPosts] = useState([])
  useEffect(() => {
    ;(async () => {
      if (loggedIn === true) {
        try {
          // Get posts from server
          const {
            data: { posts },
          } = await axios.get(`${serverUrl}/user/posts`)
          setPosts(posts)
        } catch (err) {
          console.error(err)
        }
      }
    })()
  }, [loggedIn])

  const handleLogout = async () => {
    try {
      await axios.post(`${serverUrl}/auth/logout`)
      // Check login state again
      checkLoginState()
    } catch (err) {
      console.error(err)
    }
  }

  return (
    <>
      <h3>Dashboard</h3>
      <button className="btn" onClick={handleLogout}>
        Logout
      </button>
      <h4>{user?.name}</h4>
      <br />
      <p>{user?.email}</p>
      <br />
      <img src={user?.picture} alt={user?.name} />
      <br />
      <div>
        {posts.map((post, idx) => (
          <div>
            <h5>{post?.title}</h5>
            <p>{post?.body}</p>
          </div>
        ))}
      </div>
    </>
  )
}
```

Later, you'll render this component conditional based on the logged-in state of a user.

Add the `Login` component:

```javascript
const Login = () => {
  const handleLogin = async () => {
    try {
      // Gets authentication url from backend server
      const {
        data: { url },
      } = await axios.get(`${serverUrl}/auth/url`)
      // Navigate to consent screen
      window.location.assign(url)
    } catch (err) {
      console.error(err)
    }
  }
  return (
    <>
      <h3>Login to Dashboard</h3>
      <button className="btn" onClick={handleLogin}>
        Login
      </button>
    </>
  )
}
```

This component renders a button that takes the user to the OAuth consent screen you made earlier.

Do you remember when you provided a callback URL when you created your client ID and secret? Because of this, you need to add a component to handle the callback:

```javascript
const Callback = () => {
  const called = useRef(false)
  const { checkLoginState, loggedIn } = useContext(AuthContext)
  const navigate = useNavigate()
  useEffect(() => {
    ;(async () => {
      if (loggedIn === false) {
        try {
          if (called.current) return // prevent rerender caused by StrictMode
          called.current = true
          const res = await axios.get(`${serverUrl}/auth/token${window.location.search}`)
          console.log('response: ', res)
          checkLoginState()
          navigate('/')
        } catch (err) {
          console.error(err)
          navigate('/')
        }
      } else if (loggedIn === true) {
        navigate('/')
      }
    })()
  }, [checkLoginState, loggedIn, navigate])
  return <></>
}
```

Create a router to define routes that render the components you created earlier:

```javascript
const Home = () => {
  const { loggedIn } = useContext(AuthContext)
  if (loggedIn === true) return <Dashboard />
  if (loggedIn === false) return <Login />
  return <></>
}

const router = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/auth/callback', // google will redirect here
    element: <Callback />,
  },
])
```

Finally, update the `App` component with the following code:

```javascript
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <AuthContextProvider>
          <RouterProvider router={router} />
        </AuthContextProvider>
      </header>
    </div>
  )
}
```

Now, it's time to test out the implementation. Go to your `server` directory and run the following command to spin up the server:

```bash
node server.js
```

Open another terminal in your `client` directory and run this command to spin up your React server:

```bash
npm start
```

This command will open up [http://localhost:3000](http://localhost:3000) in your default browser. You should see something like this when the server is fully running:

![React home](./091fb8fed6fd17f2e008bd4dbdd9a86646395e73-2266x1514.png)

If you click on the login button, you'll be redirected to the OAuth consent screen:

![OAuth consent screen](./623c6dc5c09ec64a461287a8ad4a3e9986bf93ff-1133x757.png)

After you're redirected, you'll be logged in and authorized to access the post resources:

![Dashboard](./5e501876abe9edd3b02784cb0c47c8207b4f5d41-1409x807.png)

If you've followed along, you've officially implemented the OAuth 2.0 authorization code flow in React. Great work!

## Authorization with Clerk

As you can see, implementing OAuth 2.0 is complicated and time-consuming. This is where Clerk can help.

Clerk is a service that takes care of user management. This means developers don't have to keep reinventing the wheel and can focus on what they do best. Moreover, Clerk can also take care of authorization.

In this section, you'll see how Clerk can easily be used to replicate the implementation in the previous section.

Before you proceed, you need to have a Clerk account. An account will give you access to both a publishable key and a secret key, as well as other customizations.

If you don't already have an account, [go to Clerk's sign-up page](https://dashboard.clerk.com/sign-up) and create one:

![Clerk sign-up page](./e199148109142f1e8a96f307613cbbb7ba2fd5a4-1271x882.png)

After you've signed up, click on **Add application** to create a new project:

![Add a new application on Clerk](./6392464d7662055edb1392579e84f065e473b943-1080x501.png)

Update the settings for your new application to match the settings shown here:

![New application settings](./a7b74dbf275c4429d20314c8b397ceef0cda8ff7-1179x767.png)

When you're done, click on **FINISH**, and a new application will be created.

Navigate to the **API Keys** section. Copy your publishable and secret keys, and save them somewhere safe:

![Clerk API Keys page](./b9d2c985fa6955f697579bc70e15c91948c861ee-1504x824.png)

### Implement the Node.js Backend

For this Clerk example, you need to rewrite the Node.js implementation you did previously. Update your `.env` file in the `server` directory with the following:

```text
CLERK_SECRET_KEY=<the secret key you copied from your Clerk account>
```

Update the environment variable accordingly.

Then run the following command in your `server` directory to install Clerk's Node.js SDK:

```bash
npm install @clerk/clerk-sdk-node@4.7.11
```

Update your `server.js` file with the following code:

```javascript
import 'dotenv/config'
import express from 'express'
import cors from 'cors'
import axios from 'axios'
import { ClerkExpressRequireAuth } from '@clerk/clerk-sdk-node'

const config = {
  postUrl: 'https://jsonplaceholder.typicode.com/posts',
  clerkSecretKey: process.env.CLERK_SECRET_KEY, // Clerk automatically picks this from the env
}

const app = express()

app.use(cors())

app.get('/user/posts', ClerkExpressRequireAuth(), async (req, res) => {
  console.log('REQUEST AUTH: ', req.auth)
  try {
    const { data } = await axios.get(config.postUrl)
    res.json({ posts: data?.slice(0, 5) })
  } catch (err) {
    console.error('Error: ', err)
  }
})

const PORT = process.env.PORT || 5000

app.listen(PORT, () => console.log(`🚀 Server listening on port ${PORT}`))
```

Here, you're using the `ClerkExpressRequireAuth()` middleware to allow only authorized access to the `/user/posts/` route. It's that easy!

### Implement the React Frontend

To implement the React frontend, update the `.env` file in your `client` directory with the following:

```text
REACT_APP_CLERK_PUBLISHABLE_KEY=<the publishable key you copied from your Clerk account>
REACT_APP_SERVER_URL = http://localhost:5000
```

Remember to update the environment variable accordingly.

Then run the following command in the `client` directory to install the Clerk React SDK:

```bash
npm install @clerk/clerk-react@4.12.4
```

Update the `App.js` file inside the `src` directory in your `client` directory with the following code:

```javascript
import logo from './logo.svg'
import './App.css'
import {
  ClerkProvider,
  SignedIn,
  SignedOut,
  UserButton,
  RedirectToSignIn,
  useAuth,
} from '@clerk/clerk-react'
import { useState, useEffect } from 'react'
import axios from 'axios'

const clerkPubKey = process.env.REACT_APP_CLERK_PUBLISHABLE_KEY
const serverUrl = process.env.REACT_APP_SERVER_URL

const Dashboard = () => {
  const [posts, setPosts] = useState([])
  const { getToken } = useAuth()

  useEffect(() => {
    ;(async () => {
      try {
        const token = await getToken()
        console.log('token: ', token)
        // Get posts from server
        const {
          data: { posts },
        } = await axios.get(`${serverUrl}/user/posts`, {
          headers: { Authorization: `Bearer ${token}` },
        })
        setPosts(posts)
      } catch (err) {
        console.error(err)
      }
    })()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <>
      <h3>Dashboard</h3>
      <UserButton />
      <div>
        {posts.map((post, idx) => (
          <div>
            <h5>{post?.title}</h5>
            <p>{post?.body}</p>
          </div>
        ))}
      </div>
    </>
  )
}

function App() {
  return (
    // Don't forget to pass the publishableKey prop
    <ClerkProvider publishableKey={clerkPubKey}>
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <SignedIn>
            <Dashboard />
          </SignedIn>
          <SignedOut>
            <RedirectToSignIn />
          </SignedOut>
        </header>
      </div>
    </ClerkProvider>
  )
}

export default App
```

Clerk's React SDK exports prebuilt components that are very useful. For instance, the `SignedIn` component renders its children (in this case, the `Dashboard` component) only when a user is signed in. The `SignedOut` component does the opposite.

The `RedirectToSignIn` component redirects the users to a form where they can choose their authentication method. In this case, the users will be prompted to use their [Google account](/blog/nextjs-google-authentication) or email address (you set this when creating your Clerk application).

The `Dashboard` component makes a request to get post resources using the token provided by the `useAuth` hook from the Clerk SDK.

If you go through the sign-up process, you'll end up with something like this:

![Dashboard after Clerk](./676a08c311b93de33c0d16fec4533db950b13cd5-903x629.png)

## Standard Authorization vs. Clerk

As you can see, implementing a standard and secure authorization can be difficult, time-consuming, and in some cases, unnecessary. However, you can solve these issues with [Clerk](/), which can help you implement user authentication and authorization with ease.

Clerk does this by providing a range of authentication methods, including social login, password-based, and multifactor authentication. It also gives developers access to a user interface where they can control their applications' authentication and user management settings. Moreover, analytics and reporting capabilities are available to developers to help them keep track of user behavior.

If you enjoyed this article, you might also like our guide to [implementing session-based authentication with React and Express](/blog/building-a-react-login-page-template).

---

# Clerk raises $15m Series A led by Madrona
URL: https://clerk.com/blog/series-a.md
Date: 2023-03-22
Category: Company
Description: Clerk surpasses a million managed users and taps into executive experience from Auth0 and Vercel

Today, we're excited to announce that Clerk has raised a $15 million Series A led by Madrona, just a few months after [our seed](/blog/a16z-seed). We're especially thrilled that [Karan Mehandru](https://www.madrona.com/team-profiles/mehandru-karan) has joined our board, who has tremendous experience in our industry from his time on Auth0's board.

More exciting than fundraising, though, is that Clerk is growing – **and fast:**

![Thumbnail: Clerk raises $15m Series A led by Madrona guide](./5715c0ca9de562c76bb02376aa303bbccc6aee99-1902x702.png)

Our growth accelerated in the new year as word-of-mouth and referrals became our #1 source of new customers. Here is some of the incredible enthusiasm developers are sharing on X:

![Thumbnail: Clerk raises $15m Series A led by Madrona guide](./8bb2940b16b1d504e1be0486ac3aa316d178acca-2358x924.png)

We're so grateful for our early adopters who helped hone Clerk into a streamlined solution – **and then told their friends!** Without them, we simply wouldn't be here today.

Of course, our work is far from done. Throughout the year we'll continue launching new authentication features, like SAML SSO, passkeys, and support for more frameworks.

We're also eager to begin helping developers with *authorization*. We'll expand customer profiles to include information about Roles and Permissions, as well as Billing and Entitlements. We'll solve authorization through components and hooks, same as we did authentication.

As always, if there's anything in particular you'd like to see from Clerk, or if you have feedback or suggestions, we'd love to hear from you. Join our [community discord](https://clerk.com/discord) or tweet at [@clerk](https://x.com/clerk) – we're listening and eager to improve.

### Press Release

**Clerk raises $15 million for React authentication and user management**

Clerk, the drop-in authentication and user management solution for React, today announced a $15 million Series A led by Madrona, with managing director Karan Mehandru joining its board. Mehandru brings a deep understanding of the space from his years on the board of Auth0. Other new investors include Vercel's CEO Guillermo Rauch, Mango Capital, and Auth0's former CRO Dave Wilner. Existing investors Andreessen Horowitz, S28 Capital, and Fathom Capital also participated.

*"The response from React and Next.js developers has been incredible. Monthly active users have grown 500% in the past 5 months, and we've crossed a million managed users,"* said co-founder and CEO, Colin Sidoti. *"When we met Karan, we were excited to tap into his experience in identity and in scaling go-to-market as we target the next generation of application development."*

React and frameworks like Next.js have sparked an architecture shift that will propagate across all application development, and this new architecture requires a tailored suite of developer tools. Hosting and database technologies have already evolved, but before Clerk, authentication was left to be solved in-house.

*“Clerk is the Stripe Checkout of authentication and user management, except it’s built for React,"* said Rauch, who also authors Next.js. *"The best practices built-in to their `<SignIn/>` and `<UserProfile/>` components would take months to implement in-house, yet no sacrifice is made in terms of Enterprise extensibility or customization to your brand.”*

With this new funding, Clerk will accelerate its roadmap beyond authentication. It aims to help developers with authorization, which requires knowledge of a user's role and permissions, as well as their billing plan and entitlements. Clerk will provide the glue that binds these systems together, while also empowering React developers to build authorization controls for their end-users.

*"Clerk is a foundational technology company dedicated entirely to serving the needs of the next generation of application developers as the authentication industry migrates from back-end APIs to front-end componentized modules,"* said Mehandru. *"As this market evolves, Clerk is extremely well positioned to be the "system of action" and the "system of record" for customer identities, much like what Auth0 accomplished in the last decade. I am excited to partner with Colin, his brother and cofounder Braden, and the Clerk team as they empower the developers of the future and build an enduring and exciting company over the next decade and beyond."*

Clerk is actively hiring across product and engineering roles. Please visit [clerk.com/careers](/careers) for more information.

---

# Refactoring our frontend API key: Familiar DX is the best DX
URL: https://clerk.com/blog/refactoring-our-api-keys.md
Date: 2023-01-27
Category: Engineering
Description: We switched to the familiar Publishable Key, but we changed less than you'd think

Like most other developer tools, Clerk's SDKs are configured with two "keys," one for the backend and one for the frontend.

And we share the same core requirements:

- The backend key must have significantly random so it cannot be guessed
- The frontend key is public, so it only needs to be a unique identifier

Although there's no security benefit in doing so, many tools have their frontend key mirror the format and length of their backend key. Take Stripe, for example:

!\[Although there's no security benefit in doing so, many tools have their frontend key mirror the format and length of their backend key. Take Stripe, for example: screenshot]\(./995856b7b27f199531ec648ab53d2866eaeae372-814x332.png '(Don't worry, the secret key has been rolled.)

But as an authentication company, Clerk has an extra requirement for our frontend key, and it meant that mirroring our secret key would cause *performance* issues.

Wait, what? How could a key cause performance issues?

The purpose of a frontend key is to be a unique identifier when interacting with a frontend-facing API. Here's an example of how Stripe's SDK uses the publishable key:

![The purpose of a frontend key is to be a unique identifier when interacting with a frontend-facing API. Here's an example of how Stripe's SDK uses the publishable key: screenshot](./6de4356a8cc28037f37729a941cdfb6e483ce6ef-689x175.png "That's the same publishable key as above!")

Notice how this request is being sent to a **stripe.com** domain? Stripe SDKs always make requests to **stripe.com**, even if you're embedding their elements into your own website.

But since Clerk runs an authentication API and since we're responsible for maintaining sessions, we can't securely do the same. We need to set [HttpOnly cookies](https://owasp.org/www-community/HttpOnly) from a first-party context, which means our API needs to be accessible through our customers' domains. *(Developers configure this by setting a CNAME in their DNS records.)*

When we launched, our frontend key was simply the hostname where our frontend API is hosted. Developers configured it like this in their React apps:

```jsx {{ prettier: false }}
<ClerkProvider frontendApi="clerk.example.com">
```

By passing the API hostname directly, we avoided making an extra, waterfalled API request to exchange a traditional random key with the hostname. This led to faster overall loading speeds.

But we hit an unexpected problem with this strategy: it *really* confused developers. It was a common complaint in friction logs, and when we watched developers integrate Clerk we could see the confusion wash over their face.

A hostname as a frontend key is completely unfamiliar, and no matter what we tried with design and naming, we couldn't get over the hurdle.

So this week, we finally threw in the towel. Our new frontend API keys look just like Stripes:

![So this week, we finally threw in the towel. Our new frontend API keys look just like Stripes: screenshot](./483632fdf53ea7aa1f455e10d9ca7faa463384e5-1006x197.png "Clerk's new publishable key")

But we refuse to exchange developer experience for performance, so there's a not-so-secret subtlety to our new publishable key. Take a closer look:

`pk_test_Y2xlcmsuZXhhbXBsZS5jb20k`

Now, base64-decode the part after `pk_test_`:

`clerk.example.com$`

Indeed, we just base64-encoded the old value and started calling it a publishable key. We added a $ as a simple stop character so we can detect when keys are malformed.

As a final step, we changed the prop name for React:

```jsx {{ prettier: false }}
<ClerkProvider publishableKey="pk_test_Y2xlcmsuZXhhbXBsZS5jb20k">
```

Quick, dirty, and a little silly – but it works! We haven't sacrificed performance and our new publishable key feels much more familiar. Developers have stopped raising their eyebrows and breeze through initial setup.

---

# Validating, Creating, and Styling React-Bootstrap Forms
URL: https://clerk.com/blog/validate-create-style-react-bootstrap-forms.md
Date: 2023-01-25
Category: Guides
Description: Create a complete login and sign-up flow with React Bootstrap forms, then learn how to use tools like Clerk to easily manage authentication and authorization.

Building user interfaces works best by building small components and then combining and reusing them across your application. This ensures that different components in your application are not tightly coupled and encourages reusability. [React.js](https://reactjs.org), or React, is a free and open source JavaScript library that can help you do this.

However, creating a new page in React can be difficult, especially if you have a complex view with multiple components that must be rendered based on various conditions. This is where React-Bootstrap comes in.

React-Bootstrap is a frontend library providing an array of components and many other functionalities out of the box that you can directly import into your website instead of building them from scratch. One such component is bootstrap forms that you can use to easily create and design complex forms in React.

In this article, you'll learn about React-Bootstrap by creating a complete login and sign-up flow with bootstrap forms; you'll then see how to use tools like [Clerk](/) to further ease your life in terms of managing authentication and authorization flow.

Let's start with React-Bootstrap; the next section will talk about what React-Bootstrap forms are and how they can help you increase the quality of your application while saving you time and effort.

## What Is React-Bootstrap?

React-Bootstrap is a frontend component library that provides [pure React components](https://dev.to/sumitkharche/pure-components-in-react-57on). React-Bootstrap includes a plethora of ready-to-use components that you can import directly into your application. It's also very simple to make these components responsive, which would otherwise be a very time-consuming task.

The [documentation for React-Bootstrap](https://react-bootstrap.github.io/docs/getting-started/introduction) is also very well supported and simple to understand, allowing you to get started with a lower learning curve.

### Why Use React-Bootstrap Forms?

There are several scenarios in which you might want to create forms for your website. Perhaps you need a sign-in/sign-up page for your customers to log in, an address page for an e-commerce app, or have some other use case where you want multiple inputs from your app's user. Even something as simple as adding a task to your to-do app requires a form.

Creating complex workflows in forms can be difficult. You must have an array of input types, such as text, password, and so on, as well as various validations based on input types and state attributes. This can eventually lead to hundreds of lines of code that are difficult to understand and debug.

This is where [React-Bootstrap forms](https://react-bootstrap.github.io/docs/forms/overview) shine. With bootstrap forms, you can create a form with minimal code.

In the following sections, you'll learn how to design and build a sign-in/sign-up page using bootstrap forms. Let's get started!

## Implementation

In this section, you'll first see how to create a React project and then use React-Bootstrap to create your own sign-in/sign-up page.

### Project Setup

Before you can start writing code, you must first install React, React-Bootstrap, and other dependencies in your project. If you already have these installed, you can skip this section.

#### Installing npm and Node.js

Run the commands `node —version` and `npm —version` to see if Node.js and npm are already installed. If it says `command not found` for either command, proceed to the next step.

The first thing you need to do is [download a Node.js installer](https://nodejs.org/en/download) for your system. As recommended, download the LTS version. Install and run the software. This will automatically install Node.js and npm for you.

Once you're done installing, run the above commands once more to verify that both npm and Node.js have been correctly installed.

#### Setting Up a React Project

Once you have npm installed, it's easy to create a React project. Just run the command `npx create-react-app <projectname>`. This example will use "bootstraplogin" as the project name.

Once the project is created, you need to install [React-Bootstrap](https://react-bootstrap.github.io/docs/getting-started/introduction). For that, first run `cd bootstraplogin` and then run `npm install react-bootstrap bootstrap`. This will install React-Bootstrap and bootstrap to your project and add them as dependencies in your **package.json** file.

To ensure that everything is working as it should, run the command `npm start` and visit `http://localhost:3000` to ensure that the React default page is getting loaded like this.

![Default React page](./TXXEXU3.jpg)

If this page is loading correctly, your setup is complete.

### Creating a Form Using React-Bootstrap

In the section below, you'll get to grips with form creation using React-Bootstrap. You'll create and style a basic form in which users are asked to provide their email and password in order to sign in.

#### React-Bootstrap Form Basics

Before you dive into implementing your login flow, let's get the basics of bootstrap forms right.

Initially, your **App.js** file, which you can find in the `src` folder of the project, will look something like this:

```js
import logo from './logo.svg'
import './App.css'

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  )
}

export default App
```

You will now update your **App.js** to create a basic form page as described below.

The first thing you need to do is import the React-Bootstrap CSS. For that, add `import 'bootstrap/dist/css/bootstrap.min.css';` to your **index.js** file.

Once that's done, update your **App.js** file to use bootstrap forms as below:

```jsx
import React from 'react'
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'

function App() {
  return (
    <Form>
      <Form.Group className="mb-3" controlId="formBasicEmail">
        <Form.Label>Email address</Form.Label>
        <Form.Control type="email" placeholder="Enter email" />
        <Form.Text className="text-muted">We'll never share your email with anyone else.</Form.Text>
      </Form.Group>

      <Form.Group className="mb-3" controlId="formBasicPassword">
        <Form.Label>Password</Form.Label>
        <Form.Control type="password" placeholder="Password" />
      </Form.Group>
      <Form.Group className="mb-3" controlId="formBasicCheckbox">
        <Form.Check type="checkbox" label="Check me out" />
      </Form.Group>
      <Button variant="primary" type="submit">
        Submit
      </Button>
    </Form>
  )
}

export default App
```

The UI should now look like this.

![Form using React-Bootstrap](./LgxT3uJ.jpg)

The first thing to notice in the above example is that you're importing `Form` from `react-bootstrap/Form`.

`<Form>` is the top-level API provided by React-Bootstrap, which wraps all the functionality required to build forms.

The `<Form.Control>` component renders the input component with bootstrap styling. Forms support different types of inputs like plaintext, email, password, file, or even color as input.
After that, you have `<Form.Group>`, which creates a section inside the form view by wrapping the `<Form.Control>` with its own styling and validations.

There's also `<Form.Label>`, which works as the title for the form group, and `<Form.Text>`, where you can add additional text.

#### Styling with React-Bootstrap

React-Bootstrap provides two main features for users to easily build responsive and well-designed web pages.

1. **The grid system:** Bootstrap's grid system uses a series of containers, rows, and columns to lay out and align content. It's built with [Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes) and is fully responsive. The grid also offers the following:

   - **Containers:** Containers provide a means to center and horizontally pad your site's contents. Use the container feature for a responsive pixel width. You can use containers as the wrapper for your component to correctly align it.
   - **Row:** Row is used to align elements in rows one after another.
   - **Column:** Column is used to align elements as columns inside a row.

![Grid system in React-Bootstrap](./ztkYOZQ.png)

2. **CSS utilities:** Bootstrap provides a [SASS](https://sass-lang.com)-based utility class to add CSS to your components. Using the utility API, you can easily add [various CSS properties](https://getbootstrap.com/docs/4.0/utilities/borders) like border, color, background, etc. You can add these utilities with any component by adding `className` to your component.

For example, `<div className="mb-3 mt-4"/>` provides a bottom margin of 3 pixels and a top margin of 4 pixels for the div.

Now that you've covered the fundamentals, let's create a sign-in/sign-up page for your website. You can find the entire code in [this GitHub repo](https://github.com/himanishm07/loginforclerk).

### Sign-In Page

The first thing to do is create the **SignIn.js** file in your `src` folder and add the below code:

```jsx
import React from 'react'
import { Col, Button, Row, Container, Card, Form } from 'react-bootstrap'

export default function Login() {
  return (
    <Container>
      <Row className="vh-100 d-flex justify-content-center align-items-center">
        <Col md={8} lg={6} xs={12}>
          <div className="border-primary border border-3"></div>
          <Card className="shadow">
            <Card.Body>
              <div className="mt-4 mb-3">
                <h2 className="fw-bold text-uppercase mb-2">Brand</h2>
                <p className="mb-5">Please enter your login and password!</p>
                <Form className="mb-3">
                  <Form.Group className="mb-3" controlId="formBasicEmail">
                    <Form.Label className="text-center">Email address</Form.Label>
                    <Form.Control type="email" placeholder="Enter email" />
                  </Form.Group>

                  <Form.Group className="mb-3" controlId="formBasicPassword">
                    <Form.Label>Password</Form.Label>
                    <Form.Control type="password" placeholder="Password" />
                  </Form.Group>
                  <div className="mb-3">
                    <p className="small">
                      <a className="text-primary" href="#!">
                        Forgot password?
                      </a>
                    </p>
                  </div>
                  <div className="d-grid">
                    <Button variant="primary" type="submit">
                      Login
                    </Button>
                  </div>
                </Form>
                <div className="mt-3">
                  <p className="mb-0 text-center">
                    Don't have an account?{' '}
                    <a href="{''}" className="text-primary fw-bold">
                      Sign In
                    </a>
                  </p>
                </div>
              </div>
            </Card.Body>
          </Card>
        </Col>
      </Row>
    </Container>
  )
}
```

Now, let's see exactly what's happening in the above example.

1. You use a `<Container>` to wrap the component. Containers provide an easy means to center and horizontally pad the content you put inside it.
2. Then, you use a `<Row>` and a `<Col>` element. The former is used to add CSS to the component to orient the entire screen horizontally and align the internal component to center; the latter is used to add breakpoints to ensure a responsive view across any screen size.
3. Next, `<div>` adds a blue horizontal line to the view for which the div is styled using the border utility provided by React-Bootstrap.
4. Next, you have `<Card>` and `<Card.Body>`. Card is a basic container with inherent styling like a rounded border and some padding. Inside the card, you can have a header, footer, and body. In the example, you only use Card.Body.
5. Then there's a `<div>` with className values as `mb-3` and `mt-4`, which provide a bottom margin of 3 pixels and a top margin of 4 pixels to the div.
6. After that, you add an `<h2>` tag for the heading in the view. `fw-bold` sets font weight as bold, `text-uppercase` makes the text uppercase, while `mb-2` is again setting 2 pixels as the bottom margin.
7. After that, you use the `<p>` tag to add text.
8. Next, you want to add inputs for the user to add username and password. For this, you use `<Form>`.
9. In code, there are two groups in `<Form>`: one for the username and the other for the password using `<Form.Group>`. `ControlId` here works as a unique id for the FormGroup.
10. Then, you add `<Form.Label>` to add a heading for the input.
11. `<Form.Control>` converts to the actual HTML input element in the view. `type` is given as `email`, which checks for a valid email address, and `placeholder` is added, which will be shown when the user has not yet provided input.
12. You then repeat steps 10 and 11 but this time for the password. Adding `type` as `Password` for `Form.Control` also ensures that the input is not visible but is added as a `**`.
13. After that, you add a `<div>`, wrapping a clickable link and a `p` tag inside it; the `p` tag has `className` "small" and "text-primary", which sets size and color for the inside text respectively.
14. An anchor tag is used to make the text clickable. You can also add an actual link that provides "Forgot password" functionality by replacing `#` with that link.
15. After that, there's another `<div>` wrapping a clickable button. If you want to find out more, React-Bootstrap provides [an API for buttons](https://react-bootstrap.github.io/docs/components/buttons).
16. Finally, you add another `<div>` with a link that once clicked should open a sign-up page for the user.

Your sign-in page should look like below.

![Sign-in page using React-Bootstrap](./dEx5e1b.jpg)

### Sign-Up Page

The sign-up page has similar components to the sign-in page but requires some extra details, which you'll see below.

First, you need to create the **SignUp.js** file in your `src` folder and add the below code:

```jsx
import React from 'react'
import { InputGroup, Col, Button, Row, Container, Card, Form } from 'react-bootstrap'

export default function SignUp() {
  return (
    <Container>
      <Row className="vh-100 d-flex justify-content-center align-items-center">
        <Col md={10} lg={8} xs={12}>
          <div className="border-primary border border-3"></div>
          <Card className="shadow">
            <Card.Body>
              <div className="mt-4 mb-3">
                <h2 className="fw-bold text-uppercase mb-2">Brand</h2>
                <p className="mb-5">Please enter your details to join us!</p>
                <Form>
                  <Row className="mb-3">
                    <Form.Group as={Col} className="mb-3" controlId="formFullName">
                      <Form.Label className="text-center">Your full name</Form.Label>
                      <Form.Control type="text" placeholder="Enter name" />
                    </Form.Group>

                    <Form.Group as={Col} className="mb-3" controlId="formPhoneNumber">
                      <Form.Label>Phone Number</Form.Label>
                      <Form.Control type="number" placeholder="Enter phone number" />
                    </Form.Group>
                  </Row>
                  <Row className="mb-3">
                    <Form.Group as={Col} className="mb-3" controlId="formUsername">
                      <Form.Label className="text-center">Email address</Form.Label>
                      <InputGroup>
                        <Form.Control type="email" placeholder="Enter username" />
                        <InputGroup.Text className="text-primary">@brand.com</InputGroup.Text>
                      </InputGroup>
                    </Form.Group>

                    <Form.Group as={Col} className="mb-3" controlId="formBasicPassword">
                      <Form.Label>Password</Form.Label>
                      <Form.Control type="password" placeholder="Password" />
                    </Form.Group>
                  </Row>
                  <div className="d-grid">
                    <Button variant="primary" type="submit">
                      Sign Up
                    </Button>
                  </div>
                </Form>
                <div className="mt-3">
                  <p className="mb-0 text-center">
                    Already have an account?{' '}
                    <a href="{''}" className="text-primary fw-bold">
                      Sign In
                    </a>
                  </p>
                </div>
              </div>
            </Card.Body>
          </Card>
        </Col>
      </Row>
    </Container>
  )
}
```

Let's see what each line of code does.

1. Similarly to the sign-in page, you create a container, then add a row and column to it.
2. You again have a div representing the horizontal line just for the view.
3. Then you once more add `<Card>`, including `<Card.Body>`, which captures the header text, and then the `<Form>`.
4. Now you have a `<Row>`, which was not the case in the sign-in page. Row has two `<Form.Group>`s: one for a name and another for a phone number. This is so that these two form groups can be rendered in a single row. Along with adding `<Row>`, you also have to add `as={Col}` to the`<Form.Group>` to align these two form groups in a single row.
5. You again have a `<Row>`, which includes two form groups. These form groups are for users to add their username and password.
6. In the username form group, `<InputGroup>` is added. InputGroup easily extends the Form.Control component by adding text, buttons, or button groups on either side of the input.
7. In the above code, `@brand.com` is added as `<InputGroup>` text, which signifies that the email address for the user will be "[username@brand.com](mailto:username@brand.com)".
8. After that, much like with the sign-in page, you have a clickable button for signing up and a link to move to the sign-in page.

![Sign-up page using React-Bootstrap](./OPNonsY.jpg)

## What's Next?

You saw above how easy it is to create your own sign-in and sign-up page using Bootstrap; however, there are aspects that Bootstrap doesn't automatically take care of, which are essential in creating a secure and functional website. For example, along with asking the user to upload their information, you also need to store that information in your database and validate the username and password when the user tries to log in.

There are various other features like authorization, social logins, and multifactor authentication, which are very complex workflows in themselves where you must ensure that your system's security is never compromised. This means that as the product owner, you must keep up with advancements to ensure a secure product.

In light of these considerations, it might be a good idea for you to delegate the sign-in/sign-up functionality to a SaaS platform like [Clerk](/) that takes care of all these complex tasks for you.

## Using Clerk to Create More Secure Login Forms

Clerk is a tool that makes it simple for you to integrate authentication and authorization functionality with your web and mobile apps without having to worry about low-level security details or even the design of the sign-in/sign-up page.

### Creating a Login Form Using Clerk

Before we get into integrating Clerk into your application, you'll have to create an account. [Sign up with Clerk](https://dashboard.clerk.com/sign-up) and you'll be shown the below page.

![Create a sign-in page using Clerk](./KBjmiuQ.jpg)

Let's go section by section. First, you need your application name. You can add more applications later, so make sure the name is specific to the project you're working on.

After that, you need to specify how you want to authenticate your users. You can choose multiple options based on your requirements. You can also mention the social media platforms you want to use for authentication.

Once you're done, click **Submit** and you'll be directed to your application page. Just click **Try it now** and it will redirect you to your sign-in/sign-up page.

### Integrating Clerk with Your Application

Integrating Clerk with your React application is a simple process. All you have to do is follow the instructions in this article. Let's set it up step by step below.

#### Set Up Clerk in Your Project

1. Go to your project repository in the command line, if you're not already there, using `cd <path_to_your_project>`.
2. Install Clerk React SDK using `npm install @clerk/clerk-react`.
3. Create a file to store `env` variables using the `touch .env.local` command.
4. Run `echo "REACT_APP_CLERK_FRONTEND_API=clerk.sharp.mustang-37.lcl.dev" > .env.local`. This will add `REACT_APP_CLERK_FRONTEND_API` as an `env` variable in your file.

And that's all you need to integrate with Clerk. It's fairly easy, right? Now let's go and make the required code changes to integrate your application with Clerk.

#### Code Changes

Add the below code to the **App.js** file in your `src` folder:

```js
import React from 'react'
import './App.css'
import {
  ClerkProvider,
  SignedIn,
  SignedOut,
  UserButton,
  useUser,
  RedirectToSignIn,
} from '@clerk/clerk-react'

const frontendApi = process.env.REACT_APP_CLERK_FRONTEND_API

function App() {
  return (
    // Wrap your entire app with ClerkProvider
    // Don't forget to pass the frontendApi prop
    <ClerkProvider frontendApi={frontendApi}>
      <SignedIn>
        <Hello />
      </SignedIn>
      <SignedOut>
        <RedirectToSignIn />
      </SignedOut>
    </ClerkProvider>
  )
}

function Hello() {
  // Get the user's first name
  const { user } = useUser()
  return (
    <div className="App-header">
      {/* Mount the UserButton component */}
      <UserButton />
      {user ? <h1>Hello, {user.firstName}!</h1> : null}
    </div>
  )
}
export default App
```

Now let's see what the above code is doing:

1. You import the required dependencies from the Clerk module.
2. `frontendApi` tells the Clerk framework which API endpoint to hit to get the user-related information.
3. The `<ClerkProvider>` component wraps your React application to provide active session and user context. It must always be added to the entrypoint of your React application.
4. The `<SignedIn>` component offers authentication checks for your application. Any child components wrapped by a `SignedIn` component will be rendered only if there's a user signed into an active session in your application. For example, when you want the cart and profile page to be visible only to logged-in users, you can use `<SignedIn>`.
5. Similarly, any child nodes wrapped by a `<SignedOut>` component will be rendered only if the user is not yet signed in to your application.
6. In the `Hello` component, the `useUser` hook gets the information regarding the logged-in user. The `<UserButton>` provides you with a clickable button.
7. In your application, if the user is signed in, it will show the `<Hello />` component, which shows user information and a `<UserButton>`.
8. If the user is signed out, you're just redirecting the app to show the sign-in page using `<RedirectToSignIn>`.

The signed-out view looks like the following.
![Sign-in page using Clerk](./A6u38wZ.jpg)

And here's the signed-in view.

![Sign-out page using Clerk](./JlidDZF.jpg)

### Clerk.com vs. React-Bootstrap

Now that you understand how Clerk works and what features it provides, let's discuss the benefits it offers over implementing your own login flow using React-Bootstrap.

1. **Customized UI:** With Clerk, you can create your sign-in/sign-up pages with just a few clicks. You don't need to worry about creating different components, styling them, and handling their state. Just import the ClerkProvider and you're done.
2. **Offloading complexity of authentication and authorization:** As discussed above, implementing authentication and authorization for your application is a very complex task. Clerk takes care of this complexity for you.
3. **Advanced functionality out of the box:** Along with authentication and authorization, Clerk provides various other functionalities like [social media links and single sign-on (SSO)](/features/social-sso), [multifactor authentication](/features/multifactor-authentication), phone-number- and email-based login, and many other features.
4. **Seamless integration with different projects:** With Clerk, you can integrate your login module with different frontend applications, be it mobile apps, web pages, etc. If you're migrating from one JS framework to another—for example, from React.js to Next.js—you just need to integrate with your new project rather than write all the code again.
5. **Better control and monitoring:** Clerk also provides you with the metadata information like registered users, registration date, and last sign in. You can also integrate with Google Analytics and Firebase for analyzing data.

## Conclusion

In this article, you learned how to create your sign-in and sign-up page, first using React-Bootstrap and then recreating it using Clerk. You also learned various benefits of React-Bootstrap and Clerk and how the latter enables you to completely offload the complex authentication and authorization flow.

Clerk helps you in [building your sign-in](/blog/building-a-react-login-page-template) and sign-up page just with a few clicks. It provides an array of functionalities like social sign in, SSO, multifactor authentication, user information, session management, etc. By doing so, it helps you focus on solving your business problems without worrying about the security of your application.

---

# Adding Google Login to Your Next.js 13 Application
URL: https://clerk.com/blog/add-google-login-next13-app.md
Date: 2023-01-25
Category: Guides
Description: Compare adding Google Login to your Next.js 13 Application by building it yourself, with using a third-party service like Clerk.

External identity providers offer several benefits, such as adding a visibly trusted sign-in option, enabling sign-in without usernames and passwords, and getting additional information about users from the identity provider if they consent to it.

A well-known identity provider seen on many websites is Google login. Users can choose to log in with their Google account on a website, which lets Google authenticate them and return their details to your application.

In this article, you'll learn how to integrate Google login in your Next.js app. First, you'll see how to use a library like [NextAuth.js](https://next-auth.js.org) to manually add a Google login to your website. Then, you'll learn how to use a third-party [authentication provider like Clerk](/nextjs-authentication) to quickly add a Google login to your website. Lastly, this article compares the two methods.

You'll find the code for this article in [this GitHub repository](https://github.com/ivankahl/nextjs-gotham-newspaper).

## Prerequisites

To follow along with this tutorial, you'll need the latest versions of [Node.js](https://nodejs.org/en/download), which, at the time of writing, is 18.12.1, and [npm](https://docs.npmjs.com/cli/v9/configuring-npm/install?v=true), which, at the time of writing, is 9.1.3.

## The Base Project

You can find the base project for this article [here](https://github.com/ivankahl/nextjs-gotham-newspaper/tree/master/nextjs-gotham-newspaper-base). The project is a Next.js 13 application that imitates a news website with a user paywall preventing users from reading articles without being logged in. Tailwind CSS is used to style the different pages and components in the project.

If you browse through the files, you'll notice that the authentication logic is incomplete. The `<LoginButton />` component shows a **Sign In** or **Sign Out** button depending on whether the user is authenticated. However, if you open the **[components/LoginButton.jsx](https://github.com/ivankahl/nextjs-gotham-newspaper/blob/master/nextjs-gotham-newspaper-base/components/LoginButton.jsx#L4)** file, you'll see there's no logic to check if the user is authenticated, and the `loggedIn` variable is always `false`. Similarly, the **[page/index.jsx](https://github.com/ivankahl/nextjs-gotham-newspaper/blob/master/nextjs-gotham-newspaper-base/pages/index.jsx)** file should display a banner prompting the user to log in if they're unauthenticated and the article if the user is authenticated. However, the logic is also missing, and `loggedIn` is always `false`. You'll add this functionality as part of the tutorial.

## Adding Google Login Using NextAuth.js

The first demonstration below shows you how to use the [NextAuth.js](https://next-auth.js.org) library to add a Google Login to your website.

To get started, create a new Next.js app using the base project above as a template. Open a new terminal window and execute the following `npx` command.

```bash
npx create-next-app gotham-newspaper-website-nextauth -e https://github.com/ivankahl/nextjs-gotham-newspaper/tree/master/nextjs-gotham-newspaper-base
```

The command will create a new Next.js application in the `gotham-newspaper-website-nextauth` folder and clone the base project from the GitHub repository.

### Register Your Website in Google Cloud

Before you can add a Google login to your website using NextAuth.js, you must register your website in Google Cloud to get OAuth keys for your application.

Navigate to [Google Cloud](https://cloud.google.com) in your browser. If you have a Google account already, you can click on **Sign in**. Otherwise, click on **Start free** to create a new account.

![A screenshot of the Google Cloud home page with the Sign in and Start free button highlighted](./Ty2XAtk.png)

Once you're logged in, click the **Select a project** dropdown in the top menu. In the dialog box that appears, click **New Project** in the top right of the dialog.

![A screenshot of the dialog box that appears when clicking on the Select a project dropdown](./3XK6PJa.png)

Give your project a descriptive name and click **Create**.

![A screenshot of the Google Cloud interface prompting for a project name and location](./Xo4SoWi.png)

Wait for Google Cloud to finish creating the project, then select the project from the **Select a project** dropdown menu in the top menu. A menu will appear on the left side of the page. Find and hover over the **APIs and services** menu item. In the submenu that appears, click **OAuth consent screen**.

![A screenshot of the menu in Google Cloud with the OAuth consent screen menu item highlighted](./7ixwc5q.png)

Google Cloud will prompt you for **User Type**. Since your application will let anyone log in using Google, select the **External** user type and click on **Create**.

![A screenshot of the Google Cloud console with External User Type selected when configuring the project's OAuth consent screen](./ogSsFCV.png)

In the next screen, provide your website's information, such as the app name and user support email. You can skip the **App domain** section and click the **Save and Continue** button at the bottom of the page.

![A screenshot of Google Cloud showing the fields you need to populate for your app](./38Jiy3k.png)

Now you'll need to configure the OAuth scopes your website will need. Click on **Add or Remove Scopes**. A window should slide out from the right where you can select which scopes your application will need. In this case, since you're using only Google to authenticate the user, select the following scopes:

- ../auth/userinfo.email
- ../auth/userinfo.profile
- openid

Click on the **Update** button and continue using the **Save and Continue** button.

![A screenshot of the Google Cloud console showing the scopes you need to add to your app](./qqijiXe.png)

The screen will prompt you to specify test users for your application while it's still in development. Click **Add Users** and add your Google email address to log in to your website. You can add up to one hundred test users. Then, when you're ready to release your application, you can change it so that anyone can log in.

Once you've added the users, click on **Save and Continue** to continue to the Summary screen.

![A screenshot of Google Cloud showing a screen where you can add test users to your website](./kLLERwh.png)

Now that you've configured your website in Google Cloud, you need to retrieve your Client Key and Client Secret, which you'll use to configure NextAuth.js.

First, click on **Credentials** in the left menu and then on **Create Credentials** at the top of the screen. In the menu that appears, select **OAuth client ID**.

![A screenshot of Google Cloud with the Create Credentials menu open and OAuth client ID highlighted](./Q7KdOj0.png)

Select `Web application` in the **Application type** field and give your application a name. Then, in **Authorised JavaScript origins**, add `http://localhost:3000` as an origin. In **Authorised redirect URIs**, add `http://localhost:3000/api/auth/callback/google` as a callback URL. Finally, click **Create** at the bottom of the page.

![A screenshot of the Google Cloud console showing the configuration options for a web application using OAuth](./oEXLHq6.png)

A dialog should appear with your Client ID and Client Secret. Copy both values and keep them somewhere safe as you'll need them when configuring **NextAuth.js**.

![A screenshot of the dialog box that appears in Google Cloud with the OAuth Client ID and Client Secret for your app](./6EZkDyR.png)

### Install NextAuth.js

Open a terminal instance and navigate to your project directory. Then, run the following command to install the NextAuth.js package in your Next.js app:

```bash
npm install next-auth
```

### Add Google Login to NextAuth.js

Once the package is installed, you need to create an auth endpoint in your Next.js app for NextAuth.js. Create a new subfolder called `auth` in the `pages/api`. Then, create a new file in the folder called **\[...nextauth].js** and paste the following code in it:

```js
import NextAuth from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
  ],
}

export default NextAuth(authOptions)
```

The code snippet above declares an `authOptions` variable containing configuration settings for your website's Google login.

Inside the `authOptions` object, notice how you access the Google ID and Secret as environment variables. To set these environment variables, create a **.env.local** file in the root directory of your project and paste the code below. Replace `<GOOGLE_ID>` and `<GOOGLE_SECRET>` with the Google ID and Secret you retrieved in the previous section when setting up your application in Google Cloud:

```plaintext
GOOGLE_ID=<GOOGLE_ID>
GOOGLE_SECRET=<GOOGLE_SECRET>
```

Lastly, you must set up `SessionProvider` in your Next.js app. This provider will let you use hooks such as the `useSession` hook and manage client sessions across browsers and windows. To do this, open your **pages/\_app.jsx** file and paste the following code:

```jsx
import '../styles/globals.css'
import { SessionProvider } from 'next-auth/react'

const App = ({ Component, pageProps: { session, ...pageProps } }) => (
  <SessionProvider session={session}>
    <Component {...pageProps} />
  </SessionProvider>
)

export default App
```

### Create Sign In with Google Button

Now that you can access the user's session inside your components, create a **Sign In with Google** button, which you'll display on your login page. In your `components` folder, create a new file called **GoogleButton.jsx** and paste the following code.

```jsx
import { signIn } from 'next-auth/react'

const GoogleButton = () => {
  return (
    <button
      className="flex w-full justify-center gap-5 rounded bg-white px-4 py-4 text-sm font-bold drop-shadow-md hover:bg-gray-50"
      onClick={() => signIn('google')}
    >
      <GoogleLogo />
      <div>Sign in with Google</div>
    </button>
  )
}

export default GoogleButton

const GoogleLogo = (props) => (
  <svg
    width="24"
    height="24"
    viewBox="0 0 775 794"
    fill="none"
    xmlns="http://www.w3.org/2000/svg"
    {...props}
  >
    <path
      d="M775 405.797C775 373.248 772.362 349.496 766.653 324.865H395.408V471.773H613.32C608.929 508.282 585.204 563.264 532.482 600.209L531.743 605.127L649.124 696.166L657.256 696.979C731.943 627.921 775 526.315 775 405.797"
      fill="#4285F4"
    />
    <path
      d="M395.408 792.866C502.167 792.866 591.792 757.676 657.256 696.979L532.482 600.209C499.093 623.521 454.279 639.796 395.408 639.796C290.845 639.796 202.099 570.741 170.463 475.294L165.826 475.688L43.772 570.256L42.1759 574.698C107.198 704.013 240.758 792.866 395.408 792.866Z"
      fill="#34A853"
    />
    <path
      d="M170.463 475.294C162.116 450.662 157.285 424.269 157.285 397C157.285 369.728 162.116 343.338 170.024 318.706L169.803 313.46L46.2193 217.373L42.1759 219.299C15.3772 272.961 0 333.222 0 397C0 460.778 15.3772 521.036 42.1759 574.698L170.463 475.294"
      fill="#FBBC05"
    />
    <path
      d="M395.408 154.201C469.656 154.201 519.74 186.31 548.298 213.143L659.891 104.059C591.356 40.2812 502.167 1.13428 395.408 1.13428C240.758 1.13428 107.198 89.9835 42.1759 219.299L170.024 318.706C202.099 223.259 290.845 154.201 395.408 154.201"
      fill="#EB4335"
    />
  </svg>
)
```

The button itself is relatively simple. First, you created a button that contains the Google logo and the text "Sign In with Google". Then, you called the `signIn` method included in NextAuth.js and specified that you want to sign in using Google.

### Update the Login Page and Related Components

Once you've created the Google Button, add the button to your login page. Open the **pages/login.jsx** file and replace the comment `/*Place login form here*/` with the Google button.

You'll also want to prevent logged-in users from accessing the login page. To do this, you can use the `useSession` hook and check the user's authentication status before returning the page. See the changes in the file below:

```jsx
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useSession } from 'next-auth/react'

import GoogleButton from '../components/GoogleButton'
import BrandLogo from '../components/Logo'

const Login = () => {
  // Retrieve the session and router so that we can navigate
  // the user back home if they are already authenticated
  const { status } = useSession()
  const router = useRouter()

  // If the user is authenticated, redirect them to the home
  // page
  if (status === 'authenticated') {
    router.replace('/')
  }

  return (
    <div className="grid h-screen grid-cols-8 overflow-hidden">
      <div
        className="col-span-5 overflow-hidden"
        style={{
          backgroundImage: "url('https://placeimg.com/1000/1000/nature/grayscale')",
          backgroundSize: 'cover',
        }}
      ></div>

      <div className="col-span-3 px-12 py-12">
        <BrandLogo className="mx-auto w-96" />
        <p className="mt-2 text-center">
          Gain immediate access to thousands of news articles from around the world.
        </p>
        <h2 className="mt-12 mb-8 text-2xl font-bold">Sign In</h2>
        <div className="mb-8">
          <GoogleButton />
        </div>
        <Link href="/" className="block text-center text-sm text-gray-500 underline">
          Go Back Home
        </Link>
      </div>
    </div>
  )
}

export default Login
```

You can test your app by running it using the following command in your terminal.

```bash
npm run dev
```

Click on the **Sign In** button on the home page and try to log in using the **Sign In with Google** button. You should be able to select your Google account and be redirected back to your website's home page. However, you'll still see the `LoginBanner` prompting you to sign in.

To fix this, update your `index.jsx` page and your `LoginButton.jsx` component. First, open the **pages/index.jsx** file and paste the code below, which will display the `<Article />` component to logged-in users and the `<LoginBanner />` tag to unauthenticated users:

```jsx
import { useSession } from 'next-auth/react'

import PageHeader from '../components/PageHeader'
import Article from '../components/Article'
import LoginBanner from '../components/LoginBanner'

const Home = () => {
  const { status } = useSession()

  return (
    <div>
      <PageHeader />
      {status === 'authenticated' ? <Article /> : <LoginBanner />}
    </div>
  )
}

export default Home
```

Next, open your **components/LoginButton.jsx** file and paste the following code, which will show authenticated users the **Sign Out** rather than the **Sign In** button:

```jsx
import Link from 'next/link'
import { useSession, signOut } from 'next-auth/react'

const LoginButton = ({ size, signInOnly }) => {
  // Get the user session so you can see if they are authenticated
  // or not.
  const { status } = useSession()

  const padding = size === 'large' ? 'py-2 px-4' : 'py-1 px-2'

  if (status === 'authenticated') {
    return !signInOnly ? (
      <button
        className={`${padding} rounded bg-red-600 text-sm font-bold text-white`}
        onClick={() => signOut()}
      >
        Sign Out
      </button>
    ) : null
  }

  return (
    <Link
      className={`${padding} my-1 rounded bg-red-600 text-sm font-bold text-white`}
      href="/login"
    >
      Sign In
    </Link>
  )
}

export default LoginButton
```

### Test the Google Login

Run your application using the `npm run dev` command.

Once you're authenticated, you can now see the article and a **Sign Out** button. Try logging out and in again to see how the website behaves as a logged-in and logged-out user.

Congratulations! You've implemented a Google login on your website using NextAuth.js.

However, remember that this is only the first piece of the puzzle. You must still develop additional authentication logic, like creating a database to store your registered users. For this, NextAuth.js provides [adapters](https://authjs.dev/reference/core/adapters) that let you store user details in different databases or backend systems. You'll also have to add logic and screens for registering users and, if users can sign in with a password, a "forgot password" screen.

So, while you have a simple website with authentication, some work is still needed to get it production-ready.

## Adding Google Login Using Clerk.com

[Clerk.com](/) is an authentication and user management service that lets you integrate authentication in your Next.js application with minimal effort. It also offers integrations with many third-party identity providers, such as Google.

Create a new project using the same base project mentioned previously. Execute the following `npx` command in a new terminal window.

```bash
npx create-next-app gotham-newspaper-website-clerkdev -e https://github.com/ivankahl/nextjs-gotham-newspaper/tree/master/nextjs-gotham-newspaper-base
```

### Register with Clerk.com

Before you can integrate Clerk.com in your Next.js app, you'll need to sign up for an account and create an application.

![A screenshot of the signup page in Clerk.com](./QgHgM2j.png)

[Sign up for a Clerk.com account](https://dashboard.clerk.com/sign-up). Once you've verified your email address and phone number (if you specified one), you'll land on the Dashboard page, where you can create a new application.

### Create an Application in Clerk.com

On your Clerk.com Dashboard, click on **Add application** to create an application in Clerk.com for your Next.js app.

![A screenshot of the Clerk.com Dashboard with no applications](./6xtu8sC.png)

Clerk.com will prompt you for your application's name. You'll also need to select which methods of authentication you want for your application. After naming your application, switch on **Email** in the **Standard Authentication** section and **Google** under the **Social Connections** header in the **Connected accounts** section.

![A screenshot of Clerk.com showing the interface where you configure sign in for your app](./4Rs67Vv.png)

Click **Finish** to continue to your application screen.

When configuring Clerk.com in your Next.js app, you'll need to specify some environment variables for Clerk.com. You can access these environment variables by clicking on **API Keys** in the left menu.

![A screenshot of the Clerk.com dashboard showing the API Keys for your app](./BGt21r1.png)

Copy your **Frontend API key** somewhere as you'll need it in the next section. You can also click the **Show key** in the **Backend API keys** section and copy that. Finally, copy the **JWT verification key** and store it somewhere handy.

### Install the Clerk.com Package

Clerk.com has created an [npm package](https://www.npmjs.com/package/@clerk/nextjs) that simplifies adding Clerk.com authentication to your Next.js application. Install it in your app using the following command:

```bash
npm install @clerk/nextjs
```

### Configure Clerk.com in the Next.js App

Once the package has finished installing, set up the environment variables needed for Clerk.com. In your project's root directory, create a new file called **.env.local** and paste the following code, but replace `<CLERK_FRONTEND_API>`, `<CLERK_API_KEY>`, and `<CLERK_JWT_KEY>` with the API Keys you retrieved in the Clerk.com Dashboard:

```plaintext
NEXT_PUBLIC_CLERK_FRONTEND_API=<CLERK_FRONTEND_API>
CLERK_API_KEY=<CLERK_API_KEY>
CLERK_JWT_KEY=<CLERK_JWT_KEY>
```

You'll notice that the code snippet prefixes the Clerk Frontend API key with `NEXT_PUBLIC_` so that it [can be accessed on the client side in your Next.js application](https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser).

Now, surround your application in the `<ClerkProvider />` component so your app can check if the user is authenticated and use other features in Clerk.com. To do so, open your **pages/\_app.jsx** file and surround your `<Component />` component with a `<ClerkProvider />` element. The sample below shows the final result:

```jsx
import '../styles/globals.css'
import { ClerkProvider } from '@clerk/nextjs'

const App = ({ Component, pageProps }) => (
  <ClerkProvider {...pageProps}>
    <Component {...pageProps} />
  </ClerkProvider>
)

export default App
```

That's all that's needed to get Clerk.com to work in your Next.js app.

One thing to note is that if you're using Next.js API routes or server-side rendering that relies on authentication, you'll need to [configure the Clerk.com middleware](/docs/nextjs/configure-middleware) as well. However, you can skip this step since this demonstration uses neither of these features.

### Update the Login Page and Related Components

Now that Clerk.com is set up, you can add the `<SignIn />` component to your login page. This component will render the login form, which prompts users to log in with Google login or to use an email address. The page should also redirect authenticated users to the home page.

Open the **pages/login.jsx** file and replace the code there with the following code sample:

```jsx
import { SignIn, useUser } from '@clerk/nextjs'
import Link from 'next/link'
import { useRouter } from 'next/router'
import colors from 'tailwindcss/colors'

import BrandLogo from '../components/Logo'

const Login = () => {
  const { isLoaded, isSignedIn } = useUser()
  const router = useRouter()

  // Redirect the user if they are already authenticated
  if (isLoaded && isSignedIn) router.replace('/')

  return (
    <div className="grid h-screen grid-cols-8 overflow-hidden">
      <div
        className="col-span-5 overflow-hidden"
        style={{
          backgroundImage: "url('https://placeimg.com/1000/1000/nature/grayscale')",
          backgroundSize: 'cover',
        }}
      ></div>

      <div className="col-span-3 px-12 py-12">
        <BrandLogo className="mx-auto w-96" />
        <p className="mt-2 text-center">
          Gain immediate access to thousands of news articles from around the world.
        </p>
        <h2 className="mt-12 mb-8 text-2xl font-bold">Sign In</h2>
        <div className="mb-8">
          <SignIn
            appearance={{
              variables: { colorPrimary: colors.red[600] },
              elements: { rootBox: 'mx-auto' },
            }}
          />
        </div>
        <Link href="/" className="block text-center text-sm text-gray-500 underline">
          Go Back Home
        </Link>
      </div>
    </div>
  )
}

export default Login
```

The `<SignIn />` component [offers customization options](/docs/nextjs/appearance-prop) that let you customize the component's appearance. In this case, the code sets the primary color to the website's primary color and the sign-in form is centered using the `mx-auto` TailwindCSS class.

Run your app using the `npm run dev` command and navigate to your website in a browser. When you click on **Sign In** and are taken to the login page, you'll notice the Clerk.com sign-in component.

![A screenshot of your Gotham website login screen containing the Clerk.com sign-in component](./5cbDied.png)

Test your authentication by clicking on **Continue with Google**. Once you're authenticated, the app should redirect you to the home page.

However, you'll still see the `<LoginBanner />` component instead of the article. Fix this by opening the **pages/index.jsx** file and replacing the code there with the following code sample, which will check whether the user is authenticated and show the appropriate component:

```jsx
import PageHeader from '../components/PageHeader'
import Article from '../components/Article'
import LoginBanner from '../components/LoginBanner'
import { SignedIn, SignedOut } from '@clerk/nextjs'

const Home = () => (
  <div>
    <PageHeader />
    <SignedIn>
      <Article />
    </SignedIn>
    <SignedOut>
      <LoginBanner />
    </SignedOut>
  </div>
)

export default Home
```

The code sample uses the `<SignedIn />` and `<SignedOut />` components from Clerk.com. The `<SignedIn />` component will [display its contents to logged-in users](/docs/component-reference/signed-in) while the `<SignedOut />` component [displays its contents to unauthenticated users](/docs/component-reference/signed-out).

You must also update the `<LoginButton />` component to show authenticated users the **Sign Out** button. Do this by opening the **components/LoginButton.jsx** file and replacing the contents with the following.

```jsx
import Link from 'next/link'
import { useAuth } from '@clerk/nextjs'

const LoginButton = ({ size, signInOnly }) => {
  const { isLoaded, isSignedIn, signOut } = useAuth()

  const padding = size === 'large' ? 'py-2 px-4' : 'py-1 px-2'

  if (isLoaded && isSignedIn && !signInOnly) {
    return (
      <button
        className={`${padding} rounded bg-red-600 text-sm font-bold text-white`}
        onClick={() => signOut()}
      >
        Sign Out
      </button>
    )
  }

  return (
    <Link
      className={`${padding} my-1 rounded bg-red-600 text-sm font-bold text-white`}
      href="/login"
    >
      Sign In
    </Link>
  )
}

export default LoginButton
```

Notice how the code sample above uses the `useAuth` hook instead of the `useUser` hook. The `useAuth` hook is similar to the `useUser` hook. However, it also returns a `signOut` method used in the **Sign Out** button's `onClick` handler to sign the user out.

Rerun your application using the `npm run dev` command. Next, test authenticating and logging out of your Google account. The website should display the article and **Sign Out** button when logged in and the login banner and **Sign In** button when logged out.

## NextAuth.js vs. Clerk.com

As is clear from this tutorial, Clerk.com is easier to implement with fewer and simpler steps than NextAuth.js. It also has some added benefits.

Clerk.com manages the OAuth Keys required to make Google authentication work and lets you add [other social logins](/features/social-sso) such as Facebook, Twitter, GitHub, and more by simply toggling it in your dashboard.

With NextAuth.js, the security burden falls on you as the developer. You're responsible for transporting and saving user credentials. You also need to develop additional functionality like the "forgot password" flow and, where applicable, ensure you're GDPR and CCPA compliant.

Clerk.com [simplifies user management and authentication](/features/passwords) by managing the authentication process and storing your user's credentials securely. It provides standard functionality like "forgot password" out of the box. It also lets you view all the users who have signed up to your website, enable/disable them, and more without any additional development on your part.

Clerk.com is also already [GDPR and CCPA compliant](/features/security). They are regularly audited by third parties and conduct pen testing to ensure their infrastructure and services are secure.

If you want to provide additional security for your users using OTP codes using NextAuth.js, you'll have to develop this logic manually. Clerk.com provides [out-of-the-box support for OTP codes using both SMS and email](/features/email-sms-passcodes-otp).

With Web3 gaining traction, you might want to support logging in with Web3. While NextAuth.js's [Credentials provider](https://next-auth.js.org/providers/credentials#example---web3--signin-with-ethereum) lets you use Web3, it does require [significant development](https://docs.login.xyz/integrations/nextauth.js). Adding [Web3 authentication](/features/web3) to your app with Clerk.com is pretty effortless. Simply enable your application's authentication with Metamask and use it alongside other authentication providers such as Google login and with other Clerk.com features such as OTP codes.

## Conclusion

This article showed you how to integrate Google login manually with NextAuth.js as well as how to do it with Clerk.com's user management service. The code for both methods is in [this GitHub repository](https://github.com/ivankahl/nextjs-gotham-newspaper).If you enjoyed this article, you might also like our guide to [implementing session-based authentication with React and Express](/blog/building-a-react-login-page-template).

Compared to using NextAuth.js, Clerk.com is easier and provides additional benefits like out-of-the-box "forgot password", OTP, and Web3 authentication functionality as well as user management features. It's also GDPR and CCPA compliant.

---

# Guide to Conditional Rendering in React
URL: https://clerk.com/blog/conditional-rendering-react.md
Date: 2023-01-25
Category: Guides
Description: Learn how conditional rendering is used to show a personalized UI, complete a user flow without changing routes, or show a loading or error state.

Conditional rendering is a pattern used in React to render a different user interface (UI) based on whether a condition is `true` or `false`. This can be as simple as rendering a loading message while waiting for an API to return the data, where the loading message is conditionally rendered based on whether the loading is `true` or `false`.

Conditional rendering is quite helpful for showing a personalized UI based on the user's preference, completing a user flow without changing routes, or showing a loading or error state.

In this article, you'll learn about different conditional rendering methods in React applications. You'll use the [Clerk client for React](/docs/reference/clerk-react/installation) to implement a simple dashboard application that conditionally shows the UI based on whether or not the user is signed in.

## What's Conditional Rendering in React?

Conditional rendering in React allows you to implement interactive interfaces where you render different UIs based on the application state; these can range from user inputs to user preferences. Let's dig into conditional rendering with some practical use cases.

### Showing Personalized UIs

This can be based on your preference as a user and is one of the most common uses you'll see out there. Consider [Twitter](https://twitter.com), for example. You'll only see the **Explore** section if you're not signed in to Twitter.

![Twitter hiding sections conditionally](./nxa2ZXD.png)

When you're signed in, you'll see a personalized feed in sections like **Home** and **Messages**.

![Conditionally show personalized feed in Twitter](./x1wicjE.png)

### Completing a User Flow without Changing Routes

This is another use case for conditional rendering. Consider [Typeform](https://www.typeform.com/templates/t/user-persona-survey-template), for example. The application has multiple pages in a single form, but rather than navigating to a new page for every question, it uses conditional rendering to show only the current question.

![Conditional rendering in Typeform](./5cOXLts.jpg)

### Showing a Loading or Error State

This is essential for any application dealing with data loading. Consider Twitter again. See how **Home** and **Trending** both show a loader while fetching the feed.

![Conditional rendering for loading](./0B4g0lH.png)

## Implementing Conditional Rendering on Authentication

There are a few ways to do conditional rendering in React, and you'll learn about five of them in this article. You can follow the examples by setting up this [GitHub repository](https://github.com/Anshuman71/conditional-rendering-clerk) on your local drive.

### "If…else" Statements

You can use the good old `if…else` conditional statement in JavaScript. You can use the `if` condition to return a certain JSX if the user is signed in and something else if they aren't.

```tsx
// AppWithIf.jsx

function App() {
  const isLoggedIn = true
  if (isLoggedIn) {
    return <div className="App">Hello, logged in user!</div>
  } else {
    return <div>User not logged in!</div>
  }
}

export default App
```

### The Conditional (Ternary) Operator

The [conditional (ternary) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) can also be used to check whether the user is signed in and return components accordingly.

The ternary operator is a good choice if you've abstracted your JSX into components:

```tsx
// AppWithTernary.jsx

function SignedIn() {
  return <div className="App">Hello, logged in user!</div>
}

function SignedOut() {
  return <div>User not logged in!</div>
}

function App() {
  const isLoggedIn = true
  return isLoggedIn ? <SignedIn /> : <SignedOut />
}

export default App
```

### The Switch Case

The switch case is useful when you want to conditionally render a component based on a range of choices—for example, if you're conditionally rendering the component based on the user's permission level, as shown below:

```tsx
// AppWithSwitch.jsx

function Viewer() {
  return <div className="App">Hello, logged in as Viewer!</div>
}

function SignedOut() {
  return <div>User not logged in!</div>
}

function Admin() {
  return <div className="App">Hello, logged in as Admin!</div>
}

function Editor() {
  return <div className="App">Hello, logged in as Editor!</div>
}

function App() {
  const userPermission = 'admin'
  switch (userPermission) {
    case 'viewer':
      return <Viewer />
    case 'editor':
      return <Editor />
    case 'admin':
      return <Admin />
    default:
      return <SignedOut />
  }
}

export default App
```

### Immediately Invoked Function Expressions (IIFE)

An IIFE is a good option if you want to use the conditional statement within the `return` statement. In this example, the anonymous function below the `h1` tag runs on every render and returns the JSX based on the user's login state:

```tsx
// AppWithIIFE.jsx

function App() {
  const isLoggedIn = true
  return (
    <div>
      <h1>Amazing application</h1>
      {(function () {
        if (isLoggedIn) {
          return <div className="App">Hello, logged in user!</div>
        } else {
          return <div>User not logged in!</div>
        }
      })()}
    </div>
  )
}

export default App
```

### Higher-Order Components

A [higher-order component](https://reactjs.org/docs/higher-order-components.html) (HOC) is a pattern for reusing component logic. You can use a HOC to abstract the authentication logic with multiple components. In this example, the `withAuth` function is a HOC that takes a component as input and returns another component with conditional rendering based on the user's login state:

```tsx
// AppWithHOC.jsx

import { useEffect, useState } from 'react'

async function getUserAuth() {
  // get Auth state from somewhere
  return true
}

function withAuth(Component) {
  return function ComponentWithAuth() {
    const [isLoggedIn, setLoggedIn] = useState(true)
    useEffect(() => {
      getUserAuth().then((auth) => setLoggedIn(auth))
    }, [])
    return isLoggedIn ? <Component /> : <div>Please log in to view this component.</div>
  }
}

function Dashboard({}) {
  return (
    <div>
      <h1>Amazing application</h1>
      <p>Best dashboard</p>
    </div>
  )
}

const DashboardWithAuth = withAuth(Dashboard)

function App() {
  return <DashboardWithAuth />
}

export default App
```

## Conditional Rendering with Clerk

You've now learned about different ways of using conditional rendering for authentication. You can see that dealing with authentication can get very messy: the more auth state you manage, the more complex the conditionals become. What if there was an authentication provider that takes care of all the conditional rendering for you?

Next you'll see how the Clerk frontend client can be used to implement conditional rendering based on the user's authentication state.

Run the following command in your terminal to create a new React project:

```shell
npx create-react-app clerk-react-conditionals
```

Navigate to the newly made project in your terminal by running the following:

```shell
cd clerk-react-conditionals
```

Now install the Clerk React client using the following command:

```shell
npm install @clerk/clerk-react
```

Log in to your Clerk dashboard and copy the frontend API key from the **API keys** page.

![Copy frontend API key from Clerk](./gCZCwfD.png)

Create the new file **.env.local** and securely save the frontend API key like so:

```bash
REACT_APP_CLERK_FRONTEND_API=<paste-the-key-from-clerk-dashboard>

```

*Note:* Don't commit the **.env.local** file to keep the key safe.

In the **App.js** file, add the `ClerkProvider` at the top level in the `App` component. Pass the `ClerkProvider` with the `frontendApi` prop from the environment variables. The `ClerkProvider` provides the authentication state and methods to all the child components:

```tsx
import React from 'react'
import {
  ClerkProvider,
  SignedIn,
  SignedOut,
  SignOutButton,
  SignInButton,
  useUser,
} from '@clerk/clerk-react'

const frontendApi = process.env.REACT_APP_CLERK_FRONTEND_API

function App() {
  return (
    <ClerkProvider frontendApi={frontendApi}>
      <SignedIn>
        <p style={{ padding: 20 }}>Welcome to the amazing dashboard!</p>
      </SignedIn>
      <SignedOut>
        <p style={{ padding: 20 }}>You must be signed in to use the dashboard.</p>
      </SignedOut>
    </ClerkProvider>
  )
}
```

The `SignedIn` and `SignedOut` components from Clerk conditionally render the children. The `SignedIn` component renders children if the user is signed in, and the `SignedOut` component renders children if they're not. This example renders different messages, but you can use any other component according to your application.

Run `npm start` in your terminal to start the application and open [http://localhost:3000](http://localhost:3000) in a web browser. You should see the following message.

![Signed-out user view](./FqK1818.png)

### Using the React Hook

The dashboard conditionally renders different messages based on a user's sign-in state, but the user still needs the option to sign in. As a good practice, you can put the **Sign in** and **Sign out** buttons at the top `NavBar` component:

```tsx
import React from 'react'
import {
  ClerkProvider,
  SignedIn,
  SignedOut,
  SignOutButton,
  SignInButton,
  useUser,
} from '@clerk/clerk-react'

function NavBar() {
  const { user, isSignedIn } = useUser()
  return (
    <nav
      style={{
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        padding: '10px 20px',
        backgroundColor: 'peachpuff',
      }}
    >
      <h2>Amazing dashboard</h2>
      {isSignedIn ? (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <p style={{ marginRight: 10 }}>Hello, {user.firstName}!</p>
          <SignOutButton />
        </div>
      ) : (
        <SignInButton />
      )}
    </nav>
  )
}
```

The `NavBar` component uses the `useUser` hook from Clerk to get the `user` object and `isSignedIn` Boolean flag. The component renders a navbar with an `h2` "Amazing dashboard" and, depending on the value of `isSignedIn`, displays either a greeting with the user's first name and a `SignOutButton` component or a `SignInButton` component.

Now use the `NavBar` component in the `App`. The final application code should look as shown below:

```tsx
import React from 'react'
import {
  ClerkProvider,
  SignedIn,
  SignedOut,
  SignOutButton,
  SignInButton,
  useUser,
} from '@clerk/clerk-react'

const frontendApi = process.env.REACT_APP_CLERK_FRONTEND_API

function App() {
  return (
    <ClerkProvider frontendApi={frontendApi}>
      <NavBar />
      <SignedIn>
        <p style={{ padding: 20 }}>Welcome to the amazing dashboard!</p>
      </SignedIn>
      <SignedOut>
        <p style={{ padding: 20 }}>You must be signed in to use the dashboard.</p>
      </SignedOut>
    </ClerkProvider>
  )
}

function NavBar() {
  const { user, isSignedIn } = useUser()
  return (
    <nav
      style={{
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        padding: '10px 20px',
        backgroundColor: 'peachpuff',
      }}
    >
      <h2>Amazing dashboard</h2>
      {isSignedIn ? (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <p style={{ marginRight: 10 }}>Hello, {user.firstName}!</p>
          <SignOutButton />
        </div>
      ) : (
        <SignInButton />
      )}
    </nav>
  )
}

export default App
```

Your secured dashboard application can be seen in action below.

## Conclusion

After completing this tutorial, you'll have successfully built a secured dashboard application that conditionally renders specific UIs according to the user's sign-in state. While doing so, you learned about different ways of conditionally rendering the UI in React. You also discovered various components and hooks that Clerk provides to render UIs conditionally depending on the user's authentication status.

[Clerk](/) is a complete user management solution with [pre-built components](/docs/component-reference/overview), hooks, and even a [user interface](/docs/users/user-profile) for user profile management. Clerk is an easy-to-integrate solution for your React applications with first-class support for all modern frameworks such as Next.js, Remix, and RedwoodJS. [Give Clerk a try](https://dashboard.clerk.com/sign-in) for free to explore a complete list of its [features](/docs).

---

# Next.js 13 Routes Part 2: Implementing Protected Routes
URL: https://clerk.com/blog/next13-api-routes-2.md
Date: 2023-01-25
Category: Guides
Description: Learn how to create protected routes using React Context as well as how using Clerk makes this process easier.

In part one of this series, you learned about Next.js API routes and how to protect API routes with JWT authentication. In this part, you'll learn how to create protected routes using React Context as well as how using [Clerk](/) makes this process easier.

## Exploring the Starter App

To get started, a starter app already has been made that you can clone from [GitHub](https://github.com/heraldofsolace/NextJS-protected-routes-demo):

```bash
git clone https://github.com/heraldofsolace/NextJS-protected-routes-demo.git
cd NextJS-protected-routes-demo
yarn install
yarn dev
```

The app will start running at `localhost:3000`.

The posts page can be found at `http://localhost:3000/posts`, which shows a list of all the posts.

![List of all posts](./d2kASE5.png)

This page has a companion API route that returns all the posts in JSON:

```bash
$ curl "http://localhost:3000/api/posts"
[{"id":1,"userId":1,"text":"Hello, World!"},{"id":2,"userId":2,"text":"Hello, NextJS"},{"id":3,"userId":1,"text":"Lorem ipsum dolor sit amet. "},{"id":4,"userId":3,"text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ut rhoncus neque. Sed lacinia magna a mi tincidunt, ac interdum."}]
```

The data for posts is stored in **data.js**.

The `/api/auth/login` route implements JWT authentication and returns a JWT when the correct username and password combination is supplied:

```bash
$ curl -X POST "http://localhost:3000/api/auth/login" -d '{"username": "john", "password": "password"}' -H 'Content-Type: application/json'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjMsImxvY2F0aW9uIjoiRnJhbmNlIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE2Njk0NDUzMDQsImV4cCI6MTY2OTUzMTcwNH0.mlShbJr6xG8TIOyY6B2gD0c-leoyw1T3Sr5EncTgl00"}
```

Finally, the `/api/users/me` route returns the currently authenticated user, provided a valid JWT is passed in the header:

```bash
$ curl http://localhost:3000/api/users/me -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjMsImxvY2F0aW9uIjoiRnJhbmNlIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE2Njk0NDUzMDQsImV4cCI6MTY2OTUzMTcwNH0.mlShbJr6xG8TIOyY6B2gD0c-leoyw1T3Sr5EncTgl00"
{"id":3,"username":"john","name":"John","location":"France"}
```

The user profiles can also be found in **data.js**.

The goal of the article is to protect the `/api/posts` API route and the `/posts` page using the JWT authentication strategy. In part one, you saw how JWT authentication can be added to API routes using the `jwt.verify` method. However, it's resource intensive to manually verify and decode the JWT in every single API route. It's also tedious to manually include the authentication header in every request that you make from a page.

In this article, you'll learn how to "share" an authenticated session across all pages by using an `AuthContext`. You'll also refactor the JWT verification to a `withAuth` wrapper that will make it easy to protect API routes. Finally, you'll see how using Clerk makes this process smooth and seamless.

## Implementing AuthContext

`AuthContext` is simply a [React Context](https://reactjs.org/docs/context.html) that will make it easy to pass required authentication parameters throughout the app. To implement this, first install the required libraries:

```bash
yarn add js-cookie axios
```

`js-cookie` will be used to store the JWT in the browser's cookie. `axios` makes it easy to preconfigure a default API service with headers. You'll use this to include the token in the headers once the user logs in.

Create a file named **api.js** in the project root:

```javascript
import Axios from 'axios'

const api = Axios.create({
  baseURL: 'http://localhost:3000',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
})

export default api
```

Here, an instance of `axios` is created that will be used to make API calls later.

Create the file `auth_context.js`:

```javascript
import React, { createContext, useState, useContext, useEffect } from 'react'
import Cookies from 'js-cookie'
import api from './api'
import jwt from 'jsonwebtoken'

const JWT_KEY = process.env.JWT_SECRET_KEY
const AuthContext = createContext({})

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    async function fetchUserFromCookie() {
      const token = Cookies.get('token')
      if (token) {
        api.defaults.headers.Authorization = `Bearer ${token}`
        const { data: user } = await api.get('api/users/me')
        if (user) setUser(user)
      }
      setIsLoading(false)
    }
    fetchUserFromCookie()
  }, [])

  const login = async (username, password) => {
    const {
      data: { token },
    } = await api.post('api/auth/login', { username, password })
    console.log('TOKEN ', token)
    if (token) {
      Cookies.set('token', token, { expires: 60 })
      api.defaults.headers.Authorization = `Bearer ${token}`
      const { data: user } = await api.get('api/users/me')
      setUser(user)
    }
  }

  const logout = () => {
    Cookies.remove('token')
    setUser(null)
    delete api.defaults.headers.Authorization
    window.location.pathname = '/login'
  }

  return (
    <AuthContext.Provider
      value={{
        isAuthenticated: !!user,
        user,
        login,
        loading: isLoading,
        logout,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)
```

The most important bits in the above Context are the `fetchUserFromCookie`, `login`, and `logout` functions. The `fetchUserFromCookie` function fetches the token from the cookie and sets the `authorization` header in the default `api` instance.

It then makes a call to `/api/users/me` to retrieve and store the authenticated user. The `login` function logs in the user through the `/api/auth/login` route and stores the token in the cookie. The `logout` function deletes the user, the cookie, and the `authorization` header.

Finally, create the `withAuth` function that'll protect the API routes by wrapping the handlers:

```javascript
export const withAuth = (handler) => {
  return (req, res) => {
    const { authorization } = req.headers
    if (!authorization)
      return res.status(401).json({ error: 'The authorization header is required' })
    const token = authorization.split(' ')[1]

    jwt.verify(token, JWT_KEY, (err, payload) => {
      if (err) return res.status(401).json({ error: 'Unauthorized' })
      req.auth = { user: payload }
      handler(req, res)
    })
  }
}
```

You can now modify `pages/api/posts.js` to include `withAuth`:

```javascript
import { withAuth } from '../../auth_context'
import { users, posts } from '../../data'

export default withAuth((req, res) => {
  const { userId } = req.auth.user

  const userPosts = posts.filter((post) => {
    return post.userId == userId
  })

  res.status(200).json(userPosts)
})
```

Note that only the posts by the logged in user are returned. The logged in user is found through `req.auth.user`.

Let's now create the login page. First, add [Formik](https://formik.org) and [Yup](https://www.npmjs.com/package/yup). These are not strictly required but will help in creating the login form.

```bash
yarn add formik yup
```

Create `pages/login.js`:

```javascript
import React from 'react'
import { Formik, ErrorMessage, Form, Field } from 'formik'
import * as Yup from 'yup'
import { useAuth } from '../auth_context'
import { useRouter } from 'next/router'
import api from '../api'

const LoginForm = () => {
  const { login } = useAuth()
  const router = useRouter()
  return (
    <Formik
      initialValues={{ username: '', password: '' }}
      validationSchema={Yup.object({
        username: Yup.string().required('Required'),
        password: Yup.string().required('Required'),
      })}
      onSubmit={async ({ username, password }, { setSubmitting, setErrors }) => {
        try {
          await login(username, password)
          setSubmitting(false)
          router.push('/posts')
        } catch (error) {
          const formikErrors = { password: error.response.data.error }
          setErrors(formikErrors)
          setSubmitting(false)
        }
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          <div>
            <label>Username</label>
            <br />
            <Field name="username" type="text"></Field>
            <ErrorMessage name="username" component="p"></ErrorMessage>
          </div>
          <div>
            <label>Password</label>
            <br />
            <Field name="password" type="password"></Field>
            <ErrorMessage name="password" component="p"></ErrorMessage>
          </div>
          <button type="submit" disabled={isSubmitting}>
            Login
          </button>
        </Form>
      )}
    </Formik>
  )
}

export default LoginForm
```

This page uses the `login` function described above to log in the user.

Now update `pages/posts.js` to make use of `AuthContext`:

```javascript
import { useEffect, useState } from 'react'
import api from '../api'
import { useAuth } from '../auth_context'

export default function Posts() {
  const [posts, setPosts] = useState(null)
  const { user, logout } = useAuth()
  console.log(user)
  useEffect(() => {
    async function fetchPosts() {
      const { data: postsList } = await api.get('api/posts')
      setPosts(postsList)
      console.log(postsList)
    }
    if (user) fetchPosts()
  }, [user])

  return (
    <>
      <ul>
        {posts?.map((post) => {
          return <li key={post.id}>{post.text}</li>
        })}
      </ul>
      <button onClick={logout}>Logout</button>
    </>
  )
}
```

Finally, update `pages/_app.js` to wrap everything in `AuthProvider`:

```javascript
import '../styles/globals.css'
import { AuthProvider } from '../auth_context'

function MyApp({ Component, pageProps }) {
  return (
    <AuthProvider>
      <Component {...pageProps} />
    </AuthProvider>
  )
}

export default MyApp
```

Start the server with `yarn dev` and visit `http://localhost:3000/login`. Log in with the credentials (you can find the username in **data.js** and the password is "password") and you'll be redirected to the posts page. You can verify that you're only seeing posts from the logged in user.

The final app for this section can be found in the `manual` branch of the [GitHub repo](https://github.com/heraldofsolace/NextJS-protected-routes-demo.git).

## Authentication with Clerk

In this section, you'll replace the manual authentication with [Clerk](/). Before starting with Clerk, you should revert the changes you've made so far. You can simply run `git stash && git clean -fdx` to stash your changes.

First, [create an application](/docs/authentication/set-up-your-application) in Clerk. You can keep all the default options.

![Creating an application in Clerk](./vvJUUtJ.png)

Once the application is created, go to the **API keys** page and copy the frontend API key, the backend API key, and the JWT verification key. Paste these into `.env`:

```
NEXT_PUBLIC_CLERK_FRONTEND_API=your_frontend_api_key
CLERK_API_KEY=your_backend_api_key
CLERK_JWT_KEY=your_jwt_key
```

Install the required library:

```bash
yarn add @clerk/nextjs
```

Wrap `pages/_app.js` with `Clerkprovider`:

```javascript
import { ClerkProvider, SignedIn, SignedOut, RedirectToSignIn } from '@clerk/nextjs'
import { useRouter } from 'next/router'

const publicPages = ['/']

function MyApp({ Component, pageProps }) {
  const { pathname } = useRouter()
  const isPublicPage = publicPages.includes(pathname)

  return (
    <ClerkProvider {...pageProps}>
      {isPublicPage ? (
        <Component {...pageProps} />
      ) : (
        <>
          <SignedIn>
            <Component {...pageProps} />
          </SignedIn>
          <SignedOut>
            <RedirectToSignIn />
          </SignedOut>
        </>
      )}
    </ClerkProvider>
  )
}

export default MyApp
```

The `publicPages` array decides which pages will not be protected under authentication. For a protected page, if the user is not signed in, they will be redirected to the login page.

Create the file **middleware.js** in the project route:

```javascript
import { withClerkMiddleware } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export default withClerkMiddleware((req) => {
  return NextResponse.next()
})

// Stop Middleware running on static files
export const config = { matcher: '/((?!.*\\.).*)' }
```

Modify `pages/api/posts.js` to include the `getAuth` function that authenticates the user with Clerk:

```javascript
import { posts } from '../../data'
import { getAuth } from '@clerk/nextjs/server'

export default function handler(req, res) {
  const { userId } = getAuth(req)
  const userPosts = posts.filter((post) => {
    return post.userId == userId
  })

  res.status(200).json(userPosts)
}
```

In the Clerk dashboard, go to the **Users** page and create a test user.

![Creating a new user](./qKmIBQi.png)

After the user is created, open the record and copy the ID as shown below.

![Copying user ID](./a1f73gA.png)

Open **data.js** and replace the numeric `id` field of any one user and the `userId` field in the `posts` array:

```javascript
export const users = [
  {
    id: 'user_2IUxt1YsiCfebtlUwOe5dU91Det',
    username: 'bob',
    name: 'Bob',
    location: 'USA',
    password: '$2y$10$mj1OMFvVmGAR4gEEXZGtA.R5wYWBZTis72hSXzpxEs.QoXT3ifKSq',
  },
  // ...
]

export const posts = [
  {
    id: 1,
    userId: 'user_2IUxt1YsiCfebtlUwOe5dU91Det',
    text: 'Hello, World!',
  },
  // ...
  {
    id: 3,
    userId: 'user_2IUxt1YsiCfebtlUwOe5dU91Det',
    text: 'Lorem ipsum dolor sit amet. ',
  },
  // ...
]
```

Run the server again with `yarn dev`. Visit `http://localhost:3000/posts` and you should be redirected to Clerk's login page.

![Clerk's login page](./j8qJYJV.png)

After logging in, you'll be redirected back to the `/posts` page. Verify that you can see only the posts corresponding to the logged in user.

![The posts page](./IYQC9tp.png)

## Conclusion

Protecting Next.js routes with authentication is a vital part of developing any web app. However, manually creating authentication mechanisms can be tedious and time-consuming. A solution like Clerk’s [Next.js authentication](/nextjs-authentication) comes with all the bells and whistles so that you don't need to worry about auth and can focus on the core app instead.

---

# Next.js 13 Routes Part 1: Getting Started with Next.js API Routes
URL: https://clerk.com/blog/next13-api-routes-1.md
Date: 2023-01-25
Category: Guides
Description: API routes in Next.js provide a solution to build server-side API logic.

An API route creates a server-side bundle separate from the client-side bundle. As is usual for Next.js pages, file-based routing is used to create API routes, where files inside `pages/api` are mapped to `/api/*` routes.

Apart from building the backend logic, you can use API routes to run logic that depends on a secret you don't want to expose to the client—for example, connecting to a database through a database URL string. Since API routes are server-side only, you don't have to worry about exposing the database credentials to the client.

You can also use API routes to mask external service URLs. For example, if you call `https://some-service/foo`, you can route it through an API route such as `/api/foo` to hide the actual URL used.

In this article, you'll get hands-on experience in building API routes while learning about different types of API routes in Next.js.

## Implementing API Routes in Next.js

To follow this tutorial, make sure you have [Node.js 14.6.0](https://nodejs.org) or newer. This article uses [Yarn](https://yarnpkg.com) as the package manager, but you can also use npm. If you'd like to see the code of the finished application, you can find it in this [GitHub repo](https://github.com/heraldofsolace/nextjs-api-demo).

First, create a Next.js app:

```sh
yarn create next-app
```

Once you're prompted, choose a name for your app—for example, `api-routes-demo`. Select **No** when asked if you want to use TypeScript. You can keep the default answers to all the other questions. After the app is created, move into the app directory. You can delete the **pages/api/hello.js** file that was created by default.

To work with the app, you'll need some sample data. To keep things simple, you'll use a static data set, but in a real-world app, you'll likely use a database.

Create the file **data.js** in the project root:

```jsx
export const users = [
  {
    id: 1,
    username: 'bob',
    name: 'Bob',
    location: 'USA',
  },
  {
    id: 2,
    username: 'alice',
    name: 'Alice',
    location: 'Sweden',
  },
  {
    id: 3,
    username: 'john',
    name: 'John',
    location: 'France',
  },
]

export const posts = [
  {
    id: 1,
    userId: 1,
    text: 'Hello, World!',
  },
  {
    id: 2,
    userId: 2,
    text: 'Hello, NextJS',
  },
  {
    id: 3,
    userId: 1,
    text: 'Lorem ipsum dolor sit amet. ',
  },
  {
    id: 4,
    userId: 3,
    text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ut rhoncus neque. Sed lacinia magna a mi tincidunt, ac interdum.',
  },
]

export const comments = [
  {
    id: 1,
    postId: 1,
    userId: 2,
    text: 'Hi there!',
  },
  {
    id: 2,
    postId: 1,
    userId: 3,
    text: 'Lorem ipsum',
  },
  {
    id: 3,
    postId: 2,
    userId: 1,
    text: 'Nulla bibendum risus sed vestibulum lobortis. Fusce.',
  },
  {
    id: 4,
    postId: 2,
    userId: 1,
    text: 'In ut nulla vitae dolor scelerisque lacinia. ',
  },
  {
    id: 5,
    postId: 2,
    userId: 1,
    text: 'Praesent semper enim eu ligula rutrum finibus.',
  },
]
```

## Basics of API Routes

The heart of the API routes is the pages/api directory. Any file in this directory is mapped to a route of the form `/api/*`. So, the file `pages/api/foo.js` will get mapped to `/api/foo`. To make the API route work, you'll need to export a default function from the file, as shown below:

```jsx
export default function handler(req, res) {}
```

The function receives two arguments:

- `req`: An instance of [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage), with some [pre-built middleware](https://nextjs.org/docs/api-routes/request-helpers)
- `res`: An instance of [http.ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse), with some [helper functions](https://nextjs.org/docs/api-routes/response-helpers)

Before proceeding, keep these two things in mind:

1. Next.js 13 introduced a new [app directory](https://beta.nextjs.org/docs/routing/fundamentals#the-app-directory) that offers an improved routing system over the traditional `pages` directory. However, *API routes should still be [defined in the pages/api directory](https://beta.nextjs.org/docs/data-fetching/api-routes)*. As this article is being written, the Next.js team hasn't decided yet how API routes will look like in the `app` directory. So this article will use the usual `pages/api` directory structure, but keep in mind it may change in the future.
2. Next.js offers a beta version of [Edge API routes](https://nextjs.org/docs/api-routes/edge-api-routes), which use the [Edge Runtime](https://nextjs.org/docs/api-reference/edge-runtime). These API routes are often faster than their Node.js runtime counterparts but come with limitations such as not having access to native Node.js APIs. This article will not discuss Edge API routes and will only deal with standard API routes.

### Static Routes

A static route is a fixed, predefined route that matches a single path verbatim. Next.js offers two equivalent ways of creating static routes. If you want a static route `/api/foo`, you can do one of the following:

1. Create the file **foo.js** in `pages/api`, or
2. Create the file **index.js** in a directory named `foo` in `pages/api`

Even though both approaches are equivalent, the second approach is much easier to work with if you have nested or dynamic routes under that particular route, as you'll see later.

Let's create two static routes—`/api/users` and `/api/posts`—and see both approaches in action. First, create a `users` directory in `pages/api` and create an **index.js** file in this directory with the following code:

```jsx
import { users } from '../../../data'

export default function handler(req, res) {
  res.status(200).json(users)
}
```

As you can see, the `handler` function returns the list of users with a `200` status. You can test this route by sending a GET request to `localhost:3000/api/users`:

```sh
$ curl <http://localhost:3000/api/users>
[{"id":1,"username":"bob","name":"Bob","location":"USA"},{"id":2,"username":"alice","name":"Alice","location":"Sweden"},{"id":3,"username":"john","name":"John","location":"France"}]

```

You'll take the second approach for the `/api/posts` route. Create the file `posts.js` in `pages/api`:

```jsx
import { posts } from '../../data'

export default function handler(req, res) {
  res.status(200).json(posts)
}
```

Similar to the previous route, this one returns the list of all posts. A similar `GET` request can be used to test this route:

```sh
$ curl <http://localhost:3000/api/posts>
[{"id":1,"userId":1,"text":"Hello, World!"},{"id":2,"userId":2,"text":"Hello, NextJS"},{"id":3,"userId":1,"text":"Lorem ipsum dolor sit amet. "},{"id":4,"userId":3,"text":"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur ut rhoncus neque. Sed lacinia magna a mi tincidunt, ac interdum."}]

```

It's possible to pass query parameters to routes such as `/api/foo?bar=baz`. These query parameters can be accessed in the handler function through the `req.query` object. Let's see this by passing a `limit` query parameter to the `/api/posts` route. Modify `posts.js` with the following code:

```jsx
import { posts } from '../../data'

export default function handler(req, res) {
  const { limit } = req.query
  res.status(200).json(posts.slice(0, limit))
}
```

Now you can pass the `limit` parameter to limit the number of results:

```sh
$ curl "<http://localhost:3000/api/posts?limit=2>"
[{"id":1,"userId":1,"text":"Hello, World!"},{"id":2,"userId":2,"text":"Hello, NextJS"}]

```

### Dynamic Routes

Fixed static routes may not be always enough for complex routing needs. For example, if you have multiple users, it's not convenient to set up individual static routes for each user, such as `/api/users/1`, `/api/users/2`, and so on. To solve this, you need to use dynamic routes, where one or more segments work as parameters that the user can pass.

For example, a route like `/api/users/[id]` can match `/api/users/xxx`, where `xxx` can be any valid URL component. The actual parameter passed through the URL can be accessed by the name assigned in the route (`id` in this example), and the appropriate record can be fetched from the database.

To create a dynamic segment, you need to add square brackets (`[ ]`) around the name of the file. For example, **pages/api/users/\[id].js** will be mapped to the route `/api/users/[id]`. Just like in the case of static routes, you can also create a directory named `[id]` and create **index.js** inside it. Both approaches are precisely equivalent. The parameter `id` can be accessed in the handler function through the `req.query` object:

```jsx
const { id } = req.query
```

It's possible to have multiple dynamic segments by using a nested directory structure. For example, `pages/api/users/[id]/[postId].js` will be mapped to `/api/users/[id]/[postId]`, and both id and postId can be accessed via `req.query`:

```json
{ "id": "1", "param": "foo" }
```

Remember that a route parameter will override a query parameter with the same name. So in the route `/api/users/1?id=foo`, `req.query.id` will be `1` and not `foo`.

Create a directory `[id]` in `pages/api/users` and create `index.js` inside it, which will map to the `/api/users/[id]` route. Write the following code in `index.js`:

```jsx
import { users } from '../../../../data'

export default function handler(req, res) {
  const { id } = req.query
  const user = users.find((user) => {
    return user.id == id
  })
  if (typeof user !== 'undefined') return res.status(200).json(user)
  res.status(404).json({ error: 'Not found' })
}
```

This function finds the appropriate user from the array and returns it. If no user with the specified ID is found, a "Not found" error is returned instead. Test the route with the following request:

```sh
$ curl <http://localhost:3000/api/users/3>
{"id":3,"username":"john","name":"John","location":"France"}

```

### Nested Routes

It's possible to create nested routes in Next.js by simply nesting directories. A directory structure like `pages/api/foo/bar.js` or `pages/api/foo/bar/index.js` will be mapped to `/api/foo/bar`. Let's create a `posts` route under `/api/users/[id]`, which will return all posts by the specified user.

Create the file `posts.js` in `pages/api/users/[id]` with the following code:

```jsx
import { users, posts } from '../../../../data'

export default function handler(req, res) {
  const { id } = req.query
  const user = users.find((user) => {
    return user.id == id
  })
  if (typeof user === 'undefined') return res.status(404).json({ error: 'Not found' })

  const userPosts = posts.filter((post) => {
    return post.userId == id
  })

  res.status(200).json(userPosts)
}
```

This function first finds the appropriate user and then filters the posts array to find posts with the particular `userId`. Test that it works correctly:

```sh
$ curl <http://localhost:3000/api/users/1/posts>
[{"id":1,"userId":1,"text":"Hello, World!"},{"id":3,"userId":1,"text":"Lorem ipsum dolor sit amet. "}]
```

### Catch-All Routes

Catch-all routes are similar to dynamic routes, but whereas in a dynamic route, the dynamic segment matches only that particular part of the route, in a catch-all route, it matches all paths under that route. This can be created by adding three dots (`...`) inside the square brackets. So, a route like `/api/foo/[...bar]` will match `/api/foo/a`, `/api/foo/a/b`, `/api/foo/a/b/c`, and so on. However, it won't match `/api/foo`—i. e. , the path parameter cannot be omitted.

If you'd like the path parameter to be optional, you can convert it to an optional catch-all route by using two square brackets (`[[ ]]`). So, `/api/foo/[[...bar]]` matches `/api/foo/a`, `/api/foo/a/b`, `/api/foo/a/b/c`, and so on, as well as `/api/foo`. The path parameters can be accessed through `req.query` as before, but in this case, it will be an array of one or more elements if parameters are passed or an empty object if the parameter is omitted.

Let's create an optional catch-all route `/api/comments/[[...ids]]`, which will do the following:

- Return all comments if no parameter is passed
- Return all comments under post `xx` for a route like `/api/comments/xx`
- Return all comments by user `yy` under post `xx` for a route like `/api/comments/xx/yy`

Create the directory `comments` in `pages/api` and create the file **\[\[...ids]].js** inside:

```jsx
import { users, posts, comments } from '../../../data'

export default function handler(req, res) {
  const { ids } = req.query
  if (Array.isArray(ids)) {
    if (ids.length > 2) {
      return res.status(400).json({ error: 'There cannot be more than two parameters ' })
    }
    if (ids.length === 1) {
      const postId = ids[0]
      const postComments = comments.filter((comment) => {
        return comment.postId == postId
      })
      return res.status(200).json(postComments)
    }
    if (ids.length === 2) {
      const [postId, userId] = ids
      const postUserComments = comments.filter((comment) => {
        return comment.postId == postId && comment.userId == userId
      })
      return res.status(200).json(postUserComments)
    }
  }
  return res.status(200).json(comments)
}
```

You can test this route with zero or more parameters:

```sh
$ curl <http://localhost:3000/api/comments> # Returns all comments
[{"id":1,"postId":1,"userId":2,"text":"Hi there!"},{"id":2,"postId":1,"userId":3,"text":"Lorem ipsum"},{"id":3,"postId":2,"userId":1,"text":"Nulla bibendum risus sed vestibulum lobortis. Fusce."},{"id":4,"postId":2,"userId":1,"text":"In ut nulla vitae dolor scelerisque lacinia. "},{"id":5,"postId":2,"userId":1,"text":"Praesent semper enim eu ligula rutrum finibus."}]

$ curl <http://localhost:3000/api/comments/1> # All comments of post 1
[{"id":1,"postId":1,"userId":2,"text":"Hi there!"},{"id":2,"postId":1,"userId":3,"text":"Lorem ipsum"}]

$ curl <http://localhost:3000/api/comments/1/2> # All comments of post 1 and user 2
[{"id":1,"postId":1,"userId":2,"text":"Hi there!"}]

```

## Adding JWT Authentication

If you're working on a project where your API routes should be kept private, you'll need to protect them from unauthorized access. [JWT authentication](https://jwt.io/introduction) is one of the most commonly used authentication mechanisms for securing APIs. You'll now add JWT authentication to the `/api/users` route so that the users need to log in before accessing that route.

First, stop the server if it's running. Install the required dependencies with `yarn add bcryptjs jsonwebtoken`. The `bcrypt` library is used to hash and compare the passwords, and the `jsonwebtoken` library is used to create and verify the JWT. Create an ENV file with the following content:

```sh
JWT_SECRET_KEY = mysecret
```

This key will be used to sign the JWT, so in an actual application, this should be a random string and kept secret.

Next, open `data.js` and add a `password` key to all the users. For simplicity, add the same password to all the users:

```
password: '$2y$10$mj1OMFvVmGAR4gEEXZGtA.R5wYWBZTis72hSXzpxEs.QoXT3ifKSq' // Add this key to all users
```

The value in quotes is the hashed version of the string "password".

Next, create `auth.js` inside `pages/api`. This file will log the user in and generate the token. Start with the necessary imports:

```jsx
import { users } from '../../data'
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

const JWT_KEY = process.env.JWT_SECRET_KEY
```

Ensure that only the `POST` method is allowed:

```jsx
// ...
export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })
}
```

The next step is to extract the username and password from the body and ensure that a user with the username exists:

```jsx
const { username, password } = req.body

if (!username || !password)
  return res.status(400).json({ error: 'Username and password are required' })

const user = users.find((user) => {
  return user.username === username
})

if (!user) return res.status(404).json({ error: 'User not found ' })
```

Then, use `bcrypt` to compare the password in the request with the actual password:

```jsx
const { password: userPassword, id, location, name } = user
const match = await bcrypt.compare(password, userPassword)
if (!match) return res.status(401).json({ error: 'Wrong password' })
```

If the passwords are a match, sign and send the JWT:

```jsx
const payload = { userId: id, location, name }

jwt.sign(payload, JWT_KEY, { expiresIn: 24 * 3600 }, (err, token) => {
  res.status(200).json({ token })
})
```

The entire file should look like this:

```jsx
import { users } from '../../data'
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'

const JWT_KEY = process.env.JWT_SECRET_KEY

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })

  const { username, password } = req.body
  if (!username || !password)
    return res.status(400).json({ error: 'Username and password are required' })

  const user = users.find((user) => {
    return user.username === username
  })

  if (!user) return res.status(404).json({ error: 'User not found ' })

  const { password: userPassword, id, location, name } = user
  const match = await bcrypt.compare(password, userPassword)
  if (!match) return res.status(401).json({ error: 'Wrong password' })

  const payload = { userId: id, location, name }
  jwt.sign(payload, JWT_KEY, { expiresIn: 24 * 3600 }, (err, token) => {
    res.status(200).json({ token })
  })
}
```

Let's test this route. Start the server with yarn dev and send a request to `localhost:3000/api/auth`:

```sh
$ curl -X POST "<http://localhost:3000/api/auth>" -d '{"username": "bob", "password": "password"}' -H 'Content-Type: application/json'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImxvY2F0aW9uIjoiVVNBIiwibmFtZSI6IkJvYiIsImlhdCI6MTY2OTEwMDI3OCwiZXhwIjoxNjY5MTg2Njc4fQ.0EFxEtqR2-rZij-PqD66bWatC_kiMl6QDpNlaqyjaLQ"}

```

Note that you'll likely get a different token.

You can now use this token to check the authenticity of the user. Let's protect the `/api/users` route with JWT. Replace the content in **pages/api/users/index.js** with the following:

```jsx
import { users } from '../../../data'
import jwt from 'jsonwebtoken'

const JWT_KEY = process.env.JWT_SECRET_KEY

export default function handler(req, res) {
  const { authorization } = req.headers

  if (!authorization) return res.status(401).json({ error: 'The authorization header is required' })
  const token = authorization.split(' ')[1]

  jwt.verify(token, JWT_KEY, (err, payload) => {
    if (err) return res.status(401).json({ error: 'Unauthorized' })
    return res.status(200).json(
      users.map((user) => {
        return { ...user, password: '' }
      }),
    )
  })
}
```

The above code checks the `Authorization` header for the JWT. If a token is passed, the `jwt.verify` function is invoked to check the token's authenticity. Once authenticated, the data is returned (with the password field stripped from the users—you wouldn't want to expose the passwords, would you?).

Test this route by passing the `Authorization` header to the request:

```sh
$ curl <http://localhost:3000/api/users> -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImxvY2F0aW9uIjoiVVNBIiwibmFtZSI6IkJvYiIsImlhdCI6MTY2OTEwMDI3OCwiZXhwIjoxNjY5MTg2Njc4fQ.0EFxEtqR2-rZij-PqD66bWatC_kiMl6QDpNlaqyjaLQ" # Replace with your token
[{"id":1,"username":"bob","name":"Bob","location":"USA","password":""},{"id":2,"username":"alice","name":"Alice","location":"Sweden","password":""},{"id":3,"username":"john","name":"John","location":"France","password":""}]

```

An invalid token will raise the following error:

```sh
$ curl <http://localhost:3000/api/users> -H "Authorization: Bearer: invalid"
{"error":"Unauthorized"}
```

As you can see, adding JWT authentication to API routes is an involved process that can be complicated. Using something like Clerk’s [Next.js authentication solution](/nextjs-authentication) makes the process much easier since it does the heavy lifting of authentication, taking the task off your plate.

With Clerk, authentication is as simple as importing the `getAuth` function:

```jsx
import { getAuth } from '@clerk/nextjs/server'
```

Calling it inside the handler function is also easy, which will automatically authenticate the user and return the user ID:

```jsx
const { userId } = getAuth(req)
```

You'll learn more about authentication with Clerk in part two of this series.

## Conclusion

Next.js API routes offer extreme flexibility in keeping your server-side logic close to the frontend and creating an API application. With the many different types of API routes available, it's critical to implement proper authentication to protect the routes from bad actors. However, adding authentication can be a complex and daunting task. This is why solutions like [Clerk](/) exist to make it as easy and seamless as possible.

---

# Clerk and Create T3 Turbo
URL: https://clerk.com/blog/clerk-t3-turbo.md
Date: 2022-12-28
Category: Guides
Description: This guide shows you how to integrate Clerk into T3-Turbo so you can have user management for everyone.

T3-Turbo is one of the easiest ways to get both Type safety while using Next.js and Expo. The biggest missing feature is the ability to share authentication between your applications.

This guide shows you how to integrate Clerk into T3-Turbo so you can have user management for everyone.

> For comprehensive Expo authentication without the T3 stack, [learn more about our Expo support](/expo-authentication).

If you don’t want to do this manually, use the [starter repo](https://github.com/clerkinc/t3-turbo-and-clerk). It is
open source and free.

## Assumptions

There are a few assumptions here when working through this guide.

- You understand TRPC and its functionality.
- You are familiar with Expo.
- You have a working knowledge of Turborepo

## Setup

### Create your free Clerk account

You need a free Clerk account to use everything in this guide, so head to [https://dashboard.clerk.com](https://dashboard.clerk.com) and sign up for your free account.

### Create a new application in the dashboard

You will need to create a new application, name it and select Discord as a social sign-in.

If you change any setting here, you may need to update your Expo code to handle any requirements you change.

![Clerk T3 Turbo guide illustration](./033608f71ab9631e79b9c3569e92bb808e6dc213-3918x1924.png)

###

![Clerk T3 Turbo guide illustration](./c00fd14484c9b3f888f03d00f543923351f66a34-3920x2244.png)

### Update .env to use your keys

Under the dashboard, you will need your API keys and create a copy of the `.env.example` as `.env`

![Clerk T3 Turbo guide illustration](./f244212d6116858dbef13ee31ecf14af91fd759e-3914x2182.png)

## Adding global env to Turbo

We want to have our env available in many parts of our application. You can add the following to our `turbo.json` .

```json {{ prettier: false }}
"globalEnv": [
  "DATABASE_URL",
  "NEXT_PUBLIC_CLERK_FRONTEND_API",
  "CLERK_API_KEY",
  "CLERK_JWT_KEY"
]
```

###

Update Next.js package.json

We need to be able to access the .env when working in development, so replace the dev script with the following:

```json {{ prettier: false }}
"with-env": "dotenv -e ../../.env --",
"dev": "pnpm with-env next dev",
```

##

## Updating our Next.js application

First, we will work on our Next.js application, whose only change is [adding Clerk](/nextjs-authentication), so we have powerful user authentication.

### Install

```bash
pnpm install @clerk/nextjs --filter @acme/nextjs
```

Now that the Clerk package has been installed in our Next.js application, we need to wrap our application in the `<ClerkProvider>`, which gives us access to the authentication state throughout our application.

Open up your `_app.tsx` file and modify the code.

```tsx
import '../styles/globals.css'
import type { AppType } from 'next/app'
import { ClerkProvider } from '@clerk/nextjs'
import { trpc } from '../utils/trpc'

const MyApp: AppType = ({ Component, pageProps: { ...pageProps } }) => {
  return (
    <ClerkProvider {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default trpc.withTRPC(MyApp)
```

## Add Middleware.ts

Clerk uses Next.js middleware to allow your application to keep track of authentication behind the scenes.

```tsx
import { withClerkMiddleware } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export default withClerkMiddleware((_req: NextRequest) => {
  return NextResponse.next()
})

// Stop Middleware running on static files
export const config = {
  matcher: [
    /*
     * Match request paths except for the ones starting with:
     * - _next
     * - static (static files)
     * - favicon.ico (favicon file)
     *
     * This includes images, and requests from TRPC.
     */
    '/(.*?trpc.*?|(?!static|.*\\..*|_next|favicon.ico).*)',
  ],
}
```

You will notice the matcher at the bottom, which ensures that middleware doesn’t run on every request. This will scope it to TRPC + API routes leaving your images and static files alone.

## Embedding our UI components

Clerk provides highly customizable components from Sign-up to Organization management. They require zero form creation or state management, making them easy to implement. Each component will use the [Next.js optional catch all route](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes). This allows you to redirect the user inside your application using OAuth providers.

### Sign in

```jsx
import { SignIn } from '@clerk/nextjs'

const SignInPage = () => (
  <main className="flex h-screen flex-col items-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
    <div className="flex flex-col items-center justify-center gap-4">
      <div className="container flex flex-col items-center justify-center gap-12 px-4 py-8">
        <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">Sign In</h1>
        <SignIn path="/sign-in" routing="path" signUpUrl="/sign-up" />
      </div>
    </div>
  </main>
)

export default SignInPage
```

### Sign up

```jsx
import { SignUp } from '@clerk/nextjs'

const SignUpPage = () => (
  <main className="flex h-screen flex-col items-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
    <div className="flex flex-col items-center justify-center gap-4">
      <div className="container flex flex-col items-center justify-center gap-12 px-4 py-8">
        <h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">Sign In</h1>
        <SignUp path="/sign-up" routing="path" signInUrl="/sign-in" />
      </div>
    </div>
  </main>
)

export default SignUpPage
```

### Updating index.tsx

The final step for Next.js is to update our `index.tsx` to use Clerk. Once we update this page, users will have a way to sign in, sign out and see data from TRPC in the future.

We need to import a prebuilt component from Clerk and a hook that allow us to access the current auth state.

`import { useAuth, UserButton } from "@clerk/nextjs";`

### What is useAuth?

The `useAuth` hook is a convenient way to access the current auth state. This hook provides the minimal information needed for data-loading, such as the user id and helper methods to manage the current active session.

### What is UserButton?

Initially popularized by Google, users have come to expect that little photo of themselves in the top-right of the page – it’s the access point to manage their account, switch accounts, or sign out.

The `<UserButton/>` component renders this familiar user button UI. It renders a clickable user avatar - when clicked, the full UI opens as a popup. Here is an example

![Clerk T3 Turbo guide illustration](./12da04075590eb1282144bf1b8ede1a59a634988-942x1052.png)

### Updating AuthShowcase

The `AuthShowCase` component in the `index.tsx` file needs to be updated to use the `useAuth()` hook, allow the user to sign in, and show our UserButton if the user is signed in.

```tsx {{ prettier: false }}
const AuthShowcase: React.FC = () => {
  const { isSignedIn } = useAuth()
```

Now we can update our secret message from `{ enabled: !!session?.user }` to `{ enabled: isSignedIn }`

```tsx {{ prettier: false }}
const AuthShowcase: React.FC = () => {
  const { isSignedIn } = useAuth()
  const { data: secretMessage } = trpc.auth.getSecretMessage.useQuery(
    undefined,
    { enabled: isSignedIn },
  )
```

Now the final change is to use `isSignedIn` in our return statement to conditionally show the secret message and UserButton component or show a sign-in link. We can do that by replacing `{session?.user && (` with `isSignedIn` and underneath, providing the false statement using `!isSignedIn` this is where we can place a Link to our mounted `/sign-in page`.

```jsx
{
  isSignedIn && (
    <>
      <p className="text-center text-2xl text-white">
        {secretMessage && (
          <span>
            {' '}
            {secretMessage} click the user button!
            <br />
          </span>
        )}
      </p>
      <div className="flex items-center justify-center">
        <UserButton
          appearance={{
            elements: {
              userButtonAvatarBox: {
                width: '3rem',
                height: '3rem',
              },
            },
          }}
        />
      </div>
    </>
  )
}
```

The `UserButton` component allows users to manage their accounts, including signing out or updating their profile and password or linking a new account through OAuth providers. I added some customization to make the UserButton larger.

To learn more about customization, go to our documentation where we explain how to use the [appearance
prop](/docs/component-customization/appearance-prop)

The final step is to add a login text that pushes the user to the mounted sign-in page.

```jsx
{
  !isSignedIn && (
    <p className="text-center text-2xl text-white">
      <Link href="/sign-in">Sign In</Link>
    </p>
  )
}
```

Below is the final AuthShowcase.

```tsx
const AuthShowcase: React.FC = () => {
  const { isSignedIn } = useAuth()
  const { data: secretMessage } = trpc.auth.getSecretMessage.useQuery(undefined, {
    enabled: !!isSignedIn,
  })

  return (
    <div className="flex flex-col items-center justify-center gap-4">
      {isSignedIn && (
        <>
          <p className="text-center text-2xl text-white">
            {secretMessage && (
              <span>
                {' '}
                {secretMessage} click the user button!
                <br />
              </span>
            )}
          </p>
          <div className="flex items-center justify-center">
            <UserButton
              appearance={{
                elements: {
                  userButtonAvatarBox: {
                    width: '3rem',
                    height: '3rem',
                  },
                },
              }}
            />
          </div>
        </>
      )}
      {!isSignedIn && (
        <p className="text-center text-2xl text-white">
          <Link href="/sign-in">Sign In</Link>
        </p>
      )}
    </div>
  )
}
```

Now that the Next.js work is done, we should update our TRPC API to use Clerk instead of NextAuth, so we can test this work and get ready to update Expo.

## Updating API package to use Clerk

Currently, the API package has TRPC context, and middleware uses Auth.js (previously NextAuth). We can replace this with Clerk so we can use it both with Expo and Next.js.

You can delete the Auth package in the packages folder completely and update the Prisma schema to remove the user
data. None of this is needed to use Clerk makes sure you remove any references if you do this

### Updating TRPC context

The context is found at `packages/api/src/context.ts`. In this package, we will add the ability to detect the user being signed in and retrieve the full User object. First, we must remove the auth package import and add `getAuth` , `clerkClient`, `SignedInAuthObject` and `SignedOutAuthObject` as a type.

```tsx {{ del: [1], ins: [2, 3] }}
import { getServerSession, type Session } from '@acme/auth'
import { getAuth } from '@clerk/nextjs/server'
import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/nextjs/api'
```

### What is `getAuth` ?

The `getAuth()` helper retrieves the authentication state and can be used anywhere within a next.js server. It provides information needed for data-loading, such as the user id and can be used to protect your API routes.

### Updating `createContext` function

For the `createContext` function, we want to pass around the user object anywhere in our application. For this, we can create an async function called `getUser` that retrieves the `userId` and subsequently retrieves the user data.

```tsx
export const createContext = async (opts: CreateNextContextOptions) => {
  return await createContextInner({ auth: getAuth(opts.req) })
}
```

Now we can retrieve a user, and if not, we will return `null` we can pass this to our `createContextInner` in case you need it for testing without a request object.

```tsx
type AuthContextProps = {
  auth: SignedInAuthObject | SignedOutAuthObject
}
/** Use this helper for:
 *  - testing, where we dont have to Mock Next.js' req/res
 *  - trpc's `createSSGHelpers` where we don't have req/res
 * @see https://beta.create.t3.gg/en/usage/trpc#-servertrpccontextts
 */
export const createContextInner = async ({ auth }: AuthContextProps) => {
  return {
    auth,
    prisma,
  }
}
```

## Updating TRPC middleware

The TRPC middleware found in `/packages/api/src/trpc.ts` needs a small update to the `isAuthed` function. The if statement becomes `!ctx.auth.userId`, and the return becomes `auth: ctx.auth`

```tsx
import { initTRPC, TRPCError } from '@trpc/server'
import { type Context } from './context'
import superjson from 'superjson'

const t = initTRPC.context<Context>().create({
  transformer: superjson,
  errorFormatter({ shape }) {
    return shape
  },
})

const isAuthed = t.middleware(({ next, ctx }) => {
  if (!ctx.auth.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' })
  }
  return next({
    ctx: {
      auth: ctx.auth,
    },
  })
})

export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(isAuthed)
```

Our Next.js application can be tested with TRPC, including the secret message that uses protected routes. Give it a test.

## Updating our Expo application

The Expo application currently doesn’t support authentication; the good news is Clerk supports Expo with our `@clerk/expo` package.

### Install `@clerk/expo` package and secure-store

`pnpm install @clerk/clerk-expo expo-secure-store --filter @acme/expo`

### Create a Token Cache

We need a token cache here, which is why we installed `expo-secure-store` that allows you to store key pair values on the device. Create a new file under `/src/utils/` named `cache.ts` and paste in the following code:

```tsx
import * as SecureStore from 'expo-secure-store'
import { Platform } from 'react-native'

export async function saveToken(key: string, value: string) {
  // console.log("Save token", key, value);
  await SecureStore.setItemAsync(key, value)
}

export async function getToken(key: string) {
  const value = await SecureStore.getItemAsync(key)
  return value
}

// SecureStore is not supported on the web
// https://github.com/expo/expo/issues/7744#issuecomment-611093485
export const tokenCache =
  Platform.OS !== 'web'
    ? {
        getToken,
        saveToken,
      }
    : undefined
```

I won’t explain this code above as it’s an expo-specific package, but this allows us to store the JWT securely and not in memory.

### Add `<ClerkProvider>`

Similar to our Next.js application, we need to wrap up our `_app.tsx` in the `<ClerkProvider>` to allow access to auth state anywhere in the application. What is different here is we need use our newly created `TokenCache`, and we need to provide the Clerk frontend API directly to the `<ClerkProvider />`

```jsx
import { StatusBar } from 'expo-status-bar'
import React from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { TRPCProvider } from './utils/trpc'

import { HomeScreen } from './screens/home'
import { ClerkProvider } from '@clerk/clerk-expo'
import { tokenCache } from './utils/cache'

// Find this in your Dashboard.
const clerk_frontend_api = 'FRONT_END_API'

export const App = () => {
  return (
    <ClerkProvider frontendApi={clerk_frontend_api} tokenCache={tokenCache}>
      <TRPCProvider>
        <SafeAreaProvider>
          <HomeScreen />
          <StatusBar />
        </SafeAreaProvider>
      </TRPCProvider>
    </ClerkProvider>
  )
}
```

### Protecting pages

Our Expo package provides an easy way to gate content using `SignedIn` and `SignedOut`, which will gate the content depending on the user's current state.

```jsx
import { StatusBar } from 'expo-status-bar'
import React from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { TRPCProvider } from './utils/trpc'

import { HomeScreen } from './screens/home'
import { ClerkProvider, SignedIn, SignedOut } from '@clerk/clerk-expo'
import { tokenCache } from './utils/cache'

// Find this in your Dashboard.
const clerk_frontend_api = 'FRONT_END_API'

export const App = () => {
  return (
    <ClerkProvider frontendApi={clerk_frontend_api} tokenCache={tokenCache}>
      <SignedIn>
        <TRPCProvider>
          <SafeAreaProvider>
            <HomeScreen />
            <StatusBar />
          </SafeAreaProvider>
        </TRPCProvider>
      </SignedIn>
      <SignedOut></SignedOut>
    </ClerkProvider>
  )
}
```

## Implementing a way to sign in.

With Clerk Expo, you must implement your pages to sign in or sign up as a user. We will use Discord, but you can use any social or standard sign-in you want. First, we need to install `expo-auth-session` to handle sessions.

`pnpm install expo-auth-sessions --filter @acme/expo`

### Creating a Sign In component

Create a folder called `components` and then create a file called `SignInWithOAuth.tsx`. This will be our component that you can easily extend to use more providers or swap out Discord for something else.

```jsx
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'
import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()
}
```

Here we are using another helper, which allows you to sign in as a user and set the session after the fact. We need to make sure Clerk is fully loaded before we attempt a sign in so we can use the `isLoaded` as part of the `useSignIn` helper.

```jsx
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null
}
```

Now we need to create our function that will run when a user taps the button to sign in:

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
```

Next we need to tell where to redirect to after a successful or unsuccessful login attempt via Discord,`expo-auth-session` provides a helper to make a redirect, which is called `makeRedirectUri`, and set it to `/oauth-native-callback`

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })
```

At this point, we can start our sign in attempt using `signIn.create` passing in our strategy, which is `oauth_discord`, and our newly created `redirectUrl`

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })
```

The `SignIn` object holds all the state of the current sign in and provides helper methods to navigate and complete the sign in process. You can read about this in our [documentation](/docs/reference/clerkjs/signin), as you may want to use the different sign in methods in the future.

The next part of the sign in is to retrieve the external redirect URL and use AuthSession. This will give us the ability to know if the OAuth was successful or not.

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        firstFactorVerification: { externalVerificationRedirectURL },
      } = signIn

      const result = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL?.toString() || '',
        returnUrl: redirectUrl,
      })

      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { type, params } = result || {}
      if (type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }
```

You may notice that we have an eslint to disable the checks on the next line. There is an assumption in this example that it will always be successful, but you can use the following [AuthSession](https://docs.expo.dev/versions/latest/sdk/auth-session/#returns-7) documentation to implement anything you may want to handle.

We now need to retrieve `rotatingTokenNonce` and reload our `signIn` object. This will allow us to get the `sessionId` and sign the user in.

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        firstFactorVerification: { externalVerificationRedirectURL },
      } = signIn

      const result = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL.toString(),
        returnUrl: redirectUrl,
      })

      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { type, params } = result || {}
      console.log
      if (type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }

      // Get the rotatingTokenNonce from the redirect URL parameters
      const { rotating_token_nonce: rotatingTokenNonce } = params

      await signIn.reload({ rotatingTokenNonce })
```

Finally we can retrieve the `createdSessionId` from the `signIn` object and set that to our current session. This will let us know that the user is signed in with a valid session.

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        firstFactorVerification: { externalVerificationRedirectURL },
      } = signIn

      const result = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL?.toString() || '',
        returnUrl: redirectUrl,
      })

      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { type, params } = result || {}
      if (type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }

      // Get the rotatingTokenNonce from the redirect URL parameters
      const { rotating_token_nonce: rotatingTokenNonce } = params

      await signIn.reload({ rotatingTokenNonce })

      const { createdSessionId } = signIn

      if (!createdSessionId) {
        throw 'Something went wrong during the Sign in OAuth flow. Please ensure that all sign in requirements are met.'
      }

      await setSession(createdSessionId)
      return
    }
```

Finally we can add a catch for anything that might go wrong that we aren’t handling and return the error.

```jsx {{ prettier: false }}
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        firstFactorVerification: { externalVerificationRedirectURL },
      } = signIn

      const result = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL?.toString() || '',
        returnUrl: redirectUrl,
      })

      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { type, params } = result || {}
      if (type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }

      // Get the rotatingTokenNonce from the redirect URL parameters
      const { rotating_token_nonce: rotatingTokenNonce } = params

      await signIn.reload({ rotatingTokenNonce })

      const { createdSessionId } = signIn

      if (!createdSessionId) {
        throw 'Something went wrong during the Sign in OAuth flow. Please ensure that all sign in requirements are met.'
      }

      await setSession(createdSessionId)
      return
    } catch (err) {
      console.log(JSON.stringify(err, null, 2))
      console.log('error signing in', err)
    }
  }
```

Our sign in method is now complete, so we can create a simple UI that, on touch, will attempt a sign in.

```jsx
return (
  <View className="rounded-lg border-2 border-gray-500 p-4">
    <Button title="Sign in with Discord" onPress={handleSignInWithDiscordPress} />
  </View>
)
```

Our Sign In component is now complete below is the full code. We will use this for a Sign Up component for anyone who doesn’t have an account yet for our application.

```jsx
import { useSignIn } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignInWithOAuth = () => {
  const { isLoaded, signIn, setSession } = useSignIn()

  if (!isLoaded) return null

  const handleSignInWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signIn.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        firstFactorVerification: { externalVerificationRedirectURL },
      } = signIn

      const result = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL.toString(),
        returnUrl: redirectUrl,
      })

      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { type, params } = result || {}
      console.log
      if (type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }

      // Get the rotatingTokenNonce from the redirect URL parameters
      const { rotating_token_nonce: rotatingTokenNonce } = params

      await signIn.reload({ rotatingTokenNonce })

      const { createdSessionId } = signIn

      if (!createdSessionId) {
        throw 'Something went wrong during the Sign in OAuth flow. Please ensure that all sign in requirements are met.'
      }

      await setSession(createdSessionId)

      return
    } catch (err) {
      console.log(JSON.stringify(err, null, 2))
      console.log('error signing in', err)
    }
  }

  return (
    <View className="rounded-lg border-2 border-gray-500 p-4">
      <Button title="Sign in with Discord" onPress={handleSignInWithDiscordPress} />
    </View>
  )
}

export default SignInWithOAuth
```

## Create a Sign Up component

The good news is that the sign in component and the sign up component is very similar and 99% of the code we created can be reused and swapped for `useSignUp` hook. To save you from reading more content, here is the code.

```tsx
import { useSignUp } from '@clerk/clerk-expo'
import React from 'react'
import { Button, View } from 'react-native'

import * as AuthSession from 'expo-auth-session'

const SignUpWithOAuth = () => {
  const { isLoaded, signUp, setSession } = useSignUp()

  if (!isLoaded) return null

  const handleSignUpWithDiscordPress = async () => {
    try {
      const redirectUrl = AuthSession.makeRedirectUri({
        path: '/oauth-native-callback',
      })

      await signUp.create({
        strategy: 'oauth_discord',
        redirectUrl,
      })

      const {
        verifications: {
          externalAccount: { externalVerificationRedirectURL },
        },
      } = signUp

      const result = await AuthSession.startAsync({
        authUrl: externalVerificationRedirectURL!.toString(),
        returnUrl: redirectUrl,
      })
      console.log(result)
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { type, params } = result || {}
      console.log
      if (type !== 'success') {
        throw 'Something went wrong during the OAuth flow. Try again.'
      }

      // Get the rotatingTokenNonce from the redirect URL parameters
      const { rotating_token_nonce: rotatingTokenNonce } = params

      await signUp.reload({ rotatingTokenNonce })
      const { createdSessionId } = signUp

      if (!createdSessionId) {
        throw 'Something went wrong during the Sign up OAuth flow. Please ensure that all sign in requirements are met.'
      }

      await setSession(createdSessionId)

      return
    } catch (err) {
      console.log(JSON.stringify(err, null, 2))
      console.log('error signing up', err)
    }
  }

  return (
    <View className="my-8 rounded-lg border-2 border-gray-500 p-4">
      <Button title="Sign Up with Discord" onPress={handleSignUpWithDiscordPress} />
    </View>
  )
}

export default SignUpWithOAuth
```

### Creating a Sign in, Sign Up screen

We now need to use our components in our application. Under the screens folder, create a new file called `signInSignUp.tsx` and paste the following code. We are displaying the components we created so the code below is generic React Native code.

```jsx
import React from 'react'

import { View, SafeAreaView } from 'react-native'

import SignInWithOAuth from '../components/SignInWithOAuth'
import SignUpWithOAuth from '../components/SignUpWithOAuth'

export const SignInSignUpScreen = () => {
  return (
    <SafeAreaView className="bg-[#2e026d] bg-gradient-to-b from-[#2e026d] to-[#15162c]">
      <View className="h-full w-full p-4">
        <SignUpWithOAuth />
        <SignInWithOAuth />
      </View>
    </SafeAreaView>
  )
}
```

### Updating \_app.tsx

We can now update our `SignedOut` control component to use the `SignInSignUpScreen` so if a user doesn’t have a valid session and is not signed in, they will be able to sign in.

```jsx
import { StatusBar } from 'expo-status-bar'
import React from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { TRPCProvider } from './utils/trpc'

import { HomeScreen } from './screens/home'
import { SignInSignUpScreen } from './screens/signInSignUp'
import { ClerkProvider, SignedIn, SignedOut } from '@clerk/clerk-expo'
import { tokenCache } from './utils/cache'

// Find this in your Dashboard.
const clerk_frontend_api = 'YOUR_API_KEY'

export const App = () => {
  return (
    <ClerkProvider frontendApi={clerk_frontend_api} tokenCache={tokenCache}>
      <SignedIn>
        <TRPCProvider>
          <SafeAreaProvider>
            <HomeScreen />
            <StatusBar />
          </SafeAreaProvider>
        </TRPCProvider>
      </SignedIn>
      <SignedOut>
        <SignInSignUpScreen />
      </SignedOut>
    </ClerkProvider>
  )
}
```

### Updating TRPC client

The TRPC client found under `utils/trpc` needs a very small update. As part of TRPC, you can provide headers as part of `httpBatchLink` . We can get the JWT token by using `getToken`, which is part of the `useAuth` hook.

First, add the import `import { useAuth } from "@clerk/clerk-expo";` , then, inside our TRPCProvider, add the `getToken` code before the client.

```tsx {{ prettier: false }}
export const TRPCProvider: React.FC<{
  children: React.ReactNode
}> = ({ children }) => {
  const { getToken } = useAuth()
  const [queryClient] = React.useState(() => new QueryClient())
......

```

Then finally, add the Authorization header to our `httpBatchLink`

```jsx {{ prettier: false }}
async headers() {
  const authToken = await getToken()
  return {
    Authorization: authToken,
  }
},
```

This will allow you to send a valid token with your requests to the TRPC backend. Below is the full code:

```tsx
import { createTRPCReact } from '@trpc/react-query'
import type { AppRouter } from '@acme/api'
/**
 * Extend this function when going to production by
 * setting the baseUrl to your production API URL.
 */
import Constants from 'expo-constants'
/**
 * A wrapper for your app that provides the TRPC context.
 * Use only in _app.tsx
 */
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import { transformer } from '@acme/api/transformer'
import { useAuth } from '@clerk/clerk-expo'

/**
 * A set of typesafe hooks for consuming your API.
 */
export const trpc = createTRPCReact<AppRouter>()

const getBaseUrl = () => {
  /**
   * Gets the IP address of your host-machine. If it cannot automatically find it,
   * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm
   * you don't have anything else running on it, or you'd have to change it.
   */
  const localhost = Constants.manifest?.debuggerHost?.split(':')[0]
  if (!localhost) throw new Error('failed to get localhost, configure it manually')
  return `http://${localhost}:3000`
}

export const TRPCProvider: React.FC<{
  children: React.ReactNode
}> = ({ children }) => {
  const { getToken } = useAuth()
  const [queryClient] = React.useState(() => new QueryClient())
  const [trpcClient] = React.useState(() =>
    trpc.createClient({
      transformer,
      links: [
        httpBatchLink({
          async headers() {
            const authToken = await getToken()
            return {
              Authorization: authToken,
            }
          },
          url: `${getBaseUrl()}/api/trpc`,
        }),
      ],
    }),
  )

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </trpc.Provider>
  )
}
```

Go ahead and test everything. At this point, you have a working application that uses TRPC

## Final Updates to our TRPC API

The final update here is to update the `post` router to use a `protectedProcedure` for creation so that when a user uses the Expo application, they will need to be logged in to create a new post. Open the `post.ts` under `/packages/api/src/router` and update the `create` to use `protectedProcedure` .

```jsx
import { router, publicProcedure, protectedProcedure } from '../trpc'
import { z } from 'zod'

export const postRouter = router({
  all: publicProcedure.query(({ ctx }) => {
    return ctx.prisma.post.findMany()
  }),
  byId: publicProcedure.input(z.string()).query(({ ctx, input }) => {
    return ctx.prisma.post.findFirst({ where: { id: input } })
  }),
  create: protectedProcedure
    .input(z.object({ title: z.string(), content: z.string() }))
    .mutation(({ ctx, input }) => {
      return ctx.prisma.post.create({ data: input })
    }),
})
```

## Next Steps

Now you have a working T3 Turbo application that has authentication both in Next.js and Expo with protected routes. Here are some next steps you might want to implement:

- Additional login methods for Expo ( more social logins or email and password screen)
- Use one of our [integrations](/docs/guides/development/integrations/overview) to use a database, such as Neon, Supabase, or Firebase.
- Join our [Discord](https://clerk.com/discord)

---

# Clerk raises $6.2m led by Andreessen Horowitz
URL: https://clerk.com/blog/a16z-seed.md
Date: 2022-11-30
Category: Company
Description: Clerk is building the future of authentication for React, where the Component is the new API

Today, we're thrilled to announce $6.2m in new funding led by Martin Casado at Andreessen Horowitz, with continued participation from S28 Capital, Fathom Capital, and South Park Commons. The formal press release is available below.

This funding marks a major milestone for Clerk. For the first time, we've raised based on the traction of our customers, who are simply blowing us away with their continued success. Here's a chart showing cumulative sign ups across all customer applications since we launched:

![Cumulative customer sign-ups chart](./33e2503da8bdd68d27f8100245b0e13c0faf8a3d-1940x762.png)

Our key insight at Clerk is that the Component is the new API. In fact, over 95% of our customers use **all** of our `<SignUp/>`, `<SignIn/>`, and `<UserProfile/>` components! Of the few who implement our APIs directly, most will build a custom sign-up flow with our APIs, but still use our `<SignIn/>` and `<UserProfile/>` components for efficiency.

This outcome is obvious in hindsight. Components combine the power of backend APIs with beautiful, prebuilt frontends, so they inherently deliver more value than APIs alone. We believe component-based integrations have set a new standard for developer tools, and we're excited for the massive improvement in developer productivity they enable.

With this new funding, Clerk will reinforce and expand on our foundation, including:

- Continued, emphatic support for cutting-edge frameworks like Next.js, Remix, Redwood, and more on the way
- Additional authentication strategies like passkeys and SAML SSO
- Expanded capabilities in our `<CreateOrganization/>` and `<OrganizationProfile/>` components for B2B SaaS

We can't wait to see what you build!

### Press Release

Clerk, the drop-in authentication and user management solution for React, announced today that it has closed a $6.2m seed round led by Andreessen Horowitz. Existing investors S28 Capital, Fathom Capital, and South Park Commons also joined the round. This new funding will allow Clerk to expand its suite of authentication and user management tools, and create new tools to help B2B SaaS companies manage both user accounts and business accounts.

Clerk empowers React developers to build sign-up and sign-in flows as well as user profiles without the assistance of backend developers. The solution comes complete with security essentials like multi-factor authentication and active device management. The beautifully designed React components can be customized to match any company's branding. By providing a full stack solution, Clerk minimizes the time engineers need to spend on authentication, freeing up development resources to focus on the core of their business.

The vast majority of authentication solutions on the market today are still managed in-house and are often incomplete and fail to meet security best practices. Many of these solutions are dated, as web development technologies have evolved to component-based frontend technologies like React. With Clerk’s solution, developers are getting what they prefer—components—because they help them move faster. Components are especially appealing in authentication and user management where businesses do not get much benefit from reinventing the wheel of self-serve 2FA or avatar-uploading. Clerk includes both in their `<UserProfile/>` component.

*"Our key insight is that React Components have replaced REST APIs as the premier developer experience,"* said **Colin Sidoti, Clerk cofounder and CEO**. *"Since components have a built-in backend and presentation layer, they're an order-of-magnitude faster to implement than APIs."*

While Clerk is intended for React frontends, the authentication solution works with any framework, programming language, or API gateway on the backend. This approach has been especially helpful for legacy web applications that are transitioning to React on the frontend, but do not want to change their backend technology.

*"There's a growing preference for React among businesses of all sizes, and devtools must evolve to cater to this new architecture. Clerk is hands-down the easiest and most-complete approach to auth and user management in React today,"* said **Martin Casado, General Partner at a16z.** *"We are excited to help accelerate Clerk's growth and reach more businesses, founders and startups who are looking for easy authentication and user management."*

Clerk's vision is to build a truly complete customer management solution for developers, helping facilitate authentication, authorization, communications, billing, and more. Clerk believes these essential services can all be bundled together, and presented cohesively in simple React components.

Notable startups using Clerk include database company Grafbase, women's shapewear company Honeylove, and apartment rental company Blueground. Clerk's React-optimized solution has led the company to be included in Netlify's Jamstack Innovation Fund, and named one of the [10 most promising Jamstack startups](https://www.netlify.com/blog/jamstack-innovation-fund-launches-with-the-10-most-promising-jamstack-startups).

---

# How to skip Next.js middleware for static and public files
URL: https://clerk.com/blog/skip-nextjs-middleware-static-and-public-files.md
Date: 2022-10-06
Category: Engineering
Description: Stop your Next.js middleware from running on static assets like images and Next.js internals

[Next.js middleware](https://nextjs.org/docs/advanced-features/middleware) is incredibly powerful, but by default it runs on more requests than is normally necessary. To see this in action, you can add a simple logger:

```javascript {{ title: 'middleware.js' }}
export default function middleware(req) {
  console.log(req.url)
}
```

Here's the log for loading [http://localhost:3000/](http://localhost:3000) on a brand new Next.js application:

```text
http://localhost:3000/
http://localhost:3000/_next/static/chunks/webpack.js?ts=1665116452838
http://localhost:3000/_next/static/chunks/main.js?ts=1665116452838
http://localhost:3000/_next/static/chunks/react-refresh.js?ts=1665116452838
http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1665116452838
http://localhost:3000/_next/static/development/_buildManifest.js?ts=1665116452838
http://localhost:3000/_next/static/development/_ssgManifest.js?ts=1665116452838
http://localhost:3000/_next/static/chunks/pages/index.js?ts=1665116452838
http://localhost:3000/favicon.ico
http://localhost:3000/_next/static/development/_devMiddlewareManifest.json
http://localhost:3000/_next/static/development/_devPagesManifest.json
http://localhost:3000/vercel.svg
```

The log shows several static assets that you probably don't want to process in your middleware, and doing so could break your application. To solve this, you can configure a [matcher](https://nextjs.org/docs/advanced-features/middleware#matcher), which tells your middleware to only run on requests that match a certain pattern.

To prevent middleware from running on static files, you can leverage a simple trick: static files have a **`.`** in the path, while dynamic files do not.

With a regular expression, you can configure middleware to run on every file that does not contain a **`.`**:

```javascript {{ title: 'middleware.js' }}
export default function middleware(req) {
  console.log(req.url)
}

export const config = { matcher: '/((?!.*\\.).*)' }
```

With this matcher, the log only shows one request:

```text
http://localhost:3000/
```

This matcher is a great starting point that minimizes middleware invocation. As your application grows, though, you may need to tweak it. Below are the two most common revisions.

### Including *some* static assets

The easiest way to include some static assets in middleware is to retain the exclusionary regular expression, but define an *additional* matcher. Here's a sample that excludes all static assets from middleware except `favicon.ico`:

```javascript {{ title: 'middleware.js' }}
export const config = {
  matcher: ['/((?!.*\\.).*)', '/favicon.ico'],
}
```

### Excluding more paths from middleware

Sometimes you will want to filter more than just static files. This can be done in two ways:

#### 1. Expanding the regular expression

Expanding the regular expression prevents additional middleware invocations, but regular expressions are also harder to read and write. Here's an example that excludes API routes in addition to static assets:

```javascript {{ title: 'middleware.js' }}
export const config = { matcher: '/((?!.*\\.|api\\).*)' }
```

#### 2. Adding a conditional statement

A more readable approach is to create a conditional statement, but this will waste compute resources since your middleware will be unnecessarily invoked. Here's an example:

```javascript {{ title: 'middleware.js' }}
import { NextResponse } from 'next/server'

export default function middleware(req) {
  if (req.nextUrl.pathname.startsWith('/api/')) {
    return NextResponse.next()
  }
  // ... your middleware
}

export const config = { matcher: '/((?!.*\\.).*)' }
```

This should be everything you need to get started on the right foot with middleware!

---

# Let's stop arguing about JWTs and just fix them
URL: https://clerk.com/blog/lets-stop-arguing-about-jwts-and-just-fix-them.md
Date: 2022-10-05
Category: Engineering
Description: JWTs have won. It's time we embrace them and fix the dangerous implementations.

This week there was a [fresh](https://news.ycombinator.com/item?id=33019960) [batch](https://news.ycombinator.com/item?id=33061873) of debates about JWTs as session tokens on Hacker News. For the uninitiated, this happens incredibly frequently on HN, to the point where moderator dang has posted roundups:

![Lets Stop Arguing About Jwts And Just Fix Them guide illustration](./5e84204a848495c412d2cf8d45a8b193b3417bcd-1258x476.png)

Just about every thread goes the same way:

1. Everyone agrees that infrequently-refreshed, stateless JWTs should not be used as session tokens because they're irrevocable
2. Everyone agrees that it's pointless to use long-lived, stateless JWTs in combination with a stateful, database-backed blocklist of revoked JWTs
3. Everyone agrees that database-backed, stateful session tokens are easy to build and sufficient for the 99% case
4. With trepidation, everyone agrees that frequently-refreshed, stateless JWTs are acceptable as session tokens because the short expiration serves as a revocation mechanism

Why the trepidation on 4? Usually, it's because 3 is easier. Here's [one viral post's](https://apibakery.com/blog/tech/no-jwt) rationale (they refer to stateful session tokens as bearer tokens):

> \[...] you've just reintroduced a bearer token, because that's exactly what the refresh token is. Looking at JWTs from that perspective, you've introduced a client-side cache of user identity (the JWT) and added a bunch of complexity (involving the creation, verification, and token refresh) for the hope of optimizing part of the work you used to do on the server (checking user identity using a bearer token). Was it worth it?

The author is right: The complexity of 4 is high and the benefits for the 99% case are minimal.

Yet, JWTs are clearly winning. Their usage has grown, not dwindled, while two major trends in software development have increased their demand.

### Trend 1: Developers are relentlessly focused on efficiency

This has been a trend for decades. It's *impressive* that today's developers care about a 10ms query vs a \<1ms JWT, but it's not *surprising*.

The simple reality is that Google Lighthouse scores – and the wealth of evidence supporting that users prefer fast applications – have placed an unprecedented emphasis on loading speed.

The developers who care most are no stranger to complexity, as they're often thinking about problems like distributed storage and operating at the edge. It's no surprise they're eager to treat one less query as an easy win.

### Trend 2: Modern integrations often require a synced session

This trend is more recent. Third-party integrations are sweetening the buy-vs-build deal by doing more for their customers. If an integration knows which user is signed in and their permissions, then it can expose APIs directly to the frontend, and even offer user-facing UIs as part of their service:

- New database tools like Supabase, Hasura, Grafbase, Convex all allow querying from the frontend, and use JWTs to know which user is signed in
- Embeddable help desk and community tools like Zendesk and Canny use JWTs to know which user is signed in
- Stripe's user-facing UIs (Checkout, Customer Portal, Payment Element) use a backend API request and session token to know which user is signed in (not a JWT, but still part of the trend!)

The newer a tool is, the more likely it is to allow syncing sessions by JWT.

### It's time to embrace JWTs and disarm the footguns

These trends aren't going anywhere. It's time we embrace JWTs and fix the bad implementations that have stained their reputation:

#### For web developers

Check your application code. If you use stateless JWTs (no database check) and they don't expire in 5 minutes or less, report it to your security team.

#### For authentication tools

Don't generate session JWTs with a lifetime greater than 5 minutes. If developers want to configure a longer lifetime, warn that it's bad practice for XSS attacks.

Provide a refresh mechanism that depends on a HttpOnly cookie in the browser.

#### For integrations

Don't accept session JWTs with a lifetime greater than 5 minutes. If you must, warn developers that it's bad practice for XSS attacks.

Critically, provide developers with a mechanism to send a new JWT after the first JWT expires – don't receive a JWT once and then manage an independent session.

### Really, just expiration?

Yes, long expiration is the #1 most common security mistake with JWTs. It leaves many sites especially vulnerable when XSS attacks or account takeovers occur.

#### Does Clerk do this?

Yes! Clerk's session JWTs default to a 1 minute lifetime, and our refresh mechanism depends on an HttpOnly cookie. We even offer "Active Device" management in our `<UserProfile/>` component so end-users can revoke their own sessions.

---

# How to pass a value from Next.js middleware to API routes and getServerSideProps
URL: https://clerk.com/blog/nextjs-pass-value-from-middleware-to-api-routes-and-getserversideprops.md
Date: 2022-10-05
Category: Engineering
Description: Compute a value in middleware and pass it to your API route or getServerSideProps. Works in both Node and Edge runtimes.

Unlike many web frameworks, [Next.js middleware](https://nextjs.org/docs/advanced-features/middleware) doesn't have a built-in mechanism for passing values from middleware to other parts of the application.

> For authentication-specific middleware patterns in Next.js, see our [Next.js Authentication](/nextjs-authentication) solution.

Instead, middleware has a feature called "rewrites" that we can leverage to pass data.

The high-level idea is to "rewrite" requests to the same URL, but modify the request's metadata to include the data we want to pass. Then, we can read the metadata from our API route or getServerSideProps.

Unfortunately, there are inconsistencies across runtimes that make it difficult to get this working. We hope these snippets help save you save you some headaches.

**License: MIT**

**[Live Demo](https://nextjs-context-demo.vercel.app) | [Source Code](https://github.com/clerkinc/nextjs-context-example)**

**Usage: middleware.js**

```javascript
import { NextResponse } from 'next/server'
import { withContext } from './context'

// Pre-define the possible context keys to prevent spoofing
const allowedContextKeys = ['foo']

export default withContext(allowedContextKeys, (setContext, req) => {
  setContext('foo', 'bar')
  return NextResponse.next()
})
```

**Usage: API route (Node)**

```javascript
import { getContext } from '../../context'

export default function handler(req, res) {
  res.status(200).json({ foo: getContext(req, 'foo') })
}
```

**Usage API route (Edge)**

```javascript
import { getContext } from '../../context'

export default function handler(req) {
  return new Response(JSON.stringify({ foo: getContext(req, 'foo') }))
}
```

**Usage: getServerSideProps (Edge and Node)**

```javascript
import { getContext } from '../context'

export const getServerSideProps = ({ req }) => {
  return { props: { foo: getContext(req, 'foo') } }
}
```

**Source: (saved to context.js on your root)**

```javascript
import { NextResponse } from 'next/server'

const ctxKey = (key) => `ctx-${key.toLowerCase()}`

export const getContext = (req, rawKey) => {
  const key = ctxKey(rawKey)

  let headerValue =
    typeof req.headers.get === 'function'
      ? req.headers.get(key) // Edge
      : req.headers[key] // Node;

  // Necessary for node in development environment
  if (!headerValue) {
    headerValue = req.socket?._httpMessage?.getHeader(key)
  }

  if (headerValue) {
    return headerValue
  }

  // Use a dummy url because some environments only return
  // a path, not the full url
  const reqURL = new URL(req.url, 'http://dummy.url')

  return reqURL.searchParams.get(key)
}

export const withContext = (allowedKeys, middleware) => {
  // Normalize allowed keys
  for (let i = 0; i < allowedKeys.length; i++) {
    if (typeof allowedKeys[i] !== 'string') {
      throw new Error('All keys must be strings')
    }
    allowedKeys[i] = ctxKey(allowedKeys[i])
  }

  return (req, evt) => {
    const reqURL = new URL(req.url)

    // First, make sure allowedKeys aren't being spoofed.
    // Reliably overriding spoofed keys is a tricky problem and
    // different hosts may behave different behavior - it's best
    // just to safelist "allowedKeys" and block if they're being
    // spoofed
    for (const allowedKey of allowedKeys) {
      if (req.headers.get(allowedKey) || reqURL.searchParams.get(allowedKey)) {
        throw new Error(`Key ${allowedKey.substring(4)} is being spoofed. Blocking this request.`)
      }
    }

    const data = {}

    const setContext = (rawKey, value) => {
      const key = ctxKey(rawKey)
      if (!allowedKeys.includes(key)) {
        throw new Error(`Key ${rawKey} is not allowed. Add it to withContext's first argument.`)
      }
      if (typeof value !== 'string') {
        throw new Error(`Value for ${rawKey} must be a string, received ${typeof value}`)
      }
      data[key] = value
    }

    let res = middleware(setContext, req, evt) || NextResponse.next()

    // setContext wasn't called, passthrough
    if (Object.keys(data).length === 0) {
      return res
    }

    // Don't modify redirects
    if (res.headers.get('Location')) {
      return res
    }

    const rewriteURL = new URL(res.headers.get('x-middleware-rewrite') || req.url)

    // Don't modify cross-origin rewrites
    if (reqURL.origin !== rewriteURL.origin) {
      return res
    }

    // Set context directly on the res object (headers)
    // and on the rewrite url (query string)
    for (const key in data) {
      res.headers.set(key, data[key])
      rewriteURL.searchParams.set(key, data[key])
    }

    // set the updated rewrite url
    res.headers.set('x-middleware-rewrite', rewriteURL.href)

    return res
  }
}
```

**Known limitations:**

Depending on the runtime, your data will be transmitted as either an HTTP header or a URL query string. This leads to several limitations:

- Headers and query strings only accept strings for key/value pairs. If you'd like to use non-strings, you'll need to bring your own serializer
- Keys are lowercases because headers are case-insensitive
- Your host likely limits the total overall length of headers and query strings. [Here are the limits for Vercel's edge runtime](https://vercel.com/docs/concepts/functions/edge-functions/limitations#limits-on-requests), for example

---

# @clerk/nextjs v4.5
URL: https://clerk.com/blog/clerk-nextjs-4-5.md
Date: 2022-10-04
Category: Engineering
Description: New in 4.5: Authentication moves to middleware, switchable runtime support, improved developer experience, preparation for Layouts

Version 4.5 of **@clerk/nextjs** brings sweeping improvements to server-side authentication in Next.js.

Get started with `npm i @clerk/nextjs@latest`

## Authentication moves to middleware

Going forward, developers will authenticate requests once in [Next.js middleware](https://nextjs.org/docs/advanced-features/middleware), then simply read the authentication state in endpoints (API routes and getServerSideProps).

**middleware.js example:**

```javascript
import { withClerkMiddleware } from '@clerk/nextjs/server'

export default withClerkMiddleware((req) => {
  // Your own middleware
})
```

**API routes example:**

```javascript
import { getAuth } from '@clerk/nextjs/server'

export default function handler(req, res) {
  const { userId } = getAuth(req)
  // ...
}
```

**getServerSideProps example:**

```javascript
import { getAuth } from '@clerk/nextjs/server'

export const getServerSideProps = ({ req }) => {
  const { userId } = getAuth(req)
  // ...
}
```

With this change, [authentication in Next.js](/nextjs-authentication) will finally feel familiar for developers who have worked with frameworks like Express or Ruby on Rails, where authentication is normally handled within middleware.

## Switchable runtime support

Next.js 12.2 brought a switchable runtime, so developers could choose between a node runtime or an edge runtime for each of their API routes and server-rendered pages.

Our new **getAuth()** helper is isomorphic, so it works regardless of the runtime you choose.

## Improved developer experience

If you've used Clerk in the past, you'll notice three small but delightful improvements to our developer experience:

1. All server-side imports are now from **@clerk/nextjs/server** instead of having import paths specific to the endpoint-type
2. Wrapper functions are no longer required inside endpoints, only once in middleware
3. **getAuth(req)** works everywhere there's a request object

```javascript {{ title: '@clerk/nextjs v4.5', prettier: false }}
// pages/api/foo.js
import { getAuth } from '@clerk/nextjs/server'

export default function handler(req, res) {
  const { userId } = getAuth(req)
  // ...
}


// pages/bar.js
import { getAuth } from '@clerk/nextjs/server'

export const getServerSideProps = ({ req }) => {
  const { userId } = getAuth(req)
  // ...
}


// middleware.js
import { withClerkMiddleware, getAuth } from '@clerk/nextjs/server'

export default withClerkMiddleware((req) => {
  const { userId } = getAuth(req)
  // ...
})
```

```javascript {{ title: 'Previous' }}
// pages/api/foo.js
import { withAuth } from '@clerk/nextjs/api'

export default withAuth((req, res) => {
  const { userId } = req.auth
  // ...
})

// pages/bar.js
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(({ req }) => {
  const { userId } = req.auth
  // ...
})

// middleware.js
// Clerk hasn't supported Next.js 12.2+ middleware until today
```

## Preparation for Layouts

Our team has been watching the [Next.js Layouts RFC](https://nextjs.org/blog/layouts-rfc) closely, and we designed the new helpers in anticipation of layouts support.

We expect that by computing the authentication state in middleware, we'll be able to share it with each of your parallel-loading layout files.

## Get started

Get started right away with our [guide to Next.js authentication](/docs/quickstarts/get-started-with-nextjs), or [join our community Discord](https://clerk.com/discord) to discuss with our team.

Thanks to the contributors: Mark Pitsilos, Nikos Douvlis

---

# Passwordless Authentication: Which Option Is Right for Your App
URL: https://clerk.com/blog/passwordless-authentication.md
Date: 2022-09-06
Category: Insights
Description: Passwordless Authentication is on an upward trajectory thanks to advancements in this space and recent password breaches.

## The Case for Passwordless Authentication

Password-based authentication systems are prone to various cyberattacks, including brute-force methods, phishing, and keylogging. In 2021, over [two billion passwords were leaked](https://www.techradar.com/news/over-two-billion-passwords-were-leaked-by-hackers-in-2021) by attackers. The number of stolen passwords has increased by [more than 35 percent](https://www.techradar.com/news/over-two-billion-passwords-were-leaked-by-hackers-in-2021#:~:text=What%E2%80%99s%20more%2C%20the%20two%20billion%20is%20an%20increase%20of%20more%20than%20a%20third%20\(35%25\)%2C%20compared%20to%20just%20two%20years%20ago) in recent years, proving how insecure password-based systems are (hint: something passwordless authentication can help with).

Even though applications encourage users to change passwords regularly and use complex and hard-to-guess phrases, it's inconvenient for users. Most users tend to choose a password that's easy to remember and, therefore, less secure.

This is where **passwordless authentication** can help. Passwordless authentication is the process of authenticating without needing to input a password or any memorized secret to gain access, in turn reducing data breaches. According to Verizon's Data Breach Investigations Report (DBIR), [over 80 percent of data breaches](https://cloudnine.com/ediscoverydaily/electronic-discovery/80-percent-hacking-related-breaches-related-password-issues-cybersecurity-trends)) happen due to stolen or weak passwords.

Passwordless authentication systems come in several different types, including biometric authentication, one-time passcodes, magic links, and social logins; and each has its own advantages and disadvantages. For instance, if you have a web application, biometric authentication or USB-based authentication might not be the best system since you'll need external devices for verification. In such cases, other authentication types, like social logins or magic links, make more sense.

In this roundup, you'll learn about different types of passwordless authentication that are currently available. You'll look at these options and compare them based on their well-supported standards, convenience, security, and promising new technologies.

### Different Types of Passwordless Authentication

As mentioned previously, passwordless authentication can mean using either a one-time passcode, magic link, or physical hard disk to give you access to the application. In the following section, you'll learn about different types of passwordless authentication, including their advantages and disadvantages.

### Magic Links

[Magic links](/features/email-magic-links) are currently a popular passwordless authentication approach that generates a one-time link and sends it to the user. If the link token verifies with the token stored in the database, the user gets access to the application. The basic workflow looks like this:

![Magic Links screenshot](./be0f848424add9550a53c129391b17a64a0df9d2-1022x662.png "Magic link workflow courtesy of Subha Chanda")

In this workflow, a user may enter their email, username, or any specific identifier. The application generates and stores tokens if the user exists on the database and sends a link to the user's email. When the user opens the magic link, the token retrieved from the link gets verified with the backend, and if the token matches, the user gains access to the application. The link stays valid for a specific amount of time, and a user will need to request a new link if it doesn't get authenticated within the allotted time frame.

This workflow may sound simple, but behind the scenes, several complex tasks are happening to ensure that an attacker can be prevented from maliciously accessing the links.

### Benefits of Magic Links

When users need to log in infrequently, magic links provide a frictionless experience that leads to a better user experience. In addition, since no password is stored in a database, there's no chance of a password breach, and the risk of a data breach is also reduced significantly.

On average, an enterprise spends[ $70 USD per password reset](https://bioconnect.com/2021/12/08/are-password-resets-costing-your-company). Large organizations in the US allocate almost a million dollars yearly for password-related support costs. With magic links, since no password is attached to the authentication system, there is no need to implement a password-reset solution or a support team.

### Disadvantages of Magic Links

Even though magic links provide significant benefits, there are also some dependency issues to take into consideration. For instance, if the user's email account gets compromised, the attacker can easily access all applications that authenticate with magic links and use the compromised email to gain access.

With magic links, you also have to worry about email deliverability. Since magic links depend on the user's email, if the email provider is down or the mailbox storage is full, the magic link will not be delivered. In this case, the user won't be able to log in until the mailbox is cleared or the email provider is up again.

In addition, large business-to-business (B2B) companies tend to filter their incoming emails strongly. That means it's possible that a magic link may land in a user's spam folder and affect the overall user experience.

If your application is smaller in size and the frequency of login is fewer, [magic link authentication](/blog/magic-links) can benefit you. But if a large set audience uses your application and it's essential to efficiently log into the application, you should provide multiple authentication options, like a social login or a password-based login, along with magic links.

### Email and SMS One-Time Passcodes

One-time password (OTP) authentication is another type of authentication that many modern applications use. This type of authentication sends a passcode (primarily numeric) to the user's registered email or mobile phone. After sending the passcode, the application then asks for the passcode. If the user provides the correct passcode, they can access the application.

OTPs are time-based (just like magic links), and they stay valid for a particular amount of time. After that time frame, a new OTP will need to be generated if the user wants access to the application.

### Pros of OTP

A [replay attack](https://en.wikipedia.org/wiki/Replay_attack) is a form of network attack in which legitimate data transmission is purposefully or fraudulently duplicated or delayed. Since no password is required in OTP-based authentication and a new OTP is required every time you log in, this type of authentication is safe from replay attacks.

In addition, because OTP-based authentication is provided by most authentication service providers, it's easy to adopt using an authentication service provider like [Clerk](/). And since no password is involved in authenticating a user, the user doesn't have to remember a password, creating a better user experience.

### Cons of OTP

One of the cons of using OTP is that the messages sent to mobile phones are unencrypted. If the phone or the email address gets compromised, an attacker can easily access the application with the user's profile.

You're also dependent on the network when using OTP. Mobile phone networks might not be stable everywhere and if there's a mobile network issue, the passcode may not get delivered to the user. This can also occur with email if the email provider is down.

Another drawback is that you have to share your mobile number with the application you use. This might be inconvenient and cause issues if your bank account is linked to the same number. Moreover, the provider might call or send the user spam messages; in addition, there is the risk of a data leak. If an attacker gets access to the database, the mobile numbers of all customers can get breached.

The issues with email OTP are similar to the problems associated with magic links. Overall, OTP-based authentication has a few drawbacks, which can be an issue considering there's only one layer of protection. However, your application can be heavily secured using OTP-based authentication as a second layer. For example, with password-based authentication, the chance of getting hacked will be significantly reduced if you integrate OTP as well.

### Social Logins

[Social login or social sign-in](/docs/authentication/social-connections/overview#social-connections-authentication-providers) is a type of login where the user logs in with an existing social account. For example, users can use their Google or Facebook credentials to log into an application, simplifying the login process.

### Benefits of the Social Sign-In

One of the benefits of the social sign-in is that the user doesn't have to fill out a registration form and enter details manually. Instead, when registering with a social sign-in, the necessary details are fetched from the social account. This creates a better user experience.

Another benefit of the social login is that the application can collect more information about the user. This information can be used to provide a better and more personalized experience. In addition, using a social login with familiar login identities helps the user perform login efficiently. Users are already familiar with services like Facebook, Google, and GitHub. Seeing a familiar logo and logging in with the social credentials help the user log in quickly.

### Disadvantages of the Social Login

One of the disadvantages of the social login is the lack of trust. If users don't trust your application, they might hesitate to share their social details.

In addition, if your application uses social login, then you give the user's control to a third party. If the social platform decides to shut its service or something else happens, your users won't be able to log into your application.

There's also a chance that the user is not providing accurate information on their social profile. In such a case, the application will receive false information about the user, which can lead to a poor user experience.

Social logins can provide a great solution if your application depends heavily on user customization. The data received from the social platform will be helpful for creating a custom experience. But this depends on the niche of your application. If your application is built for more professionals or older adults, this login solution might not be the right solution for your users. However, you could combine a social login with a traditional login system, and you can let the user decide which solution they prefer.

### WebAuthn

The [World Wide Web Consortium](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) (W3C) and [FIDO](https://fidoalliance.org) published the WebAuthn standard in 2016. It's a technology that focuses on securing private information. Rather than using a traditional password-based authentication system, WebAuthn uses [asymmetric cryptography](https://www.techtarget.com/searchsecurity/definition/asymmetric-cryptography) to secure a user's account.

When a user tries to log into your application, the server sends a challenge to the user. After completing the challenge, the user can access the application. The challenge can be solved by the server's approved method. Most of the browsers and operating systems are building native support for WebAuthn. You can see how WebAuthn works from their [official site](https://webauthn.io).

### Benefits of WebAuthn

One of the benefits of WebAuthn is its native support. WebAuthn continues to gain popularity, which means browsers and operating systems are continuously trying to provide native support. API and JavaScript functions (on most major browsers) are also available using WebAuthn.

Because WebAuthn uses public and private key encryption, it's more secure than a traditional password-based system. WebAuthn also doesn't require any password, and because of this, the chance of phishing or man-in-the-middle attack is reduced significantly.

### Disadvantages of WebAuthn

One of the disadvantages of WebAuthn is the lack of currently available support. Because the WebAuthn authentication system is new, not every browser or operating system natively supports it. However, this is rapidly changing, as most modern browsers are integrating the WebAuthn API.

WebAuthn can also be used with hardware authenticators like [YubiKey](https://www.yubico.com). But in such a case, if the device gets stolen or lost, the recovery might be tricky if there is no proper setup implemented. In addition, because WebAuthn is in a very primitive stage, the [user experience of using WebAuthn](https://blog.silverorange.com/web-authn-ux) is lacking and, at times, can feel too technical to use.

WebAuthn is an excellent alternative to traditional systems, but implementing it for a simple and small application might be overkill. It's ideal for large-scale applications that focus on privacy.

### SAML Login

Security Assertion Markup Language (SAML) is an XML-based open standard that transfers data between two parties. You can access all other services linked to your identity by logging into only one platform or dashboard. SAML transfers data between an identity provider and a service provider. The identity provider provides the user's identity and authorizes the service provider. The service provider, based on the identity provider, grants access to the user for the service.

SAML is primarily used in B2B use cases. For example, your company email might let you log into a specific dashboard from which you can access other services.

### Benefits of SAML

One of the benefits of SAML is that it provides a great user experience. Only a single credential is needed to log into different applications.

Security is another critical benefit of using SAML. It provides a single point of authentication, and the user's information doesn't leave the firewall boundary. All the different applications don't need to synchronize information between them, making the authentication system securer.

In addition, SAML is widely accepted by enterprises. It has been on the market for a long time and has gained the trust of enterprises.

### Disadvantages of SAML

One of the disadvantages of SAML is that it can be vulnerable to XML and DNS spoofing attacks, which makes the system less secure.

It's not suitable for personal applications like social platforms since setting up SAML is difficult; however, it works well for enterprise applications because employees won't need to think of multiple software that the company uses. Using a single set of login credentials, your employees can access all your software.

In addition, implementing SAML is a critical job. Different security aspects need to be considered, which might not be an easy job for a small-sized team.

### Conclusion

Passwordless authentication is gaining more attraction because it's an excellent solution for providing a better user experience and increased security to your users. In this article, you learned about different popular passwordless systems and their benefits and disadvantages.

[Passwordless authentication](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategies) can be an excellent solution for your application, but it's crucial that you understand the use case and implement a solution suitable to your needs. [Try Clerk free today](https://dashboard.clerk.com/sign-up) to add passwordless authentication to your application within minutes – not weeks.

---

# What are Webhooks? The Ultimate Guide 
URL: https://clerk.com/blog/what-are-webhooks.md
Date: 2022-08-19
Category: Insights
Description: Webhooks allow apps to communicate with one another. Find out best practices and more here.

**What are Webhooks? The Ultimate Guide**

Webhooks can be powerful tools for your business, but only if implemented correctly. In this blog post, you will learn the nature of webhooks, some of the webhook best practices for implementation, and their various use cases in a business.

You will also get to explore examples of platforms that use webhooks in their development processes. And finally, you will learn how to implement webhooks in your authentication and development strategies. By following these webhook best practices, you will ensure that your webhooks are secure and efficient.

## **What Are Webhooks?**

Webhooks are user-defined HTTP callbacks initiated by an event. In other words, a webhook is an HTTPS POST request sent whenever a specific event occurs to a URL that you have specified.

The usual trigger is some event, such as pushing code to a repository or a comment being posted to a blog. When that event occurs, the source site makes an HTTP request to the URL configured for the webhook. Webhooks can be used to perform various tasks, such as building a continuous integration system, sending notifications, or synchronizing data.

Webhooks offer a simple and flexible way to automate tasks that would otherwise be repetitive and time-consuming. They are preferred over polling because they allow you to receive information in near-real-time instead of waiting for a set interval. Their usage is not limited to development tasks; webhooks can be used for a wide range of integrations, such as social media, marketing, and customer support.

Webhooks are a powerful tool that can automatically send information from one system to another. However, before implementing webhooks in your system, it is essential to consider a few webhook best practices.

## **How Does a Webhook Work?**

So, how do webhooks work? A [webhook is an HTTP](https://cloud.ibm.com/docs/assistant?topic=assistant-dialog-webhooks) callback that is triggered by an event. When that event occurs, the source site makes an HTTP request to the configured webhook URL. Developers use webhooks to receive notifications about events within their applications. For example, a developer could use a webhook to be notified when a new user signs up for their service.

The webhook request will typically contain information about the event that triggered it. This information can be used to take action within the receiving application, such as updating a database or sending a notification. In some cases, the webhook may also trigger an action on the source site. For example, a webhook could be used to deploy application code when it is pushed to a repository automatically.

## **Best Practices When Implementing Webhooks**

Webhooks are a great way to get real-time data from your web application. They are perfect for automating tasks or getting notified when something happens in your app. Webhook traffic is described as a request that is initiated by an event. The request is then forwarded to the URL that you have configured for the webhook.

However, to reliably and efficiently use webhooks, there are some best practices to keep in mind when setting them up. The webhook best practices will ensure that your webhooks are working as intended and that you are getting the most out of them. When configuring webhooks for your application, security should be your top priority. Here are some best practices to follow when implementing webhooks:

### **Provide Developers With Sample Code to Authenticate Webhook Requests**

One of the most important things to do when setting up webhooks is to provide developers with sample code to authenticate webhook requests. This will ensure that only legitimate requests are processed and that any malicious requests are ignored.

There are a few different ways to authenticate webhook requests. The most common method is to use a shared secret. This is a piece of data known only to the server and the client. When a webhook request is made, the shared secret is used to calculate a hash. This hash is then compared to the one sent with the request. If they match, then the request is considered to be authentic.

Another method that can be used to authenticate webhook requests is to sign them with a digital signature. This signature is generated using a private key that is known only to the server. The client then verifies the signature using the server's public key. This method is more secure than using a shared secret, but it is also more complicated to set up.

Webhooks are incredibly powerful tools that enable your application to receive real-time updates from other applications or services. However, because webhooks involve sending potentially sensitive data over the internet, it's essential to take steps to ensure that your webhook endpoints are as secure.

### **Keep Your Webhook Endpoints Secure**

Webhook endpoints are URLs configured on a system to receive data sent by an HTTP callback. Webhook endpoints are [a potential attack vector](https://docs.oracle.com/en/cloud/paas/content-cloud/solutions/use-webhooks.html#GUID-3513A92A-1E36-4E47-B5C7-7D8989590AF1) for malicious actors. If an attacker can guess or brute force the URL of a webhook endpoint, a request can be sent to that endpoint and potentially cause damage.

As a preventative measure, it is crucial to keep your webhook endpoints secure. One way to do this is to use a randomly generated string as the endpoint URL. This will make it much harder for an attacker to guess the URL of the endpoint.

Another way to keep your webhook endpoint secure is to use HTTPS for all communication. This will ensure that all the webhook payload passing between the server and the client is encrypted. It also prevents anyone from eavesdropping on the transmission and potentially intercepting sensitive data.

The third step is consumer verification of the client before processing any requests. This can be done using a shared secret or a digital signature. Whichever method you choose, make sure that it is secure and that only trusted clients are able to access your webhook endpoint.

### **Configure To Receive Specific Webhook Events**

When configuring your webhooks, you should only receive the business process events in which you are interested. There is no need to receive all events for all of your users as it will just result in extra work for you and could lead to missed events if you are not paying attention.

Instead, only configure your webhooks to receive the specific events in which you have interest. This way, you can be sure that you will only receive the events that you care about and that you will be able to process them in a timely manner.

### **Utilize the Network Time Protocol To Prevent Replay Attacks**

Another critical webhook best practice when setting up is to utilize the Network Time Protocol (NTP). This protocol is used to [synchronize the clocks](https://www.ibm.com/docs/en/aix/7.1?topic=files-network-time-protocol-ntp) of computers over a network.

Using NTP will help to prevent replay attacks. A replay attack is where an attacker intercepts a webhook request and then re-sends it at a later time. If the clocks on the server and the client are not synchronized, the attacker could potentially resend the request before it has been processed. This could lead to the same event being processed more than once.

By using NTP, you can be sure that the clocks on the server and the client are synchronized. This will prevent replay attacks and ensure that each webhook request is processed only once. The use of NTP is vital when processing time-sensitive data.

### **Pay Attention to Delivery Attempt Data and Metrics**

When setting up webhooks, paying attention to the delivery attempt data and metrics is essential. This data can be used to troubleshoot any problems you are having with webhooks. Delivery attempt data is defined as information about each time webhook notifications are sent from the server to the client.

This data can be used to determine if there are any problems with the connection between the server and the client. It can also be used to determine if there are any problems with the webhook endpoint itself by reviewing already-logged events.

By paying attention to this data, you can be sure that your webhooks are being delivered successfully. This will help to prevent any missed events and ensure that your webhooks are working as intended. The delivery attempt data will tell you how many times a webhook request has been made and whether or not it was successful. This data can be used to identify any problems with the webhooks.

The webhook metrics will tell you how long it is taking for the webhook requests to be processed. This information can be used to identify any error in the system. By paying attention to the delivery attempt data and metrics, you can be sure that your webhooks are working correctly and that they are being processed promptly.

## **Why Do We Use Webhooks**

Now that we have covered some of the webhook best practices for configuration, let's take a look at why we use them. Webhooks are a great way to get real-time data from an external source. They are also a good way to keep your own internal data up-to-date.

For example, let's say that you have a website that sells products. Every time a product is sold, you could use a webhook to send the order information to your internal system. The information received from the website could then be used to update the inventory in your internal system. This would ensure that your inventory is always up-to-date.

Another example of why webhooks are useful is if you have a blog. Every time a new post is published, a webhook could be used to send the information to your mailing list. This would ensure that your subscribers are always updated on the latest posts.

Webhooks are also a good way to get real-time data from an external source. For example, let's say that you want to track the stock price of a company. You could use a webhook to get the information from an external source and then display it on your website. This would allow you to get the latest information without constantly refreshing the page. The following text describes two major ways on how to use webhooks.

### **To Receive Different Event Types**

Webhooks are essential to receive different event types. Events are defined as any actions that take place on your website or application.

Some examples of events include:

- A user signs up for an account
- A user adds an item to their shopping cart
- A user completes a purchase

If you are not using webhooks, then you will not be able to receive these events in real-time. For example, let's say that you have a web application that allows users to sign up for an account. Whenever a user signs up for an account, you could use a webhook to send an email to the user. This would allow you to receive different event types and take action based on those events.

### **To Store Information Securely**

Webhooks can also be used to store information securely. For example, let's say that you have a web application that allows users to input sensitive information. You could use a webhook to send that information to your internal system. This would allow you to store the information securely and not have to worry about it being intercepted by an attacker.

## **Authentication With Webhooks**

Another best practice when setting up webhooks is to use authentication. When you use authentication, you can be sure that only authorized users are able to access the data.

There are many[ different ways to authenticate](https://developer.zendesk.com/documentation/event-connectors/webhooks/webhook-security-and-authentication/#:~:text=Webhooks%20support%20two%20types%20of,authentication%20property%20from%20the%20request.) with webhooks. One popular way is to use an authentication tokens system. With this system, you would generate a token for each user. The user would then include the token in the webhook request. This would allow you to verify that the user is authorized to access the data.

Another way to authenticate with webhooks is to use an application programming interface (API) secret key. The API secrets would be used to sign each webhook request. This would allow you to verify that the request came from a trusted source.

## **Examples of Platforms That Use Webhooks in Their Development Process**

Many platforms use webhooks in their development process to achieve different goals. The following are some examples and use cases of webhooks explained.

### **Shopify**

Shopify is a popular e-commerce platform. Shopify uses webhooks to[ keep track of events](https://shopify.dev/api/admin-rest/2022-04/resources/webhook#top) that happen on the platform. When a customer makes a purchase, Shopify uses a webhook to send the order information to the merchant. This allows the merchant to keep track of their sales and fulfill their orders on time.

Shopify also uses webhooks to keep apps in sync with shop data or perform an action after a specific event occurs. Webhooks are a more efficient alternative to constantly polling for changes to data via an API server.

### **Discord**

Discord is a voice, video, and text chat app. It uses webhooks to[ send messages and data updates](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to a text channel in your server. By creating a webhook endpoint in your server, you'll generate a webhook URL that you can drop into a number of other services to link the two.

For example, you could set up a webhook endpoint in your server to send a message to a Discord channel whenever someone made a post on your blog. Or, you could use a webhook to automatically keep a channel in your Discord server updated with the latest sports scores.

### **Mailchimp**

Mailchimp is a popular email marketing platform. Mailchimp uses webhooks to[ keep track of events](https://mailchimp.com/developer/marketing/guides/sync-audience-data-webhooks) that happen on the platform. Mailchimp uses a webhook to send the subscription information to the list owner when a user subscribes to a list.

Webhooks are also helpful tools that can be used to collect information about audience changes in Mailchimp as they happen. By entering a valid URL that is set up to accept HTTPS POST requests, you can receive updates on subscriptions, changed email addresses, campaign sending, and more. Webhook can also be used to keep your client's profile data in sync with your database, detect when an email address starts bouncing, or alert your application when a campaign has finished sending.

## **How You Can Implement Webhooks Into Your Authentication or Development Strategies**

Now that you know what webhooks are and how they can be used, you may be wondering how you can implement them into your authentication or development strategies.

One way to use webhooks is to set up a webhook endpoint in your server. This endpoint would be responsible for handling all webhook requests. The endpoint would need to be able to authenticate the request and process the data. Once the request is authenticated, the endpoint will take the appropriate action.

Another way to use webhooks is to use them as part of your authentication strategy. Webhooks can be used to verify that a user is authorized to access data. One way to do this is to use a secret key. The secret key would be used to sign each webhook request allowing you to verify that the request came from a trusted source.

If you're looking for a[ flexible, multi-factor authentication service](/features/multifactor-authentication) that can easily integrate with your webhook endpoints, Clerk is here to help. Clerk is the leading authentication and authorization services provider in the industry. [Try Clerk for free today](https://dashboard.clerk.com/sign-up) and learn how to implement authentication into your security strategy.

---

# What Is an SDK and How Is It Different From APIs?
URL: https://clerk.com/blog/sdk-vs-api.md
Date: 2022-08-10
Category: Insights
Description: While an SDK and an API often work together in the software development process, they each serve a different function. Find out what that is here.

Modern software development typically involves using both software development kits (SDKs) and application programming interfaces (APIs). Even people with experience in the world of application development get tripped up over the fundamental difference between both sets of tools. Both help users improve the functionality of their applications quickly without having to construct something from the ground up. Let’s look at the core differences between SDKs vs. APIs and how they enhance the development experience.

## **What Is an SDK?**

An SDK is often referred to as a dev kit. It’s a set of software-building tools made available from vendors. Developers usually rely on SDKs to build applications using a specific programming language targeted at a particular system or platform. An example of that would be the Windows SDK managed by Microsoft, which contains various frameworks for creating applications that run on Windows computers.

An SDK makes it easier for developers to create applications that follow a standardized pattern for a specific environment. Users download the kit and other components, like prebuilt application examples and instructions. SDKs give you a head-start on building, testing, and deploying a robust application.

## **What Is an API?**

An API is a mechanism that helps two software modules communicate using specific protocols and definitions. It's a piece of software designed to fulfill a particular function. The weather app on your phone and the GPS application providing directions are examples of APIs.

The interface part of an API outlines the contract used between two applications attempting to speak to each other. Your API defines the request and response format used between the two. Developers can refer to the documentation for APIs to understand how they can set up requests and responses within applications built using an SDK.

### **How APIs Work**

The architecture of your standard API typically consists of client and server functions. An application making a request is the client, while the one providing the response is the server. Your weather app calls the weather service database, asking for information, and the database provides information about the atmosphere in your specific location.

Below is an overview of different APIs:

- **SOAP:** The Simple Object Access Protocol exchanges messages between clients and servers using an Extensible Markup Language (XML) format. The format became popular among developers in the past. The biggest drawback is the lack of flexibility in structuring requests and responses.
- **RPC:** Remote procedure calls ask the client to execute a function, called a procedure, from a server. That server processes the output and sends it back to the client.
- **REST:** Representational state transfer APIs define functions used by clients to access data from a server. Both clients and servers use HTTP to exchange information. REST APIs do not preserve data between client/server requests. Responses from servers are sent using plain data.
- **WebSocket:** Instead of XML, WebSocket APIs rely on JavaScript Object Notation (JSON) to pass data back and forth. It’s a more modern approach that allows for two-way communication between clients and servers. The ability for servers to send callback messages to connected clients increases its efficiency versus REST APIs.

## **How a Software Development Kit Differs From an Application Programming Interface**

The most significant difference between an SDK vs. an API is in how they enable development. Your SDK provides the building blocks you need to write new programs designed for specific platforms using your preferred programming language. There’s often at least one API included with most SDKs. Everything you need to construct a basic app comes with an SDK.

Developers can’t use APIs to build brand-new applications. However, they do allow applications to work together to enhance various aspects of your applications. For example, you can use an API to pull financial data and feed it into a dashboard featured on a website.

## **Common SDK Features**

Let’s look at features that are often included when you download an SDK.

### **Integrated Development Environment**

An integrated development environment (IDE) is used for creating various applications. The platform brings together numerous tools that developers rely on with one graphical user interface (GUI). Here’s a breakdown of the components of a typical IDE:

- **Source-code editor:** Developers use this screen to write software code. A source-code editor often comes with options to highlight specific syntax, point out bugs, and auto-complete phrases in lines of code to help speed up development.
- **Build automation:** This utility automates tasks used to set up local software builds for a developer. Build automation performs functions like packaging binary code and running automated tests.
- **Toolbar:** An IDE toolbar resembles one found in a word processor. It organizes different components that developers rely on as they are writing code.

IDEs help enhance a software engineer’s productivity thanks to the way they standardize the configuration of various developer tools. Everything you need is available through one interface.

### **Debuggers**

A debugger is a computer program that tests and finds bugs within a developer’s code. The debugger typically starts working as soon as you begin writing your code. As you compile your code and integrate it with other programming components, your debugger will highlight errors that keep the program from running correctly. Debuggers are essential to the development process, especially if you’re working on an application containing thousands of lines of code. You can also integrate unit testing to make things easier.

### **Code Samples**

Code samples show developers how to implement different programming scenarios or concepts. They’re also helpful in showing you how to set up more complex architecture components, like a grid. Many SDKs provide you with complete applications and the source files needed to run the program.

### **Code Libraries**

One of the main rules that programmers follow is "Don’t Repeat Yourself," or DRY. Code libraries help you stick by that principle by providing prewritten code that you can modify. These reusable blocks of code make it easy to save time while building projects or optimizing other pieces of your program. Another advantage of using tested code libraries is that they add stability to your application.

### **Documentation**

The documentation that comes with SDKs provides you with instructions on using each component and integrating it with other systems.

### **APIs**

APIs that come with SDKs contain libraries built using the platform’s core language. You can use your SDK to leverage APIs that add more capabilities to your program.

## **The Benefits of an SDK for Software Developers**

SDKs make your job as a developer more straightforward by making it easier to pull different components together to construct an application. They make integrating standard processes easier and allow you to access pertinent information while coding. Many SDKs come with built-in support and documentation to help you leverage the program effectively.

### **Enhanced Control of User Interface and User Experience**

An SDK gives you more control over the elements used to build your user interface. If a customer wants components laid out in a certain way, you can follow their requirements seamlessly with an SDK versus trying to use a basic code editor and disparate libraries. You’ll also have an easier time testing each screen element and ensuring that the users will be happy with the final product.

### **Quicker Time to Market**

The code samples provided with SDKs allow you to create or expand the functionality of your applications quickly. You can leverage APIs and other libraries to quickly set up new components for your program. That makes the application easier to debug, test, and deploy in different environments before you release it for general use.

### **Broader Integration Options for Existing Tech Stack**

With more vendors choosing to integrate APIs into their SDKs, there are more integration options available for developers. For example, if you have an application built using the .Net framework, you can still integrate the SDK with other frameworks to enhance the look of your UI.

## **Types of and Common Uses for Development Kits**

While some SDKs enable connections between different tools, others help your applications run on specific devices and operating systems.

### **SDKs by Hardware**

Many hardware providers make SDKs available to developers for programming technology like the Internet of Things (IoT) or programming robots. Having an SDK readily available for specific hardware makes it easier for developers to start their coding.

### **Open-Source SDKs**

While most SDKs come from specific vendors, you can find open-source SDKs that allow developers to modify the underlying source code. However, there can be security risks associated with working with an unsupported platform.

### **Proprietary SDKs**

In contrast to open-source SDKs, developers can’t access and modify proprietary source code. Instead, they must purchase a license permitting them to use proprietary code elements.

### **SDKs for Each Programming Language**

Below are examples of SDKs available for different programming languages:

#### **C#**

- [.NET SDK](https://docs.microsoft.com/en-us/dotnet/core/sdk)
- [Twilio SDK](https://www.twilio.com/docs/libraries/csharp-dotnet)
- [AWS SDK for .Net](https://aws.amazon.com/sdk-for-net)
- [EOS SDK for C#](https://dev.epicgames.com/docs/services/en-US/GameServices/CSharpGettingStarted)

#### **JavaScript**

- [Facebook SDK](https://developers.facebook.com/docs/javascript)
- [AWS SDK for JavaScript](https://aws.amazon.com/sdk-for-javascript)
- [Google Maps SDK](https://developers.google.com/maps/documentation/javascript/overview)

#### **Java**

- [Oracle SDK](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/javasdk.htm)
- [AWS SDK for Java](https://aws.amazon.com/sdk-for-java)

#### **Python**

- [Carto Python SDK](https://carto.com/developers/python-sdk)
- [Square Developer Python SDK](https://developer.squareup.com/docs/sdks/python)
- [Smartling Python SDK](https://help.smartling.com/hc/en-us/articles/4412947322267-Python-SDK)

#### **Ruby**

- [AWS SDK for Ruby](https://aws.amazon.com/sdk-for-ruby)
- [Oracle SDK for Ruby](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/rubysdk.htm)
- [DocuSign SDK for Ruby](https://developers.docusign.com/docs/esign-rest-api/sdks/ruby)

## **Do You Need Both an API and an SDK for Development?**

It’s possible to build applications using an SDK without relying on APIs. However, working with APIs and SDKs together helps enhance the programming experience. It’s easier to construct platforms that meet your requirements. There are SDKs available for both Apple and Android developers who want to create mobile apps for those environments.

You don’t have to think of it as a choice between using either an SDK or an API. Remember, many SDKs come with preinstalled APIs. If you want to build a website that allows users to sign in through their Facebook account, an SDK makes that much easier.

You can automate user verification using the Facebook Graph API. That way, you, as the developer, don’t have to go through the trouble of figuring out how to generate tokens, inspect them, and determine which app the user is trying to connect through. The API makes the sign-in process for your app seamless.

### **Benefits of SDKs**

SDKs help developers quickly add functionality to their applications and construct standard components. Typically, there’s no need to integrate an SDK with other elements. If you do, that might slow down your development process.

Most software engineers rely on SDKs to complete simple functions like allowing users to make mobile payments or setting up location services. Certain SDKs let you do more complex programming, like creating virtual reality (VR) and augmented reality (AR) programs.

When used correctly, SDKs help relieve the back-end burden of building software programs. You have a modular foundation to rely on that saves you much of the manual effort. Many of the apps created by software engineers contain a few features that serve a common purpose.

For example, a telemedicine app might offer chat features that let users reach out to medical professionals with questions or concerns. A mobile insurance app would contain features that allow users to check on a claim payment's status or print out a copy of their insurance card.

The efficiency provided by an SDK makes it easier for dev team members to work independently. It limits the number of common touchpoints, decreasing the risk of one person breaking another team member’s code during an update.

Adding APIs into the mix cuts down on lag time because of the research needed to figure out how to build a new capability. Working with an API means developers have to worry only about the necessary pieces that make their application work the way they want it to, eliminating the need to dive into how everything works under the surface.

## **Other SDK Considerations**

Some SDKs have agreements to which you must provide consent before you are allowed to use the software. Vendors often make new versions of SDKs available for alpha or beta testing, meaning they are not yet publicly available. Those features you try out could undergo modifications or changes before the SDK’s general release. Certain features may fall under a license agreement, so you have to be careful that you do not develop a project that falls outside of its scope.

Read through all the documentation of any SDK you want to use and make sure there is nothing that conflicts with your desired usage. You can’t develop proprietary apps using an SDK with a General Public License. You should also look for any constraints around SDKs that come with a Lesser General Public License (LGPL) if you intend to build a project containing proprietary elements. Think about the ease of implementing security features like multifactor authentication.

### **Commercial vs. Open Source**

Organizations also have to think about the cost of using an SDK. Many are tempted to use open-source SDKs because they’re free. However, your company could still end up paying if there’s a need for developers to spend hours getting up to speed on how the software functions. The cost of the time lost trying to figure out how an open-source SDK works could exceed what you might pay for a licensed version.

Commercial products do bring expenses associated with paying for licenses and integrating the components into current applications. However, you can turn to the vendor for support when you have questions. Paid SDKs tend to be more secure, which lowers the risk of your SDK becoming a gateway for a cyberattack.

You also have to think about the ease of using the SDK in your development shop. The goal of an SDK is to make coding easier. If you’re spending more time getting the application to function correctly than building software, there's a problem. Think carefully about implementing an open-source SDK. If your company has knowledgeable staff who are comfortable working with the product and bringing other developers along, you can succeed.

Usability is often the most significant advantage commercial products have over open-source SDKs. Even if you have developers who are capable of leveraging an open-source SDK, you have to think about who might be asked to maintain those applications if they end up moving on to another position or leaving the organization. How easy will it be to bring other software engineers up to speed on the SDK?

You should also look at the support provided by the SDK manufacturer. With open-source SDKs, there may be only online communities to turn to instead of a fully knowledgeable staff. If you have questions, there’s no guarantee that you’ll get timely answers or that there will be someone available who can help.

## **A Roundup of SDKs vs. APIs**

SDKs provide developers with everything necessary to create or modify applications. Many come with prebuilt code that helps software engineers get started on their applications. APIs create connections between two applications and allow developers to expand the functionality available within a program.

It’s easier for developers to become more efficient by using SDKs and APIs together. When deciding on an SDK, look at whether you’re really saving by going with an open-source version when it comes to the cost, time, and effort. If your development team is unfamiliar with the platform, you could lose time and money trying to adapt.

Make sure that your SDK does not come with license restrictions that would impact your development. Explore the support that would be available to you or your dev team if you were to run into problems with your SDK.

***Ready to implement your own authentication?***

Clerk has been built with developers in mind and allows you to add authentication to your application with just a few lines of code. [Get started for free today](https://dashboard.clerk.com/sign-up)!

---

# A ‹Component/› is worth a thousand APIs
URL: https://clerk.com/blog/a-component-is-worth-a-thousand-apis.md
Date: 2022-08-07
Category: Company
Description: Clerk is the only authentication service that offers a User Profile component. It sounds incremental, but it's 10x more work – this post explains why

Clerk started with a simple vision. We loved the professionalism of Google's Sign Up, Sign In, and User Profile, so we set out to build developer tools that would help everyone match their caliber.

![A Component Is Worth A Thousand Apis guide illustration](./a0904584e9cb4e0751a833c1bc0bdd14f5060fa2-1080x494.png)

With UIs as our primary goal, we realized that Clerk wouldn't be a traditional API company. We focused on building a fantastic frontend component SDK, rather than a traditional backend REST SDK.

We chose React as our first target given its popularity, and split Google's surface area into four components: `<SignUp/>`, `<SignIn/>`, `<UserProfile/>`, and `<UserButton/>`.

![A Component Is Worth A Thousand Apis guide illustration](./e9dbf939e2b523fdf51e5f9e59a6f5e16907298b-2458x1248.png)

Components are a higher-level of abstraction than REST SDKs. They provide UIs to end-users instead of just a function to developers, so they inherently offer more value.

But what's surprising is just *how much* more value. Even at Clerk, we assumed we could just "slap a UI on top of an auth API." Despite having a team full of seasoned engineers, we failed to realize just how much work sits between traditional APIs and the end-user features they enable.

### Stripe example

As an example outside of authentication, let's compare Stripe's original 9 lines for creating a charge, to the *sidebar* for their current Checkout documentation:

![A Component Is Worth A Thousand Apis guide illustration](./cccc2d59d94fa8b00928e84940a25f5392dbbb90-1082x981.png)

Checkout has far more complexity than the original Charge API, but why? Aren't both responsible for charging a customer?

The difference is that Charge asked for the exact amount to charge, while Checkout needs to display *why* an amount is being charged. It needs all of the context of the Products being purchased, their Price, the quantity, the taxes and shipping costs, any discounts applied, and more. Stripe needed to build dozens more API resources and paramaters to produce the end-user Checkout UI.

Before Checkout, this additional context lived inside the developer's application. Now that it lives inside Stripe, charging customers has become massively easier. Stripe solves so much more complexity for developers today than it did 10 years ago.

### Clerk example

Clerk is seeing the same patterns in authentication that Stripe saw in Checkout. To build our core components, we need to manage much more complexity than legacy vendors like Auth0.

This is particularly apparent with our `<UserButton/>` and `<UserProfile/>` components. We are the only authentication provider to offer these, and there's a foundational reason why: **Clerk is the only authentication provider that manages sessions as-a-service.**

Legacy vendors focus on just the sign-up and sign-in screens. Once the user's identity is verified, the session is managed within the application – not within the authentication service. This makes it impossible for them to offer drop-in `<UserButton/>` and `<UserProfile/>` components like Clerk.

And since we're not ones to cut corners, our session management solution is fully featured. We include active device management from within `<UserProfile/>`, so users can remotely force sign outs from other devices:

![A Component Is Worth A Thousand Apis guide illustration](./3135b92fe15d7d789429299c6354d1662ea941b1-616x391.png)

Plus, we (optionally) include account switching within `<UserButton/>`, just like Google's:

![A Component Is Worth A Thousand Apis guide illustration](./c962088265770e23f04b1cf2b0b5eeba2c32d2b6-471x527.png)

Beyond session management, we also enable self-serve two-factor authentication from within `<UserProfile/>`:

![A Component Is Worth A Thousand Apis guide illustration](./dfec3db47a85e8f1f17f882a97c2b00bcbdf2ab5-615x352.png)

Once setup is completed, `<SignIn/>` will automatically request the second factor during the next sign-in:

![A Component Is Worth A Thousand Apis guide illustration](./478885fd08c6525391c6878688bea54c3d091274-526x527.png)

If Clerk didn't manage sessions and two-factor authentication, developers would be stuck implementing these features from within their application – a much more challenging prospect than simply using `<UserButton/>` and `<UserProfile/>`.

### On components vs redirects

Some readers may be aware that Clerk uses embeddable components while Stripe Checkout uses a redirect flow. That's completely correct, so we kept this blog focused on points that remain true regardless of how the UI is ultimately presented to the end-user.

That being said, there is one advantage to components that deserves a special callout:

At Clerk, every component we build is composed using React hooks, and the hooks we use are actually pacakaged alongside our components. So, if frontend developers don't like our components for any reason, they can still build their own UIs **without involving a backend developer and a backend SDK.**

This extra layer of composability has been a huge boon for companies with strict branding requirements, since we eliminated the need for backend application logic regardless of if our UIs are used.

### In summary

We've seen this pattern enough to say confidently: **a `<Component/>` is worth a thousand APIs.**

Components have become the new gold-standard in developer tools – they are the new, optimal, composable building block. They save developers far more time than traditional REST SDKs, both by providing end-user UIs, and by eliminating huge swaths of code that used to be managed in-house.

---

# User Management and How It Relates to Authentication
URL: https://clerk.com/blog/user-management-in-authentication.md
Date: 2022-08-03
Category: Insights
Description: User management assists in authenticating and storing users. Find out more about it here.

**User Management and How It Relates to Authentication**

User management identifies and authorizes users in order to provide secure access to digital resources. This process usually involves an authentication procedure that confirms the user's identity without revealing the password. This article discusses user management and authentication in detail.

You will learn what user management is and how it relates to authentication. Additionally, you will look at different types of authentication and how they are used in user management systems. Finally, you will take a closer look at traditional approaches to user management versus real-time approaches. Read on to learn more!

## **What Is User Management?**

User access management is [the process of identifying, authorizing and authenticating users](https://cloud.ibm.com/apidocs/user-management) on authorization platforms. This process usually starts with user registration, where the user provides their personal information. Once the user is registered, they can sign in to the platform. During sign-in, the user's identity is confirmed, and they are given access to the platform.

User management has four main aspects: user profiles, user properties, user privileges, and user migration.

### **User Profiles**

User profiles contain the user's personal information, such as user names, contact details, and photograph. This information is used to identify individual users and is usually stored in a database. User profiles can also contain other information, such as user creation method, and users' preferences and interests.

### **User Properties**

User properties are specific attributes assigned to individual users that identify them within the system. These attributes can include the user's name, contact information, and role within the company. User properties can be static, such as individual user type (e.g. administrator, user, guest), or dynamic, such as a user's current status (e.g. active, inactive, suspended). User properties are used to identify users within the system.

### **User Privileges**

A user privilege, on the other hand, is a permission that allows a user to perform certain actions, such as creating new users or accessing restricted areas of the system. For example, a user might be given the privilege to view customer data or to create new categories in the product database. User privileges define custom user roles i.e., what a user can do and are used to control user's level of access within the user interface.

### **User Migration**

User migration is the process of moving user data from one platform to another. This is usually done when users change the user type or are deleted from the system. User migration can be a complex process, depending on the size and complexity of the user data.

## **Benefits of a User Management API**

A user management API (Application Programming Interface) [is a set of software instructions that allow one application to interact with another](https://www.forbes.com/sites/moorinsights/2020/08/06/for-devops-application-programming-integration-api-is-a-major-security-vulnerability/?sh=79fdba931f20). An API can be used to create, read, update and delete user profiles. It can also be used to manage user access and privileges.

Using an API to manage users has many benefits, including:

### **Ability To Manage User Identities and User Access**

API-based user access management allows you to manage users and their access to your platform from a central location. This means that you can easily add, remove or update user accounts without having to make changes to your code. Additionally, it is easier to keep track of user activity and identify potential security risks.

### **An Enhanced User Experience**

API-based user management can provide a better user experience by allowing users to self-register and sign in using their own social media accounts. This eliminates the need for users to remember multiple usernames and passwords. Additionally, API-based user management enables you to provide a personalized experience for each user by tailoring the content and functionality of your system to their specific needs.

A user access management API provides many other benefits for users and website administrators. For instance, an API can allow user synchronization across multiple devices and platforms. This means that users' preferences and settings can be seamlessly applied without regard to their sign-in location.

In addition, an API can allow you to create custom user roles. This is beneficial for websites that need to give specific users different access levels. Administrators can create a more seamless and user-friendly experience by using a user management API for all users.

### **Compliance With Security Policies**

API-based user access management can help you comply with security policies, such as the EU [General Data Protection Regulation (GDPR](https://gdpr-info.eu) and the [U.S. Privacy Act](https://www.hhs.gov/foia/privacy/index.html#:~:text=The%20Privacy%20Act%20of%201974,other%20identifying%20number%20or%20symbol.). This is because an API allows you to control how your user data is collected, used and stored. Additionally, an API can help you keep track of user activity and identify potential security risks.

### **A Reduction in Password Issues**

API-based user management can help to reduce password issues, such as forgotten passwords and resetting passwords. This is because an API allows you to use various authentication methods, such as social media sign-in or two-factor authentication. Additionally, an API can help you keep track of user activity and identify potential security risks.

## **What Is Authentication and How Does It Relate to User Management Systems?**

Authentication is the [process of verifying the identity of a user](https://www.forbes.com/sites/davidbalaban/2021/07/27/what-is-authentication-and-how-does-it-work/?sh=33d355b7e2bc). This can be done in a variety of ways, including using a username and password, using a biometric identifier, or using a physical token. User management systems are responsible for managing users' identities within small-and medium-sized businesses (SMBs). This includes creating and maintaining user accounts, assigning permissions to users, and resetting passwords.

Identity and access management (IAM) is a term that is often used interchangeably with the user access management system. However, IAM is a more comprehensive term that encompasses [all of the processes and technologies used to manage user identities](https://www.forbes.com/sites/davidbalaban/2021/07/27/what-is-authentication-and-how-does-it-work/?sh=33d355b7e2bc), including authentication.

When it comes to authentication, user management systems and IAM systems are responsible for managing the credentials that users need to access accounts. This includes storing passwords, generating and issuing tokens, and managing the authentication process.

The relationship between authentication and user access management systems is an important one. User access management systems are responsible for managing users' identities, and authentication is responsible for verifying those identities. Without both of these systems in place, it would be impossible to manage user accounts and ensure that only authorized users have access to them.

There are various authentication methods that SMBs can use, and the best method for a given SMB will depend on its specific needs. User management systems and IAM systems can help SMBs choose the suitable authentication method for the identified needs and ensure that users' identities are properly verified.

## **Types of Authentication for a Frictionless Experience**

There are many different types of authentication, each with its strengths and weaknesses. When choosing an authentication method, it is crucial to consider both the user experience and the security of the system.

One of the most common types of user authentication is single-factor authentication. This method relies on a single piece of evidence to verify the user's identity. The typical form of single-factor authentication is a password. While passwords are relatively easy to use, they can be cracked or guessed if they are not strong enough. As a result, many SMBs are moving to two-factor authentication.

Two-factor authentication adds an additional layer of security by requiring two pieces of evidence to verify the user's identity. The most common form of two-factor authentication is a combination of a password and a physical token, such as a key fob or a card.

Two-factor authentication is much more secure than single-factor authentication because it is much harder for an attacker to get both pieces of information. However, it can inconvenience users who for some reason lose access to the chosen second factor.

Biometric user authentication is another user authentication type that is gaining popularity. This method uses physical or behavioral characteristics to verify the user's identity. Common examples of biometric user authentication include the use of fingerprint scanners, and iris or facial recognition to verify user identity and grant access permissions.

Biometric authentication is more secure than other methods, such as passwords, because it is very difficult to fake someone's biometric information. Additionally, biometrics can be used in conjunction with other methods, such as two-factor authentication, to further improve security. Correct system design is critical to a positive user experience.

Another method of authentication is to use a physical token. This can be in the form of a card, a key, or a badge. The user must have the physical token in order to sign-in. This is more secure than password authentication but loss of the token will necessarily result in user inconvenience.

There are many different types of authentication, but not all of them are equally effective. When choosing an authentication method, it is crucial to consider the user experience as well as the security of the system.

## **Traditional Versus Real-Time User Management Approaches**

There are two approaches to user management: traditional and real-time. Traditional user management is the process of creating, storing, and managing user accounts and passwords. Real-time user management is the process of authenticating user accounts in real time, as users request access to resources.

The traditional approach to user management is time-consuming and error-prone. It requires IT staff to manually create user accounts, assign passwords, and keep track of user activity.

This approach is vulnerable to password attacks, such as [brute force and dictionary attacks](https://www.kaspersky.com/resource-center/definitions/brute-force-attack). It is also susceptible to [social engineering attacks,](https://www.itgovernance.co.uk/social-engineering-attacks) where attackers trick users into revealing user names and corresponding access permissions.

The real-time approach to user access management is more efficient and secure. It uses an authentication server to validate user credentials in real-time. This approach eliminates the need to store user passwords, which reduces the risk of password attacks.

This approach eliminates the need to create and manage user accounts manually. It also reduces the risk of password attacks by validating user credentials before storing them on the server.

The real-time approach to user access management is the preferred method for managing user accounts. It is more efficient and secure than the traditional approach.

## **How Authentication Services Aid User Management**

User access management and authentication go hand-in-hand. Authentication services provide the foundation for keeping user data secure, while user access management tools help maintain control over who has access to that data. By integrating these two technologies, small-and medium-sized businesses can increase security and efficiency while reducing the risk of data breaches.

### **Security Settings**

Start-ups can use authentication services to control access to data and applications. By setting up user groups and assigning permissions, administrators can ensure that only authorized users have access to sensitive information. Additionally, platform administrators can use authentication services to track user activity and monitor suspicious behavior.

#### **Password Management**

One of the most critical aspects of user security is password management. Authentication services can help small-and medium-sized businesses enforce strong password policies and prevent users from reusing passwords across different systems. Identity access management services can also provide a single location from which to manage access to your network users' data, making it easier for users to keep track of sign-in information.

#### **Management of Different Devices**

With the rise of mobile and the practice of allowing employees to use their own computers, phones, or other devices for work purposes (BYOD, or bring your own device), it is more important than ever for startups to be able to manage and control access to data from a variety of devices. Authentication services can help by allowing administrators to set up device-specific permissions and enforce security policies. Identity access management services can also offer a centralized platform for controlling and tracking user activity, which makes it much simpler to identify suspicious behavior across multiple devices.

#### **Multi-Factor Authentication**

Another way to improve security is to require users to authenticate with more than one factor. User platforms can employ multi-factor authentication by using a combination of passwords, biometrics, and tokens. By requiring users to provide multiple forms of authentication, SMBs can increase user interface data security and reduce the risk of unauthorized access.

### **User Profile Management**

User profile management is the term used to describe the process of creating and managing user accounts. User management tools can also be used to manage user profiles. This includes setting up new user accounts and managing user permissions, email addresses, social media accounts, and other user-related information.

By centralizing this information, SMBs can reduce the risk of data breaches and make it easier for users to keep track of sign-in information.

#### **Email Address Management**

Small-and medium-sized businesses can employ user access management tools to manage email addresses. This includes verifying, adding, removing, and updating email addresses.

A user management platform needs to be able to handle bounced emails and other problems that can occur with email addresses. Additionally, user access management tools can be used to set up email forwarding and provide a central location for managing email addresses.

#### **Social Sign-in**

Another critical aspect of user management is social sign-in. Social sign-in allows users to sign-in with existing social media accounts instead of creating new ones. User management tools provide a central location for managing social media accounts, i.e., adding, removing, and updating social media accounts related to individual users.

By integrating authentication services and user access management tools, startups can increase security and efficiency while reducing the risk of data breaches. Startups can accomplish a lot with these tools, i.e., control access to data, enforce strong password policies, and require multi-factor authentication.

User management tools can also help manage user profiles and reduce the risk of data breaches. These technologies can help SMBs keep users data safe and secure when used together.

User profile management is a vital part of any platform that relies on central user identity. It would not be easy to control who has access to what resources without proper identity access management.

## **How To Find the Cloud-Based User Management System for You**

Effective user management is a critical part of any business, and it can be challenging to find the right system for your needs. There are a few things to consider when choosing a user management system.

The first question is how many user names you need to manage. You may not need a full-fledged user management system if you only have a small user base.

You'll also want to consider the features that you need in a user interface. Some identity access management systems offer more features than others. If you need specific features in your user interface, you'll want to choose a system that provides them.

Another thing to consider is how easy the system is to use. You'll want to choose a system that is easy to use so that you can manage your users without any problems.

Finally, you'll want to consider the price of the system. User management systems can vary in price, so you'll want to choose one that fits your budget.

You can narrow down your options by considering these factors and find the appropriate user management service provider for your needs, like Clerk.

Clerk provides the tools you need to manage your user identities and access permissions while also enhancing your user experience. With our services, you can rest assured that your authentication process will be smooth and compliant with any security policies you may have in place.

---

# Building a Custom User Profile with Clerk
URL: https://clerk.com/blog/building-custom-user-profile-with-clerk.md
Date: 2022-08-03
Category: Guides
Description: Authentication is one of the most critical functions of securing your applications; however, it's also one of the most challenging functions to implement.

Failing to implement a proper authentication system can make your application vulnerable and your users' information open to potential hackers.

If you don't want to get into the technicalities of implementing an authentication system from scratch, [Clerk](/) is your solution. Clerk provides built-in components for your application that can be easily integrated into your frontend application, and it supports most of the popular frameworks, like [React](https://reactjs.org), [Next.js](https://nextjs.org), and [Gatsby](https://www.gatsbyjs.com).

Clerk can be beneficial for your customer identity management. You can easily manage your user sessions, devices, and profiles from the Clerk dashboard. Moreover, you can easily integrate Clerk with services like [Firebase](https://firebase.google.com) or [Google Analytics](https://marketingplatform.google.com/about/analytics).

In addition, Clerk makes integrating authentication to your existing application simple since it provides built-in hooks for adding authentication. You can implement social sign-in, password-based authentication, or Web3 logins easily using Clerk. You can also extend your functionalities and create custom components and user profiles using their SDK and APIs.

In this article, you'll see how Clerk authentication can be implemented in your Next.js application and how you can implement the Clerk built-in components for user profiles. You'll also learn how to create custom user profiles for your users and update users' profiles using your custom components.

## What Is Clerk?

Clerk is an all-in-one solution for your authentication and user management needs. It provides features like password-based or social sign-in, [passwordless login using magic links](/blog/magic-links) or email, and SMS passcodes. It also provides a user management dashboard, user analytics, allow/block list, rate limiting, and [more](/#home-features).

With Clerk, you can set up a complete [user management system within ten minutes](https://www.youtube.com/watch?v=mywtHZcrYfE\&feature=emb_imp_woyt). Clerk can seamlessly integrate with popular technologies, including React, Next.js, Gatsby, [RedwoodJS](https://redwoodjs.com), [Remix](https://remix.run), [Node.js](https://nodejs.org/en), and [Go](https://go.dev).

For popular frontend frameworks, like React, Remix, RedwoodJS, or Gatsby, Clerk has created well-designed components for login, sign-up, the user profile, and the user button. However, it's not limited to built-in components. You can always create custom components with the help of hooks provided by the Clerk SDK.

Because Clerk provides you with frontend SDK, API, and backend APIs for developing custom pages for your users, building a customer user profile is easy.

## Building a Custom User Profile Using Clerk

This article aims to show how you can create custom user profiles for your Next.js applications and how to implement the built-in components. To do this, you'll use the [Clerk SDK for Next.js](/nextjs-authentication).

The basic flow of the application is shown in the following GIF:

### Prerequisites

Before you build your custom user profiles with Clerk, you need to satisfy a few prerequisites, including the following:

- Create a Clerk [account](https://dashboard.clerk.com/sign-up).
- Have a basic understanding of Next.js. To learn more about Next.js, you can check out their [documentation](https://nextjs.org/docs).
- Have a basic understanding of [Tailwind CSS](https://tailwindcss.com) (optional)

You can create a free account for Clerk from [the Clerk sign-up page](https://dashboard.clerk.com/sign-up). After creating your account, you'll be redirected to the Clerk dashboard. There, you will be asked to create a new application. Add an application with the name of your choice. This is what the setup for this tutorial looks like:

![Clerk app creation page](./VfxvgIu.png)

After successfully creating the application, you'll be able to visit the application's dashboard:

![Clerk application dashboard](./Y2FoQtw.png)

You can find your API keys in the menu under the **Developers** section. Click on **API Keys** and copy the frontend API key. You'll need this later in the project.

In addition to your Clerk account, Node.js and [npm](https://www.npmjs.com) must be installed on your local computer to create and run the application.

If you want to copy the code and follow along, you can use this [GitHub repo](https://github.com/nemo0/nextjs-clerkdev-custom-profile).

### Creating a Next.js Application

The first step in building this application is to create a Next.js application. To scaffold a Next.js application, run the following command in the terminal:

```bash
npx create-next-app@latest
```

Once the installation is complete, you can run the application using `npm run dev` in the terminal, and the application will start on port `3000`. Next.js will render the `index.jsx` file inside the `pages` folder. You can delete the contents of the `index.jsx` file as it will be customized.

The second step is to integrate Tailwind CSS with your Next.js application. You can follow [this Tailwind CSS installation guide](https://tailwindcss.com/docs/guides/nextjs) to do so.

Then install the [Clerk SDK for the Next.js plug-in](/docs/quickstarts/get-started-with-nextjs). To install the SDK, run the following command in the terminal:

```bash
npm install @clerk/nextjs
```

For using forms effortlessly, you can also integrate the [`react-hook-form`](https://react-hook-form.com) plug-in by running the following command:

```bash
npm install react-hook-form
```

Another plug-in called `react-icons` is used for adding icons to the application. You can install `react-icons` by simply running the following command in your terminal:

```bash
npm install react-icons
```

These previous plug-ins are the only ones that will be used in this application.

## Integrating User Profile in Your Next.js App

To add a user profile to your Next.js app using Clerk, you need to obtain the frontend API key, create a new `.env.local` file, and paste the frontend API key to a variable called `NEXT_PUBLIC_CLERK_FRONTEND_API`. The `.env.local` file should look similar to this:

```bash
NEXT_PUBLIC_CLERK_FRONTEND_API=clerk.noice.bjsdn-81.lcl.dev
```

Then you need to wrap your Next.js application with the `ClerkProvider` component. The `ClerkProvider` wrapper can be found in the `@clerk/nextjs` SDK. The SDK also has methods called `SignedIn`, `SignedOut`, and `RedirectToSignIn`. These components can be used to secure your application. For example, the components wrapped inside the `SignedIn` will require the user to sign in. You can read about the different components [in the official docs](/docs/quickstarts/get-started-with-nextjs).

The `_app.js` file for this application should look like this:

```jsx
import '../styles/globals.css'
import { ClerkProvider, SignedIn, SignedOut, RedirectToSignIn } from '@clerk/nextjs'

import { useRouter } from 'next/router'

import Header from '../components/Header'

const publicPages = []

function MyApp({ Component, pageProps }) {
  const { pathname } = useRouter()
  const isPublicPage = publicPages.includes(pathname)

  return (
    <ClerkProvider frontendApi={process.env.NEXT_PUBLIC_CLERK_FRONTEND_API}>
      {isPublicPage ? (
        <Component {...pageProps} />
      ) : (
        <>
          <SignedIn>
            <Header />
            <Component {...pageProps} />
          </SignedIn>
          <SignedOut>
            <RedirectToSignIn />
          </SignedOut>
        </>
      )}
    </ClerkProvider>
  )
}

export default MyApp
```

You must pass the API key in the `ClerkProvider` through the `frontendApi` prop. The `publicPages` array can store the pages that are available to everyone. But for this article, all the pages will be secured.

The `SignedIn` wrapper holds all the components. That means, if you are signed in, you'll only be able to access the pages. You'll be redirected to the sign-in page if you are not signed in. You'll be redirected to the login screen if you are not logged in using the `RedirectToSignIn` component.

### Header and User Profile

The `Header` component is a very basic header. The code for the `Header` component looks like this in the `Header.jsx` file:

```jsx
import { GiAstronautHelmet } from 'react-icons/gi'
import { CgProfile } from 'react-icons/cg'
import { UserButton } from '@clerk/clerk-react'

import Link from 'next/link'

const Header = (props) => {
  return (
    <div className="w-full bg-purple-600 py-4">
      <div className="mx-auto flex w-10/12 items-center justify-between">
        <Link href={'/'}>
          <a>
            <h4 className="flex items-center text-2xl font-bold text-white">
              <GiAstronautHelmet className="mr-4" />
              Clerk is Awesome
            </h4>
          </a>
        </Link>
        <div>
          <UserButton userProfileUrl="/profile" />
        </div>
      </div>
    </div>
  )
}

export default Header
```

You can see that the `Header` component is using a component called `UserButton`. Clerk provides this component, and the button renders a toggle menu for accessing the user profile:

![UserButton demo](./Fn25xiX.png)

The `userProfileUrl` prop holds the location of the **Manage account** page. Here, the location is `/profile`.

Now, create a new page called `profile.jsx` inside the `pages` folder. You only need to render a single component to generate the user profile:

```jsx
import { UserProfile } from '@clerk/nextjs'

const Profile = () => {
  return <UserProfile />
}
export default Profile
```

The `UserProfile` component provided by Clerk does everything for you and generates an aesthetically pleasing user profile.

If you try to visit the `/profile` route now, you'll need to sign in or create a new account. Clerk already handles the authorization for accessing specific pages. After successfully logging in, you'll be able to visit the user profile page:

![Clerk UserProfile component](./pto9t8C.jpg)

You can also update your profile from here, which you'll learn about in the next section.

### Implementing Custom User Profile

To implement a custom user profile, create a new page called `view.jsx` inside your `pages` directory. This page will render a custom user profile. As this article focuses more on the technicalities of implementing a custom user profile, the design and Tailwind classes will not be discussed.

To help the user access the custom user profile, update your `index.jsx` file like this:

```jsx
import Head from 'next/head'
import styles from '../styles/Home.module.css'

import Link from 'next/dist/client/link'

const Home = () => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="flex h-screen w-full flex-col items-center justify-center">
        <h1 className="text-6xl font-bold text-purple-600">Clerk is Awesome</h1>
        <Link href={'/view'}>
          <a>
            <button className="mt-4 bg-purple-600 px-4 py-2 font-bold text-white transition-all hover:bg-purple-800">
              View Profile
            </button>
          </a>
        </Link>
      </div>
      <div></div>
    </div>
  )
}

export default Home
```

The previous code shows a centered text, "Clerk is Awesome," and a button for accessing the custom user profile with a link of `/view`.

The final design of the custom profile page will look like this:

![Custom user profile screen](./UXNgz7K.png)

You'll have to use the `useUser` hook available in the Clerk SDK to get the necessary information from the backend. The `useUser` hook returns the values of the current user along with other important information, like the user creation date and external connected accounts if two-factor authentication is connected:

![useUser returned data](./S4YNb0g.jpg)

If you look at the returned data closely, you'll find that the data contains an object `unsafeMetadata`. The `unsafeMetadata` object can hold custom values stored for custom user profile information:

![unsafeMetadata object](./g4Ik4K7.png)

For this article, you can see there are two custom fields: `customBio` and `customName`.

Clerk has three types of metadata for storing additional user information: public, private, and unsafe. Both public and private metadata can be updated or added from the backend, and you can access or view only the public metadata from the frontend. The unsafe metadata can be read or written from the frontend:

![Comparison of metadata types](./WyBBEUz.png)

Because of the ability to write from the frontend, this article will use unsafe metadata for custom user information. You can read more about metadata [in Clerk's documentation](/docs/users/metadata).

Look at the `view.jsx` file:

```jsx
import { useUser } from '@clerk/nextjs'

import Image from 'next/image'

import Link from 'next/link'

const ViewProfile = () => {
  const { isLoaded, isSignedIn, user } = useUser()
  if (!isLoaded || !isSignedIn) {
    return null
  }

  console.log(user)

  return (
    <div className="container mx-auto">
      <div className="flex">
        <div className="mx-4">
          <Image
            src={user.profileImageUrl}
            width={'250px'}
            height={'250px'}
            alt={user.fullName}
            className="rounded-lg"
          />
        </div>
        <div className="ml-4">
          <div className="-mx-4 overflow-x-auto px-4 py-4 sm:-mx-8 sm:px-8">
            <div className="inline-block w-full overflow-hidden rounded-lg shadow-md">
              <table className="w-full leading-normal">
                <tbody>
                  {/* Firstname */}
                  <tr>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      First Name
                    </td>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      {user.firstName}
                    </td>
                  </tr>
                  {/* Last Name */}
                  <tr>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      Last Name
                    </td>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      {user.lastName}
                    </td>
                  </tr>
                  {/* Emails */}
                  <tr>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      Emails
                    </td>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      {user.emailAddresses.map((email) => (
                        <div key={email.emailAddress}>{email.emailAddress}, </div>
                      ))}
                    </td>
                  </tr>
                  {/* Unsafe Metadata Example */}
                  <tr>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      Custom Name
                    </td>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      {user.unsafeMetadata.customName}
                    </td>
                  </tr>
                  <tr>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      Custom Bio
                    </td>
                    <td className="whitespace-no-wrap border-b border-gray-200 bg-white px-5 py-5 text-sm text-gray-900">
                      {user.unsafeMetadata.customBio}
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
          <div className="flex justify-center">
            <Link href={'/additional'}>
              <button className="mt-4 bg-purple-600 px-4 py-2 font-bold text-white transition-all hover:bg-purple-800">
                Update Additional Information
              </button>
            </Link>
          </div>
        </div>
      </div>
    </div>
  )
}

export default ViewProfile
```

Don't get overwhelmed. The code can look complicated, but it's not. Now, break down the essential components. Begin by importing the `useUser` hook from the Clerk SDK. Then the `Image` and `Link` components are imported from Next.js.

The `ViewProfile` component renders the user profile. The initial step of using `useUser` is to destructure its essential functions:

```jsx
const { isLoaded, isSignedIn, user } = useUser()
if (!isLoaded || !isSignedIn) {
  return null
}
```

In the previous code, the functions check if the page is not loaded or the user is not signed in. If not, then nothing is rendered. You can console the `user` object here:

```jsx
console.log(user)
```

This will return all the information available to Clerk for the particular user. Now that you have access to the `user` object, you can use it to render the profile values. For example, you can generate the user's profile image by simply accessing the `user.profileImageUrl` key. The first name of the user is stored inside the `user.firstName` key.

The template here only uses these keys: `user.profileImageUrl`, `user.firstName`, `user.lastName`, `user.fullName`, `user.emailAddresses`, and `user.unsafeMetadata`. The custom user profile can be implemented using the `user` object.

### Updating Current User Profile

If you look at the previous code, you'll find that it also contains a link to another page for updating the profile information. Look at how you can edit user information from a custom profile update page.

Create a new page with the name `additional.jsx` file inside the `pages` directory. The `react-hook-form` plug-in will be used here, though it's not necessary for a simple form like this. If your form is large and complex, `react-hook-form` is a great solution. This plug-in makes the binding of the input fields simple. You can look at React Hook Form's "Get Started" [example](https://react-hook-form.com/get-started) to get a basic idea of how it works.

Now take a look at the complete code and then break it into parts:

```jsx
import { useForm } from 'react-hook-form'
import { useUser } from '@clerk/nextjs/dist/client'

import { useRouter } from 'next/router'

const AdditionalUpdate = () => {
  const router = useRouter()

  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm()

  const { isLoaded, isSignedIn, user } = useUser()

  const onSubmit = (data) => {
    try {
      user.update({
        firstName: data.firstName,
        lastName: data.lastName,
        unsafeMetadata: {
          customName: data.customName,
          customBio: data.customBio,
        },
      })

      router.push('/view')
    } catch (error) {
      console.log(error)
    }
  }

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

  return (
    <div className="mx-10">
      <h1 className="py-4 text-2xl font-bold">Update Additional Information</h1>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label className="mb-2 block text-sm font-bold text-gray-700" htmlFor="firstName">
            First Name
          </label>
          <input
            defaultValue={user.firstName}
            {...register('firstName', {
              required: true,
            })}
            className="focus:shadow-outline w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
          />
          {errors.firstName && <span className="text-sm text-red-600">This field is required</span>}
        </div>
        <div>
          <label className="mb-2 block text-sm font-bold text-gray-700" htmlFor="lastName">
            Last Name
          </label>
          <input
            defaultValue={user.lastName}
            {...register('lastName', {
              required: true,
            })}
            className="focus:shadow-outline w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
          />
          {errors.lastName && <span className="text-sm text-red-600">This field is required</span>}
        </div>
        <div>
          <label className="mb-2 block text-sm font-bold text-gray-700" htmlFor="customName">
            Custom Name
          </label>
          <input
            defaultValue={user.unsafeMetadata.customName}
            {...register('customName', {
              required: true,
            })}
            className="focus:shadow-outline w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
          />
          {errors.customName && (
            <span className="text-sm text-red-600">This field is required</span>
          )}
        </div>
        <div className="mt-4">
          <label className="mb-2 block text-sm font-bold text-gray-700" htmlFor="customBio">
            Custom Bio
          </label>
          <textarea
            rows={6}
            defaultValue={user.unsafeMetadata.customBio}
            {...register('customBio', {
              required: true,
            })}
            className="focus:shadow-outline w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none"
          ></textarea>
          {errors.customBio && <span className="text-sm text-red-600">This field is required</span>}
        </div>

        <button
          type="submit"
          className="my-4 bg-purple-500 px-8 py-2 text-lg font-semibold text-white transition-all hover:bg-purple-700"
        >
          Update
        </button>
      </form>
    </div>
  )
}

export default AdditionalUpdate
```

The `onSubmit` function is used for saving the updated information to the server. The `user.update` function is used for updating the values. A new object with the updated values is passed into this function:

```jsx
user.update({
  firstName: data.firstName,
  lastName: data.lastName,
  unsafeMetadata: {
    customName: data.customName,
    customBio: data.customBio,
  },
})
```

As you can see from the previous object, the `firstName`, `lastName`, and two custom fields are being updated. The custom fields can be updated by updating the keys inside `unsafeMetadata`.

The `user.update` function is wrapped inside a `try…catch` block. The page will be redirected to the custom user profile if the object is successfully updated.

But how do you render the already existing values of the user? It's implemented using a similar approach to building a custom user profile. The `defaultValue` of the input field is filled with the corresponding `user` object value:

```jsx
<input
  defaultValue={user.firstName}
  {...register('firstName', {
    required: true,
  })}
/>
```

The `register` method is a `react-hook-form` method that registers the input field with the value passed. For example, the previous code registers the value with `firstName`. You can access this value by accessing the `data.firstName` object.

Finally, the complete template is placed inside `form` tags, where the `onSubmit` function looks like this: `onSubmit={handleSubmit(onSubmit)}`. The `handleSubmit` function is a `react-hook-form` function that handles submissions. It takes in another function as a parameter, and the `onSubmit` function is passed here.

The last thing you need to do is add `www.gravatar.com` to your `next.config.js` file. When there is no profile picture set by the user for their profile, a Gravatar is shown. The image component in Next.js requires the hostnames to be added to the `next.config.js`. Your `next.config.js` file should look like this:

```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  images: {
    domains: ['images.clerk.com', 'www.gravatar.com'],
  },
}

module.exports = nextConfig
```

Your user profile update page is now ready.

You can access and check the page by visiting the localhost URL, `http://localhost:3000/additional`. You can also check this [GitHub repo](https://github.com/nemo0/nextjs-clerkdev-custom-profile) for all the code from this tutorial.

The functionalities discussed earlier can also be implemented using the Clerk frontend API. The frontend API has endpoints like `https://clerk.example.com/v1/me` for updating the user profile from the frontend. You can check the [frontend API documentation](https://clerk.com/docs/reference/frontend-api) to learn more.

## Conclusion

[Clerk](/) is a great solution for quickly integrating authentication and custom user profiles into your application. It provides more than authentication; you can manage users, sessions, APIs, and more right from the Clerk dashboard.

This article aims to show you how custom user profiles can be built using the Next.js Clerk SDK. You also saw how Clerk components could be used for rapid development.

You can get started with Clerk for free for up to 500 monthly active users in your application. To create a Clerk account, [visit their sign-up page](https://dashboard.clerk.com/sign-up).

---

# Build an App with Clerk, Prisma Cloud, and Next.js
URL: https://clerk.com/blog/build-app-with-clerk-prisma-nextjs.md
Date: 2022-07-27
Category: Guides
Description: Clerk is an easy-to-use, customizable, and secure customer identity platform that provides you with multiple authentication strategies for your application. 

This platform helps developers handle user management processes for their applications, allowing them to avoid re-inventing the wheel in user authentication strategies and focus on developing the core of their business.

[Clerk](/) can be used to add customer identity management to your application and handle authentication for your application by adding custom sign-in methods. It can also help users track the devices signed in to their accounts on the user profile page, and provide leaked passwords protection by comparing the password entered by the user with the passwords found in major data breaches, as well as many more [features](/#home-features).

In this article, you will learn how to build an app using Clerk, [Prisma Cloud](https://cloud.prisma.io), and [Next.js](https://nextjs.org). This will be a simple application that helps you understand how to implement authentication in your Next.js application using Clerk. The finished application will only show the data from the database to the authenticated users.

## The Tech Stack

The following technologies will be used to create the app:

- [Next.js](https://nextjs.org) is a framework built on top of [React](https://reactjs.org). Next.js is optimized for developer experience, and provides all the features required to build powerful and highly interactive applications with no configuration required.

- [Clerk](/) is a customer identity platform compatible with popular stacks such as JavaScript, React, Next.js, Ruby, and Firebase. It allows you to implement a strong authentication system in your application by providing pre-built components such as `<SignIn />`, `<SignUp />`, and `<UserProfile />`. Clerk supports multiple authentication strategies, including passwords, [magic links](/blog/magic-links), social logins, Web3, multifactor authentication and email or SMS passcodes, and allows you to easily implement them in your application.

- [Prisma Cloud](https://cloud.prisma.io) is a cloud-based collaborative environment that provides support to developers using [Prisma](https://www.prisma.io), an object-relational mapper (ORM) that helps you model data in a human-readable format. Prisma Cloud provides you with an online data browser that allows you to easily view and edit data collaboratively online. It also offers a query console and ability to integrate with your Prisma databases and schema.

- [Heroku](https://www.heroku.com) is a cloud platform that allows developers to build, run, and operate applications.

- [PostgreSQL](https://www.postgresql.org) is an advanced open source relational database management system.

## Building an App with Clerk, Prisma Cloud, and Next.js

To follow along in building this app, you need to have the following set up:

- A code editor of your choice
- [Node.js](https://nodejs.org/en/download) and [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) installed on your development machine
- A [Clerk](https://dashboard.clerk.com/sign-up) account
- A [Prisma Cloud](https://cloud.prisma.io) account
- A [Heroku](https://signup.heroku.com) account

You can see all the code for this application in one place in this [GitHub repository](https://github.com/kimanikevin254/clerk-next-prismacloud).

### Creating a Next.js Application

The easiest way to create a Next.js application is by using `create-next-app`, which sets up everything for you automatically. To create the application, run the following command in the terminal:

```bash
yarn create next-app clerk-prisma
```

The command above creates a folder with the name `clerk-prisma`. This application will only contain a navigation bar and a content area. Open `pages/index.js` in the project root folder and replace the existing code with the following:

```js
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      <nav className={styles.nav}>
        <h2>My Tasks</h2>
      </nav>

      <div>
        <p>Sign in to view your tasks!</p>
      </div>
    </div>
  )
}
```

To provide some styling for the application, open `styles/Home.module.css` and replace the existing code with the following:

```css
.container {
  margin: 1rem auto;
  max-width: 760px;
  text-align: center;
}

.nav {
  background: gray;
  padding: 0.2rem;
}
```

Run the following command in the terminal to start a development server:

```bash
yarn dev
```

Open `http://localhost:3000/` in your browser and you should see the following:

![A website created with Next.js](./TGmz18s.jpeg)

### Adding Clerk

To add Clerk for user authentication in the application, open the [Clerk dashboard](https://dashboard.clerk.com) in your browser and create a new application by providing the required details.

![An image showing the creation of a new app on the Clerk dashboard](./bCq95OT.jpeg)

Provide the application name, and accept the default settings, which will allow the user to sign in using their email address and a password or to use their Google account. Click the **FINISH** button and you will be navigated to your application details page.

> Creating a new application in Clerk automatically creates a new development instance optimized for local application development.

#### Adding Sign-Up and Sign-In Functionality to the Application Using Clerk

In this section, you will add sign-up and sign-in forms, and build a page that is only visible to authenticated users.

Run the following command in your terminal to install Clerk's Next.js SDK, which gives you access to prebuilt components, hooks, and other features provided by Clerk:

```bash
yarn add @clerk/nextjs
```

You now need to link your local development with the Clerk instance created when you initialized your new app in Clerk. On your Clerk dashboard [API Keys page](https://dashboard.clerk.com/last-active?path=api-keys), copy the `Frontend API key`, create a `.env.local` file in the project root folder and paste in the key in the file as shown below:

```env
NEXT_PUBLIC_CLERK_FRONTEND_API={your_frontend_api_key}
```

Open the terminal to kill the running application by pressing `CTRL+C` or `CMD+C`, and relaunch the application with the command below to load the environment variables:

```bash
yarn dev
```

To use the Clerk context in the application, the entire application needs to be wrapped in the `<ClerkProvider />` component. To achieve this, open the `pages/_app.js` file and replace the existing content with the following:

```js
import '../styles/globals.css'
import { ClerkProvider } from '@clerk/nextjs'

function MyApp({ Component, pageProps }) {
  return (
    <ClerkProvider {...pageProps}>
      <Component {...pageProps} />
    </ClerkProvider>
  )
}

export default MyApp
```

It's time to add a sign-in button and some content for authenticated users. You'll achieve this with some pre-built components and a hook provided by Clerk. These are:

- `useUser()`: This is a hook used to access the current user and user state—whether they're logged in or not.
- `<SignInButton />`: This will display a sign-in button on the webpage.
- `<SignUpButton />`: This will display a sign-up button on the webpage.
- `<UserButton />`: This component displays a button for the current user to access their profile details.

You can learn more about Clerk's components, hooks, and helper functions in the [official documentation](/docs/component-reference/overview).

Open `pages/index.js` file and replace it with the following code:

```js
import styles from '../styles/Home.module.css'

import { useUser, UserButton, SignInButton, SignUpButton } from '@clerk/nextjs'

export default function Home() {
  const { isSignedIn, isLoading, user } = useUser()
  return (
    <div className={styles.container}>
      <nav className={styles.nav}>
        <h2>My Tasks</h2>
        {isSignedIn ? (
          <UserButton />
        ) : (
          <div>
            <SignInButton />
            <SignUpButton />
          </div>
        )}
      </nav>

      <div>
        {isLoading ? (
          <></>
        ) : (
          <div>
            {isSignedIn ? (
              <div>
                <p>Welcome {user.firstName}!</p>
              </div>
            ) : (
              <p>Sign in to view your tasks!</p>
            )}
          </div>
        )}
      </div>
    </div>
  )
}
```

Once you've implemented the preceding code, there will be a different view for users who are signed in and users who are not.

Signed Out Users:
![The view for users who have not signed in](./sIsmqeu.jpeg)

Signed In Users:
![The view for users who have signed in](./66NjqYF.jpeg)

### Adding Prisma to the Application

To add Prisma to the application, run the following commands in your terminal:

```bash
yarn add -D prisma

yarn add @prisma/client

npx prisma init
```

The first command installs Prisma as a `dev` dependency and gives you access to the Prisma CLI. The second command installs the Prisma client, which is a query builder, and the last command initializes Prisma in your application. Initializing Prisma creates a `prisma` folder with a `schema.prisma` file. It also creates a `.env` file in the project root folder with the `DATABASE_URL` variable.

To connect to Prisma Cloud, open [https://cloud.prisma.io](https://cloud.prisma.io) in your browser and create a new project. On the "Configure project" page, provide a project name, connect your GitHub account and click the **Create a repository** option. Click **Next** and go to the next page.

![The project configuration page](./ayaapkW.jpeg)

On the "Select template" page, select the **Empty** template option and click **Next**.

![The select template page](./klvxVYV.jpeg)

On the "Configure environment" page, click on **Provision a new database**, select **Heroku PostgreSQL** as the database provider, and click **Sign in with Heroku** to connect your Heroku account. After signing with Heroku, provide a name for your application. Click on the **Create project** button.

![The configure environment page](./j8CclCr.jpeg)

On the "Deploy project page", copy the `DATABASE_URL` and `DATABASE_MIGRATE_URL`, and insert them at the appropriate points in your local `.env` file, then click **DONE**.

![The deploy project page](./8tTSAON.jpeg)

Your local `.env` file should now have the following:

```bash
DATABASE_URL={YOUR_DATABASE_URL_VALUE}

MIGRATE_DATABASE_URL={YOUR_MIGRATE_DATABASE_URL_VALUE}
```

#### Creating a Shadow Database Manually

Some Prisma commands that are used only in development, such as `prisma migrate`, need a temporary second database that is automatically created and deleted every time these commands are run. Because Heroku PostgreSQL is hosted on the cloud, you need to create a shadow database manually and copy the connection string.

While this tutorial will walk you through the relevant steps, if you'd like to learn more about Prisma shadow databases, you can do so [here](https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database).

To create the shadow database, open your [Heroku dashboard](https://dashboard.heroku.com/apps), and locate and open the newly created project. Under the **Resources** tab, click on the **Add-ons** search bar and type "Heroku Postgres". Click on the first result to add the database.

![The deploy project page](./8tTSAON.jpeg)

Please note that you will now have two databases, as shown below:

![Two databases on Heroku](./jxDQo5T.jpeg)

Click on the one on top (the one you created manually) to open its details. On the **Settings** tab under **Database Credentials**, click on **View Credentials** and copy the database `URI`. You will add this to your local `.env` file to allow you to connect to the database.

![The shadow DB details page](./V2PggEC.jpeg)

Add the `URI` to the local `.env` file with the key `SHADOW_DATABASE_URL`.

Your local `.env` file should now have the following details:

```env
DATABASE_URL={YOUR DATABASE URL}

MIGRATE_DATABASE_URL={YOUR DATABASE MIGRATE URL}

SHADOW_DATABASE_URL={YOUR SHADOW DATABASE URL}
```

#### Defining a Schema for Your Model

To define a schema for your tasks model, open the `prisma/schema.prisma` file, and replace the existing content with the following:

```prisma
datasource db {
  provider = "postgres"
  url      = env("MIGRATE_DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
  referentialIntegrity = "prisma"
}

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

model Tasks {
  id  String  @id @default(cuid())
  task String
}
```

The `datasource db` provides details for connecting to the database for running migrations and querying. When querying the database after running the migrations, the value for `url` in `database db` will be switched to `env("DATABASE_URL")`. This is because at the time of writing, one of the limitations of the data proxy is that it can only used to [query the database with Prisma Client](https://www.prisma.io/docs/data-platform/data-proxy#important-considerations-about-the-data-proxy). The `DATABASE_MIGRATE_URL` contains a direct connection string to the database that will be used to run migrations.

The tasks model will only contain two fields:

- **`id`:** The task id
- **`task`:** The task name

#### Running the Migrations and Adding Some Data with Prisma

To run the migrations and add the `tasks` table to the database, run the following command in your terminal, and provide a name for the new migration:

```bash
npx prisma migrate dev
```

When the migration runs successfully, you should see this message on the terminal:

![A successful prisma migration](./CwMw0jY.jpeg)

Now that your database is in sync with your schema, you can add some data with Prisma. To do this, run the following command in your terminal to open Prisma Studio in your browser:

```bash
npx prisma studio
```

After Prisma Studio loads, select your model to add some data. Click on the **Add record** button, add three tasks, and click on the **Save 3 changes** button to save the tasks.

![Prisma Studio interface](./8ce8zc6.jpeg)

#### Showing the Data to Authenticated Users

Open the `prisma/schema.prisma` file and change the `url` value in the `datasource db` to `env("DATABASE_URL")`. The `prisma/schema.prisma` file should now look like this:

```prisma
datasource db {
  provider = "postgres"
  url      = env("DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
  referentialIntegrity = "prisma"
}

generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

model Tasks {
  id  String  @id @default(cuid())
  task String
}
```

Next, you need to fetch the data from the database. This can be achieved by modifying the `pages/index.js` file as shown below:

```js
import styles from '../styles/Home.module.css'

import { useUser, UserButton, SignInButton, SignUpButton } from '@clerk/nextjs'
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export default function Home({ tasks }) {
  const { isSignedIn, isLoading, user } = useUser()
  return (
    <div className={styles.container}>
      <nav className={styles.nav}>
        <h2>My Tasks</h2>
        {isSignedIn ? (
          <UserButton />
        ) : (
          <div>
            <SignInButton />
            <SignUpButton />
          </div>
        )}
      </nav>

      <div>
        {isLoading ? (
          <></>
        ) : (
          <div>
            {isSignedIn ? (
              <div>
                <p>Welcome {user.firstName}!</p>
                {tasks ? tasks.map((task) => <p>{task.task}</p>) : <div></div>}
              </div>
            ) : (
              <p>Sign in to view your tasks!</p>
            )}
          </div>
        )}
      </div>
    </div>
  )
}

export async function getServerSideProps() {
  const tasks = await prisma.tasks.findMany()
  return {
    props: {
      tasks: tasks,
    },
  }
}
```

Open `http://localhost:3000/` in your browser, and once you've logged in, you should be able to see the tasks you just added.

![The added tasks after logging in](./NTxO8tD.jpeg)

## Conclusion

In this article, you have learned about [Clerk](/), a powerful service that can handle user identity management for you. You have also learned about the various use cases of Clerk, a few of its components and hooks, and how you can [integrate it into your Next.js application](/nextjs-authentication) to easily handle user authentication. Finally, you have learned how to integrate Prisma into the application, use Prisma Cloud, and add data to your cloud-hosted database on Heroku.

---

# The Journey to Modern Web Authentication
URL: https://clerk.com/blog/the-journey-to-modern-web-authentication.md
Date: 2022-07-21
Category: Company
Description: A blog post about the story of Clerk; our journey of bringing seamless authentication to the Modern Web, where we stand now, and what lies ahead.

Web authentication is an acronym and protocol ensemble ([SSO](https://en.wikipedia.org/wiki/Single_sign-on), [OAuth](https://oauth.net), [SAML](https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language), [OpenID](https://openid.net/connect), [SCIM](https://en.wikipedia.org/wiki/System_for_Cross-domain_Identity_Management), [FIDO](https://en.wikipedia.org/wiki/FIDO_Alliance), [PKCE](https://oauth.net/2/pkce), [JWT](https://jwt.io), etc...) that traditional user management software implements one way or another. Developers know that adding authentication to an application is a mundane and tedious task and they increasingly choose to outsource it.

Authentication requires user input, usually collected via redirection to authentication pages such as the sign-up or sign-in page. UX-wise the redirection is a clean cut. Things get fascinating in the modern web world with [React](https://reactjs.org), [Vue](https://vuejs.org), [Svelte](https://svelte.dev) or [Angular](https://angular.io) applications. Users expect to land and stay on the same tab, sign in fast and securely with a Google-like experience, and maintain their sessions in a safe and unobtrusive way until they log out.

Let's mull over how to build a sign-in form with SMS [two-factor authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication) (2FA) in React. Where will the state live? Will the sign-in flow be bookmarkable? How will we transform user credentials into a session? Should I use a session cookie or a JWT? How will I ensure that the UI doesn't flicker when a guest user authenticates?

User management software so far offers the necessary authentication features but integrating them into your React application is cumbersome. This is why we've built [Clerk](/); to offer a modern-web customer identity platform that just works.

From early on, our goal was to focus on developer experience. To achieve that we had to start with a framework so we picked the most popular at the time, [Next.js](/docs/quickstarts/get-started-with-nextjs). We knew that React users expect components and hooks, so we offered both of them from the start.

While building our [React components library](/docs/component-reference/overview), we had to decide how we will package it in order to make it easy to set up and upgrade. Hot-loading the latest version using semantic versioning was our preferred choice. Another requirement was the ability to customize our components to match the look'n'feel of the host application. We decided to start simple with CSS overwrites.

The hooks API design was also exciting. We debated a lot whether *`const {user} = useUser()`* should return *`null`* or an empty *`new User()`* when not authenticated. We spent countless hours trying to figure out how should we store and update the session state. The first iteration resulted in a polling mechanism with a lot of auth data piggybacking.

The initial adoption was really warm. Clerk began to mature. We started designing new components, adding more authentication features, revamping our state management, and improving our developer experience.

In the meantime, modern web frameworks were evolving rapidly. [SSR](https://nextjs.org/docs/basic-features/pages#server-side-rendering), used mostly for public, static content back in 2020 became the defacto choice for SaaS. Edge computing support for fast API endpoints was added and distributed web infra­structure improved immensely. So we followed. Clerk was ahead of the game and one of the first solutions to add native SSR support for our [hooks](/docs/reference/overview). We also moved to [session validation at the Edge](/docs/request-authentication/nextjs#using-edge-middleware) so that verifying the JWT takes less than 1 ms.

In the first half of 2022, the product went from strength to strength. We added [organizations](/docs/organizations/overview), we bridged [web3 and web2 authentication](/blog/introducing-web3-authentication), we integrated with JWT-powered databases and tools such as [Hasura](/docs/integration/hasura) and [Supabase](/docs/integration/supabase), we expanded to more React frameworks such as [Remix](/docs/quickstarts/get-started-with-remix), [Redwood.js](/docs/quickstarts/get-started-with-redwoodjs), [Gatsby](/docs/quickstarts/get-started-with-gatsby), [Expo](/docs/reference/clerk-expo), and we've just released our new, fresh, and [fully customizable ClerkJS Components](/blog/changelog-2022-07-15).

For the second half of the year, we have a lot of exciting epics in the pipeline. First and foremost, we aim to be the first authentication solution that works with [React server components](https://nextjs.org/docs/app/building-your-application/rendering/server-components). In addition, customers request [SSR for our ClerkJS Components](https://github.com/clerkinc/javascript/discussions/183). Remember how hot-loading was a great idea two years ago? With strong SSR support, it's not anymore. We have also started working on making all our [SDKs isomorphic](https://github.com/clerkinc/javascript/issues/127) to seamlessly work on Node and V8 runtimes. And of course, we will expand our customization capabilities, as we intend to allow developers to use their [Chakra](https://chakra-ui.com) or [Material UI](https://mui.com) components in their Clerk authentication forms.

On a personal level, I am looking forward to seeing how bleeding-edge web tech will evolve and shape how we build applications. If you are intrigued by any of these topics, [talk to us on Discord](https://clerk.com/contact/support) or [apply](/careers) to join our expanding team. We are an international, all-remote team and we are [currently hiring](/careers).

---

# What is Next.js?
URL: https://clerk.com/blog/what-is-nextjs.md
Date: 2022-07-20
Category: Insights
Description: Next.js is a framework in the React ecosystem that is primarily used for developing JavaScript applications. Understand the ins and outs below.

*Updated: 09/01/2022*

Web applications are a fundamental part of the modern internet. As a web developer, you likely spend a lot of time trying to make the development process for these apps simpler and more efficient. That's why you should understand how Next.js works.

Next.js is a popular framework that makes it easier to develop JavaScript applications of all kinds. In this guide, you'll learn the answers to questions like, "What is Next.js?" and, "Why use Next.js?" You’ll also learn about the best use cases for Next.js in your development process. If you're ready to learn more, then let's dive in.

## **What Is Next.js?**

Next.js is a React framework that gives programmers a structured environment to quickly create fast web applications. With Next.js, you can use React’s powerful tools without having to build your application from the ground up.

React is a specific JavaScript library designed to help you develop interactive user interfaces (UIs). On its own, React is a useful tool, but it still requires a significant amount of work to use it correctly. That's why Next.js was built. But how does Next.js work?

Next.js is a React framework. The difference between libraries and frameworks is important. Libraries are just collections of useful tools. Like with a brick-and-mortar library, you're responsible for finding the specific information and tools you need. You have to call the library and tell it what you need.

In contrast, frameworks handle that for you. When you work within a framework, you don't need to worry about calling the library or pulling the tools. It acts as a frame for your application, giving it a fundamental structure and preventing errors from creeping in.

That's what Next.js does for React. The Next.js framework structures the React library and gives it a solid form. When you start a project using Next.js, the framework configures many settings behind the scenes and provides extra commands that allow you to accomplish tasks that aren't possible through the React library alone. Because of that, building applications within the Next.js framework takes less time while still offering all the benefits of the React library.

## **What Functionalities Does Next.js Offer?**

Next.js offers a broad range of functionalities. The framework was designed to provide not only structured access to the React library but also extra features. When you choose to use Next.js to build an application, you're guaranteeing that you'll have access to these functionalities.

### **Client-Side Rendering for Interactivity**

One of the most valuable tools Next.js offers is client-side rendering. There are three ways that Next.js can render a user interface: client-side, server-side, or static site generation. Client-side rendering is the best way to make an interactive, responsive UI.

With client-side rendering, the UI is generated on the client's end of the application. The server sends basic HTML to the app along with JavaScript code. That JavaScript code contains the instructions for the app to generate the UI, including more complex HTML, in the moment.

This allows the connection between the client's device and the server to happen quickly. All of the complicated parts of the rendering the UI happen on the client's end. As a result, there's no need to constantly query the server regarding how the UI should change based on the client's actions. The UI is able to react to the client instantly, making interactive elements smoother and less prone to delays.

### **Static Site Generation to Help Automate Your Process**

If interactivity isn't a primary goal of the application, then Next.js also supports static site generation. With this method, the user interface is prerendered by the server. More importantly, the server needs to generate this only once. The HTML is created and then stored in a content delivery network (CDN). Whenever that specific UI is needed, the app pulls the HTML from the CDN and presents it. There's no need for any rendering in the moment for the client or the server.

Prerendering your UI with Next.js's static site generation is a great way to automate certain parts of your process. Static pages may not be the most interactive, but they're quick to load and require no work from your own servers. This method takes some pressure off your servers and allows you to automate routine tasks like displaying loading screens, menus, and other low-interaction pages.

### **Ability to Build Complete Web Applications Quickly**

Part of what makes working in any framework so appealing is the way it can speed up application development. When you work with Next.js, you don't need to build your app from scratch. The framework has already laid the foundation and set the parameters for you. It even has rules and guidelines in place to help you avoid making mistakes that could cause delays or failures.

That functionality makes it easy to build your next web application quickly. Next.js helps you avoid wasting time on basic tasks and setup. Instead, you can start working on the meat of your application right away, building a complete, functional, good-looking app in less time.

## **The Impact Next.js Has on the User Experience**

It's clear that Next.js offers developers many potential tools to make applications faster and more efficient. However, the framework also impacts the user experience (UX). Next.js provides flexibility and customization options that make the UX better in three important ways:

- Security
- Speed
- Personalization

Here's how the Next.js framework supports each of these UX elements.

### **Lack of Direct Database Connection, Keeping Static Websites Secure**

Application security is a fundamental necessity for modern developers. If you don't create a secure app, your users are at risk of having their devices hacked and their information stolen. Obviously, this has a significant negative impact on the user experience.

Next.js can help make your apps more secure, particularly if you use static site generation. With static generation, there's no direct connection to any databases. User devices query the CDN instead, which provides the prerendered code. As a result, there's no easy way for hackers to inject malicious code into the database results. Users face a lower risk of malware because Next.js has stripped the need to render code on either end of the process.

Furthermore, static site generation protects user information on your servers, too. Hackers who access your web application won't be able to study its direct connection to your servers because there isn't one. There's no way for them to learn how to call your server databases and potentially access sensitive information like sign-in details or payment information.

### **Significantly Faster Page Load Times**

Whether you choose static site generation or client-side rendering, Next.js can help you significantly reduce page load times.[ According to Google](https://www.thinkwithgoogle.com/marketing-strategies/app-and-mobile/page-load-time-statistics), visitors are 32% more likely to leave a webpage that takes three seconds to load compared to a page that loads in one second. Reducing page load times is a simple but valuable way to keep users engaged with your site and prevent bounces.

Static site generation means that there's no need to render a page at all. Next.js allows the next page to load significantly faster because all it needs is the HTML that the CDN provides. There's no additional rendering requirement on either end.

Client-side rendering can be almost as fast. There's no extra delay between your server and the client's device, since your server is just providing HTML and JavaScript instructions. The client's device is responsible for rendering the actual user interface. As long as the device has reasonable rendering times, the user will see the entire page significantly faster than other methods allow.

### **Ability to Create a Completely Customized Front End**

After security and speed, the third element of a great user experience is a beautiful, functional user interface. Next.js gives UX teams the ability to build a completely customized front end to fit the app's brand and target audience.

Some frameworks limit what developers can do with various elements. That's the nature of a framework: While it offers support, it also imposes limitations, just like walls in a house. However, Next.js has been carefully constructed to give developers complete freedom to customize the front end of their web applications.

That freedom means it's easy to develop appealing front-end UIs that users enjoy interacting with. There are no clunky requirements that may get in the way of your dev team's goals or needs. You can focus on providing a great experience for your users instead of fighting with the framework or reinventing the wheel.

## **Why Front-End Developers Use Next.js**

You might still be wondering, "Why use Next.js?" It might help to understand its appeal to front-end developers. Devs using the framework appreciate it for the following functionalities.

### **Server-Side Rendering**

Server-side rendering allows your web application to quickly display interactive pages that may not be easy to render on client devices. This tool lets you implement more complicated user interfaces by placing the load on your servers instead of the client's device.

Your server will generate the HTML for each request, along with JavaScript instructions for interactivity. The HTML will allow the page to quickly display a noninteractive page. After that's displayed, the JavaScript instructions will be interpreted behind the scenes to make the page interactive. In most cases, users won't notice the delay between the page appearing and the JavaScript making it functional, since they need time to decide what to click next. Meanwhile, you get to develop more complex interactive pages without risking slowing down your site.

### **Automatic Compilation and Bundling**

A significant benefit of using a good framework like Next.js is the way it takes basic tasks off your plate. Next.js is structured to provide automatic compilation and bundling, meaning that it's "zero config." You don't need to do any configuration to get the framework ready for production.

Without the need to configure anything, Next.js is perfectly optimized for production from the moment you start development. That can save you significant time and prevent annoying and wasteful compilation failures. When your application is finished, all you need to do is test the compilation and bundling process once, and then move it to production. You can trust Next.js to handle the rest.

### **Importing CSS Files**

Next.js allows you to directly import CSS files from a JavaScript file. The built-in CSS parser makes it easy to import global style sheets that will apply to the entire web application. The style sheet will ensure that every part of your app is presented consistently, so you don't need to set the presentation for each element individually.

If you do have specific components that need unique CSS, that's possible too. You can import CSS Module files anywhere in an application just as easily as you would apply a global style sheet. The module will override the global style sheet in the locations you apply it to, so you can customize specific elements with ease.

This is possible because Next.js extends the import function to cover more than just JavaScript. If you choose to use the framework, this extended import functionality makes rapid development simpler and more accessible.

### **Data Fetching**

Next.js data fetching includes server-side rendering, client-side rendering, and static page generation. You're not limited to just one of these methods, either. While you can develop your entire application with one data-fetching technique, you can also combine them so each page of your site is generated as efficiently as possible.

For instance, you could use static generation to automate your simplest pages, such as news articles or blog posts. Meanwhile, you could use server-side generation to load your homepage, where things may frequently change but speed is of the essence. Finally, if you have a ticker page or other pages that should update without a refresh, client-side rendering is ideal. Next.js allows you to choose which pages are loaded with which method, giving you more flexibility.

### **Code Splitting**

Web applications rely on code splitting to function. Most web applications involve multiple pages, each of which can be visited through a specific URL. That makes every page a unique entry point to the app.

Code splitting is the process of dividing the total application's code bundle into chunks that are relevant to each page in particular. This speeds up load times by avoiding the need to render code that's irrelevant to the specific page.

Next.js automatically handles code splitting for you. The framework takes each file in the "pages/" directory and generates a specific JavaScript bundle for it, which will be called up when visitors access the page. This saves you time and effort.

### **React Ecosystem Access**

Finally, Next.js gives you access to the entire React ecosystem. As a React framework, it's built to cooperate with other React components. You can use all of the features the React library offers, taking advantage of a broad range of tools to make your UI more appealing.

## **When Should You Use Next.js for Development?**

If you're still not sure about whether Next.js is right for your application, it might help to understand the situations in which the framework really shines. Some of the best opportunities to use Next.js include the following.

### **Building SaaS Products**

If you have a software-as-a-service (SaaS) product that you want to expand, time is of the essence. Both development time and page load times are essential factors when you're trying to make your SaaS product more appealing. Next.js helps you trim the time wasted on both of these issues.

You can use Next.js to build out new SaaS features and products in less time. The framework will help you move from idea to finished product without having to waste time on configuration and bundling. Meanwhile, the data -fetching options allow you to build an application that loads faster on every page.

### **Building Web Applications and Portals**

Similarly, Next.js is a great way to build new web applications and portals. Paired with the right [Next.js authentication solution](/nextjs-authentication), the static site generation feature helps you build fast-loading pages that allow your users to quickly sign into the app or portal. You can even implement [multifactor authentication](/blog/nextjs-supabase-todos-with-multifactor-authentication) in your portal to strengthen your security and protect your users' data.

### **Creating an Interactive User Experience**

Next.js is structured to make interactivity a priority for the user interface. Whether you use server-side or client-side rendering, you can produce highly interactive webpages that load in less time. Each page can be built with the rendering method that allows for the most efficient interaction.

Furthermore, the ability to customize your front end makes Next.js a valuable tool for developing a unique UI that's interactive in the ways you need. You're not stuck trying to build an application around the framework's assumptions. The result is a front-end UX that's ideal for any interactive application.

## **How You Can Incorporate Next.js Into Your Development Environment**

If you're interested in using Next.js in your development environment, Clerk can help. [Clerk offers the easiest solution for Next.js authentication](/nextjs-authentication) on the market. Learn more about how you can keep your Next.js application secure and accelerate your development with Clerk's Next.js authentication tool today.

---

# Clerk joins the Netlify Jamstack Innovation Fund
URL: https://clerk.com/blog/clerk-joins-netlify-jamstack-innovation-fund.md
Date: 2022-07-12
Category: Company
Description: Clerk powers authentication for over 5,000 Jamstack teams – now we're working with Netlify to further our commitment to the ecosystem

Clerk provides serverless, edge-compatible authentication to over 5,000 Jamstack teams. Today, we're thrilled to join the Netlify [Jamstack Innovation Fund](https://www.netlify.com/jamstack-fund) to further our commitment to the ecosystem.

Netlify has been an inspiration for Clerk since their co-founder, Matt Biilman, [spoke about Jamstack in 2016](https://vimeo.com/163522126). The idea felt like it would change the way we build software, *and indeed it has*.

Clerk leverages Jamstack to set a new standard for developer tools. Instead of only providing a backend SDK, we also offer a frontend SDK with React components and hooks to empower frontend developers.

For example, our [`<SignUp/>` component](/components/sign-up) embeds a complete, conversion-optimized sign-up flow in our customer's application:

![Clerk Joins Netlify Jamstack Innovation Fund guide illustration](./9507286557ea85867e815093a4f0108edfa4cdf8-720x486.svg)

And for complete customization, frontend developers can instead leverage our [`useSignUp()`](/docs/reference/clerk-react/usesignup) hook to build a UI of their own.

Both `<SignUp/>` and `useSignUp()` are implemented entirely on the frontend, while Clerk's API is responsible for the backend of user and session management. This enables teams to build fully-featured authentication in minutes instead of days or weeks.

We're proud to be recognized as one of the 10 most promising Jamstack startups, and excited for the future as we begin working closely with the Netlify team. Stay tuned!

---

# Authentication vs. Authorization: What's the Difference?
URL: https://clerk.com/blog/authentication-vs-authorization.md
Date: 2022-07-06
Category: Insights
Description: Understand the differences between authentication vs. authorization and the purpose they both serve.

Authentication and authorization are essential parts of security. Companies need both to protect their networks and systems from unauthorized access to business resources. Let’s look at the core elements of authentication vs. authorization and ways to leverage them to enhance your organization’s security posture. The evolution of network security makes access control more critical than ever. While people tend to use the terms *authentication* and *authorization* interchangeably, it’s important to understand how they contrast and how each helps protect company applications.

## **The Differences Between Authentication vs. Authorization Explained**

Below is an overview of the differences in how each process works.

### **The Authentication Process**

[Authentication](/features/web3) focuses on recognizing and proving that an individual is using the correct identity. For example, when someone logs into their workstation, they’re prompted for verification through credentials like a username or password.

An authentication solution running behind the scenes checks the information provided against a database of stored credentials. If the data checks out, the user gains access to the target resource.

Usernames and passwords have been the go-to for most businesses when asking workers to verify their identity. Security protocols typically require employees to keep their passwords secret to prevent unauthorized users from using their credentials in a way that could harm the company.

As hackers have evolved the methods they use to go after companies’ systems, passwords have become more vulnerable. Many IT departments try to keep passwords secure by requiring users to enact a certain degree of complexity when creating them. In addition, workers are prompted by the system to change their passwords frequently, usually every 30 days.

The onus is on IT personnel to manage stored complex passwords. The difficulty of remembering intricate passwords often leads users to reuse their passwords on multiple devices and in different systems. Even if a password meets the length requirements, the actual entry phrase may be extremely uncomplicated, making it easy for cybercriminals to guess the pattern and gain system access using stolen credentials.

### **The Authorization Process**

Authorization covers the permissions provided to users when it comes to system resources. For example, someone working in the accounting department may have permission to access accounting software that isn’t granted to someone who works in operations.

However, having permission to access that application doesn’t mean the user has free rein to go where they want. While they may have permission to log certain transactions, the worker may be blocked from functions that allow them to retrieve sensitive information like Social Security numbers and bank account information.

Additional privileges are typically granted based on a user’s role within a company. For example, an IT director would likely have system permissions that aren’t given to an application developer. At the same time, that IT director would likely not have access to the repository where software engineers store their code.

System administrators usually control who receives privileges to access various organizational resources and how far those permissions extend. Authorization is about making sure individuals receive the permissions they need and ensuring they don’t proceed beyond those set boundaries.

Let’s use a nightclub analogy to wrap up the contrast between authentication and authorization. Authentication is what gets you past the line and through the doors. However, that doesn’t mean you get to go right up to the VIP lounge. Instead, someone must give you specific permission to access that area.

## **Types of Authentication**

Let’s look at the different methods employed by businesses to validate a user's identity.

### **Multifactor Authentication**

Passwords don’t offer the level of security that’s necessary in today’s digital world. Cyberthieves use methods like keyloggers to capture password entries or look for clues to a person’s work credentials by poring over their social media profile. Some resort to using password-cracking tools, which try out various username-and-password combinations until they get a hit.

[Multifactor authentication](/features/multifactor-authentication) (MFA) adds an extra layer of security to identify users. That way, hackers need more than a user’s password to get past a company’s security. Users must identify themselves in multiple ways, reducing the risk of hackers using employee information to steal data or sabotage business systems.

### **Two-Factor Authentication**

Two-factor authentication (2FA) requires users to provide two authentication factors before receiving access to a system, application, or device. It provides more robust security protections than requiring only a password or code.

Most 2FA methods require users to provide a password and then a security token or one-time password (OTP) that is generated from a separate device, like a mobile phone. The extra layer of security makes it more difficult for attackers to use a password to access a user’s device or get into their online accounts.

### **Single-Factor Authentication**

Single-factor authentication (SFA) grants access to a system by asking a user for only one type of identification. Password-based SFA is the most common type used by organizations. The best way to implement SFA is by establishing robust password protocols that are enforced by system administrators.

The drawback to SFA is that many users have difficulty coming up with strong passwords that they can easily remember. Problems also result when IT departments don’t enforce standards that ensure users don’t try to get around the protocols by repeating passwords or using patterns that are easy for an experienced hacker to figure out.

### **Token Authentication**

Tokens are a form of 2FA that uses digitally encoded signatures to authenticate a user attempting to access a network or another IT resource. The token comes in the form of a unique OTP that's generated every time a user logs into a system. Users enter the information along with another authentication factor to prove their identity.

Many organizations use security tokens because it’s easy to scale the system to accommodate new employees. It’s also possible to use access tokens on multiple servers. The flexibility of the process allows companies to use security tokens for various applications and websites simultaneously.

**Biometric Authentication**

Biometric authentication uses distinct biological characteristics to verify someone’s identity. Using your fingerprint to log into your phone is a form of biometric authentication. The physical trait used to identify a person must match information that was previously stored in a biometric authentication system.

Other standard biological information used for this method of authentication include:

- Genetic material, like DNA
- Iris recognition
- Retinal scans that analyze the blood vessels at the back of the eyes
- Hand geometry recognition, which evaluates the unique characteristics of a person’s hand
- Voice identification

Biometric devices consist of a scanner, technology that converts and evaluates biometric data for comparison, and a database that stores biometric information. The device used for scanning can include a fingerprint reader or voice analyzer. Once the user provides their data, the biometric application attempts to match it to a previously stored sample.

## **How to Choose an Authentication Strategy**

Most modern companies have developers located everywhere, making it more complicated to implement effective security measures. Establishing an authentication strategy goes a long way toward striking a balance between continued flexibility for employees and protecting business assets.

### **Evaluate Your Environment**

Look at your company's available hardware to determine whether it’s sufficient to support multiple login and authentication requests. You don’t want to end up with a bunch of authentication failures that keep workers from performing their jobs. Review the network connectivity available and make sure remote users have what they need to establish connections.

### **Establish a Strong Password Policy**

If passwords are going to be part of your security posture, you need to establish strong policies governing their use. First, encrypt any passwords that are sent over the network to prevent them from getting intercepted. You want to make it difficult for attackers to crack user credentials. Ideally, the password should change by the time the attacker figures things out.

Passwords should be easily remembered by individual users but complex enough to ward off potential hackers. While characters like hashtags and “at” symbols can aid in that effort, adding too many of them can make it harder for users to remember. Users can add a unique suffix to a password, like [dandelion@hardtoguess.net](mailto:dandelion@hardtoguess.net), to make it more complicated. Other rules your organization should follow when it comes to password creation include:

- Not using any part of the user’s name
- Making the password at least eight characters in length
- Having the password contain at least one non alphabetic character
- Adding both upper- and lower-case characters
- Prompting users to change passwords at least every 45 days, though 30 is ideal

Make sure to limit how many times users can try logging in with an incorrect password before a lockout policy is implemented. Many hackers use tools that endlessly guess at password options. However, be aware that hackers can take advantage of such a policy to launch a denial-of-service (DoS) attack that locks out legitimate users. If employees forget their password, set up options that allow them to recover their account securely.

### **Consider Multifactor Authentication**

Adding MFA to your organization’s security posture provides extra layers of protection around employees' credentials and sensitive data. If you work in certain industries, like finance, MFA may be required to comply with established regulations.

The noninvasive nature of MFA doesn’t interfere with your IT infrastructure. Many MFA solutions on the market allow companies to enable single sign-on (SSO) for all company platforms, meaning users don’t have to keep up with complicated passwords for every application.

MFA offers additional security for remote workers, whom hackers often target. Requiring a second confirmation of identity alleviates concerns around cyberthieves using compromised credentials to log into company devices or systems.

## **Types of Authorization**

Organizations should establish access boundaries for users, software applications, and even specific hardware using company resources. The two main methods of granting authorization to users are role-based access control (RBAC) and attribute-based access control (ABAC).

### **RBAC**

RBAC helps organizations maintain control over authenticating users while authorizing them to access systems and applications. It focuses on providing rights to individuals based on their role, the environment in which they work, and specific resource attributes. RBAC controls broad access granted to users throughout an organization.

Administrators manage the distribution of permissions to various organizational roles and the users assigned to those groups. Certain users may be assigned to a group that lets them edit sensitive information while individuals in a different category receive view-only permission.

Some users may be eligible for assignment to multiple role groups that expand their access within an organization. A project manager may have more permissions granted to them via several groups while an analyst working under them would receive more limited access.

The most significant benefit of using RBAC for authorization is that administrators don’t have to make major changes whenever someone switches jobs or leaves the company. Instead, the administrator removes that individual from a role group and moves them to a new one. RBAC also makes it easier to grant necessary permissions to new employees based on their roles.

### **ABAC**

ABAC, also called policy-based access control (PBAC), is often put in place to protect information held in databases, business applications, application programming interfaces, and microservices contained within complicated architectures. Users receive permission to access system resources based on attributes like their role, the device being used, the attempted action, or their location. Each ABAC attribute must align with an established policy before the user receives access to the desired resource.

Organizations typically use ABAC when there’s a need to set up more dynamic security parameters versus those available through RBAC. The granular detail makes it possible for businesses to meet unique security challenges.

Policies for ABAC fall under the governance of corporate policies. Any changes made are enforced throughout the entire company. It’s also easier to implement complicated regulatory requirements with ABAC. Administrators gain real-time control over users' attempts to access company assets, systems, and networks.

ABAC makes it possible to manage a larger pool of control factors versus RBAC. It reduces the risk of users managing to gain unauthorized access thanks to the level of control it offers. For example, someone who works in finance could be restricted from accessing certain bank information outside of specific time periods or particular locations.

## **Authorization and Its Relation to User Permissions**

Authentication is about establishing the identity of an entity trying to gain access to assets, networks, systems, or data controlled by an organization. That includes verifying the host ID of a remote machine, validating the certificate of a software component, or checking an employee's credentials through various means.

Once that entity gains access, the authorization process kicks in to determine what permissions are available. That includes looking at the role groups that entity belongs to and what access they have in different systems and applications.

## **How Authorization and Authentication Relate to Identity and Access Management**

Identity management involves making sure that users have the access they need for various IT resources. It ties directly into the process of authenticating users and access management, controlling where users are allowed to go and what actions they can take with granted permissions.

One benefit of identity and access management is that organizations can ensure that users don’t have more access than necessary. That helps thwart attackers hoping to use stolen credentials to access company data and networks.

## **How to Determine Which Method of Authentication Is for You**

Network security aims to ensure authorized individuals have required access while keeping out unauthorized users. Setting up ABAC properly can quickly devolve into complexity. If there are no strict regulatory guidelines for your industry, then RBAC may be sufficient for your organizational needs. There are also additional costs and human resources required to implement ABAC.

Regardless of which method you choose, try to apply the minimum number of filters that are necessary to comply with your company's security posture. Plan out directory data and the approaches your business wishes to take in granting access.

It’s also possible to use RBAC and ABAC in a hierarchical manner, through which RBAC covers broader protocols and ABAC kicks in when there’s a need for finite security management. An example of that would be using RBAC to figure out which groups within an organization are allowed to access a resource. The company could then use ABAC to figure out the permissions given to users and any actions they can take.

## **How You Can Utilize Authentication Concepts and Authorization Methods**

Authorization policies manage access to IT resources and control what users do with that permission. If you are going to make passwords part of your company’s access protocols, make sure you implement strong password policies to prevent employees from using combinations that could make it easy to compromise their credentials.

One way for companies to expand their security protections is by implementing MFA in addition to using IDs and passwords. Having a secondary method for user authentication keeps hackers from using stolen information to access company resources.

When deciding between RBAC and ABAC, go with the option that covers your essential security needs without adding unnecessary complexity. You can also choose to combine the two to ensure that you’ve established the most robust security protection possible for your organization.

Ready to start your authentication journey? [Start building your authentication journey for free with Clerk](https://dashboard.clerk.com/sign-up).

---

# Refactoring Stripe's API for frontend access
URL: https://clerk.com/blog/refactoring-stripes-api-for-frontend-access.md
Date: 2022-06-10
Category: Engineering
Description: We built `use-stripe-subscription` to make it easier for React developers to implement Stripe Billing

Today we launched [`use-stripe-subscription`](https://github.com/clerkinc/use-stripe-subscription), a package that makes it easier for frontend developers to implement Stripe billing. It features:

- `useSubscription()` - a React hook that returns:
  - `products` - an array of Product objects available for subscription
  - `redirectToCheckout()` - Redirects to Stripe Checkout to purchase a subscription to one of the `products`
  - `subscription` - the current customer’s active Subscription object, if it exists
  - `redirectToCustomerPortal()` - Redirects to Stripe Billing’s Customer Portal so the customer can manage their subscription
- `<Gate>` - a component that selectively renders children based on the customer’s subscription

`use-stripe-subscription` is open-source and was built to work with any authentication and user management solution, not just Clerk.

## Why did Clerk build this?

Clerk is a Customer Identity *Platform.* We don’t just handle authentication, we also make it easier to sync and leverage customer identity with developers’ favorite tools.

`use-stripe-subscription` leverages customer identity to add a new authorization layer to Stripe’s API. By default, Stripe’s API is only designed for backend access, since it relies on a secret key for authorization that cannot be exposed to the frontend.

We added a per-customer authorization layer, which allows frontend developers to securely retrieve subscription information about the signed-in customer, without exposing the secret key to the frontend.

This sounds fancier than it is: most teams using Stripe are effectively building this in-house. We just packaged the solution to save in-house teams from reinventing the wheel.

## Securing Stripe’s API for frontend access

To secure an API for frontend access, developers can refer to one, simple question:

> *What actions can a signed-in user perform on their own?*

Frontend APIs should be designed to power self-serve user interfaces. If the API is granted broader permissions, then a malicious actor may make unauthorized requests.

Luckily, Stripe’s API contains two objects that `use-stripe-subscription` leverages to determine which actions a user can perform on their own.

### The Customer object

In Stripe, every Subscription object belongs to a Customer object. The Customer object can represent an individual or a business.

As a long as an API can map the signed-in user to their Customer object, it’s trivial to restrict endpoints

- If a user *does not* have a Subscription, allow them to initialize a Checkout Session with their Customer ID to begin a new subscription
- If a user *does* have a Subscription, allow them to read the object, and to start a Customer Portal session to manage the subscription

### The Customer Portal Configuration object

Allowing users to start a Checkout Session from the frontend might sound unsafe – how can the API know which products a user is allowed to purchase on their own?

For this, we leverage Stripe’s Customer Portal product, which requires developers to specify which subscriptions a customer can switch between on their own:

![Refactoring Stripes Api For Frontend Access setup guide](./28f3676c57c3089778598df7cb781aa66675edb3-1764x948.png)

*Stripe’s Customer Portal settings page requires developers to configure which products a customer can switch between on their own*

Before `use-stripe-subscription` creates a Checkout Session, it verifies that the product is listed in the Customer Portal Configuration object. We assume that, if the configuration shows a customer is able to switch to a product, they should also be allowed to purchase that product new.

To make things easy for developers, the list of available subscription products is always accessible in the `products` attribute of the `useSubscription()` hook. This list is derived directly from the Customer Portal Configuration object.

## Passing in the Stripe Customer ID

To configure `use-stripe-subscription`, developers must create an endpoint on their server that communicates with Stripe’s API. The endpoint is responsible for retrieving the product list and current subscription information, as well as for generating Checkout and the Customer Portal sessions.

The package provides a complete Javascript implementation for this endpoint, except that developers must build their own function to determine the Stripe Customer ID associated with the request.

```javascript
import { subscriptionHandler } from 'use-stripe-subscription'

const handler = async (req, res) => {
  // Build your own findOrCreateCustomerId
  const customerId = await findOrCreateCustomerId(req)

  res.json(await subscriptionHandler({ customerId, query: req.query, body: req.body }))
}
```

This implementation is ideal because it works for both B2C and B2B subscription companies. The package doesn’t know (and therefore, is not opinionated about) whether the user is operating on behalf of a personal Customer object, or on behalf of a business’s Customer object.

## What about webhooks?

We know that frontend developers prefer to avoid webhooks, so `use-stripe-subscription` does not require them. Instead, it makes just-in-time API requests to Stripe to ensure it always has the latest data.

For very high-traffic websites, this strategy unfortunately has the potential to run into to Stripe’s API rate limit (100 read operations per second).

From our perspective, it’s quite unfortunate that Stripe asks developers to configure webhooks and setup a cache just to have access to updated data. It’s much simpler to query data directly from Stripe as it’s needed.

To alleviate this limitation, we investigating ways to add a robust caching solution to the package. Discussions and PRs toward this end are very much appreciated.

---

# Quickly Build a User Switcher, Just Like Gmail
URL: https://clerk.com/blog/build-a-user-switcher-just-like-gmail.md
Date: 2022-06-03
Category: Guides
Description: Build an app with complete authentication and a user switcher just like gmail has.

Developing an authentication system from scratch can be time-consuming, and the process can be prone to bugs. If you're looking for a customer identity platform that provides user management features like authentication, authorization, and management of user profiles, roles, and permissions, check out [Clerk](/). Clerk can save you time when it comes to building and testing your authentication flow. It also provides multi-session authentication, which allows users to seamlessly log in and switch between different accounts.

In this tutorial, you'll learn how to easily implement your own user profile switcher using Clerk and [Next.js](https://nextjs.org). You can follow along with this tutorial using [this GitHub repository](https://github.com/Anshuman71/clerk-user-switcher).

## What Is Clerk

Clerk is a one-stop solution for authentication and customer identity management. It helps you build a flawless user authentication flow that supports logging in with a password, multifactor authentication, or social logins with providers like [Google](/blog/nextjs-google-authentication), LinkedIn, Facebook, and GitHub. Clerk provides components like `<SignUp/>` and `<SignIn/>` that you can plug into your application right away to quickly build an authentication flow.

It's common for users to have multiple accounts for different purposes. For instance, you may have a personal YouTube channel for your friends and family, and another one specifically intended for your audience in your work as a developer. With Clerk’s multisession feature, you can seamlessly and intuitively switch between both accounts as needed.

## Building a User Switcher

Before you begin building a user switcher, you'll need a code editor like [Visual Studio Code](https://code.visualstudio.com/download). You'll also need [Node.js](https://nodejs.org/en) (version 14 or newer) and [npm](https://www.npmjs.com) installed.

[Create your Clerk account](https://dashboard.clerk.com/sign-up) by following the steps on their website. Once registered, navigate to the Clerk dashboard, where you'll begin this tutorial:

![Clerk dashboard home for starting the tutorial](./0bc55bf7d729c611963f855cae53cafe5d8acb63-3840x2160.png)

### Set Up the Project

To set up the project, run `npx create-next-app clerk-app` in your terminal. This will initialize a Next.js project. Then you need to run `npm install @clerk/nextjs` inside the project and open your [Clerk dashboard](https://dashboard.clerk.com) in a web browser. In the left navigation, click on **API Keys** and copy the **Frontend API key**, **Backend API keys** and **JWT verification key**:

![Clerk API keys page showing frontend and backend keys](./03c567841a526a3410b52ba746eab23708ac9990-3840x2160.png)

Save the keys copied above in a `.env.local` file inside your project:

```text {{ title: '.env.local' }}
NEXT_PUBLIC_CLERK_FRONTEND_API=<frontend-key>
CLERK_API_KEY=<backend-api-key>
CLERK_JWT_KEY=<jwt-verification-key>
```

### Set Up the Authentication Flow

The authentication flow of your application dictates how the users access different parts of your application. In this example, the user authentication flow works like this:

![User switcher flow diagram](./f2858db3eadd7bb4ff6eae2859b578e8a5ee217f-2264x1272.png)

To begin, implement the authentication flow in the `pages/_app.js`:

```jsx {{ title: 'pages/_app.js' }}
import { ClerkProvider, SignedIn, SignedOut } from '@clerk/nextjs'

import { useRouter } from 'next/router'

import Head from 'next/head'

import Link from 'next/link'

// pages that can be accessed without an active user session.

const publicPages = ['/', '/sign-in/[[...index]]', '/sign-up/[[...index]]']

const MyApp = ({ Component, pageProps }) => {
  const router = useRouter()

  return (
    <ClerkProvider {...pageProps}>
      <Head>
        <title>Clerk app</title>

        <link rel="icon" href="/favicon.ico" />

        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      </Head>

      {/*If the route is for the home, sign-in and sign-up pages: show them without checks.*/}

      {publicPages.includes(router.pathname) ? (
        <Component {...pageProps} />
      ) : (
        <>
          {/*Show all pages if the user is signed in*/}

          <SignedIn>
            <Component {...pageProps} />
          </SignedIn>

          {/*Ask to sign in if the user isn't signed in*/}

          <SignedOut>
            <main>
              <p>
                Please{' '}
                <Link href="/sign-in">
                  <a>sign in</a>
                </Link>{' '}
                to access this page.
              </p>
            </main>
          </SignedOut>
        </>
      )}
    </ClerkProvider>
  )
}

export default MyApp
```

### Create the Sign-Up and Sign-In Pages

Next.js uses file-system-based routing, which makes it easy to create new pages. To learn more about Next.js routing check out their [official documentation](https://nextjs.org/docs/routing/introduction).

To create the sign-up and sign-in pages, navigate to the `pages/` folder in your project and create two new folders: `sign-up/` and `sign-in/`. Then create a new `[[...index]].js` file inside both of these folders. These routes will catch all the paths, including `/sign-up` and `/sign-in`.  You can read more about dynamic routing in the [Next.js documentation](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes).

Use the prebuilt components for `<SignIn/>` and `<SignUp/>` to populate the pages:

```jsx {{ title: 'pages/sign-up/[[...index]].js' }}
import { SignUp } from '@clerk/nextjs'

const SignUpPage = () => <SignUp path="/sign-up" routing="path" signInUrl="/sign-in" />

export default SignUpPage
```

![Clerk SignUp component page screenshot](./baf4da4192af228f95122e38d5bb1c1cdef75f38-3840x2160.png)

```jsx {{ title: 'pages/sign-in/[[...index]].js' }}
import { SignIn } from '@clerk/nextjs'

const SignInPage = () => <SignIn path="/sign-in" routing="path" signUpUrl="/sign-up" />

export default SignInPage
```

![Clerk SignIn component page screenshot](./23a3986bedc299e0b5bd24170b1b10cb97c538e9-3840x2160.png)

### Create the Home Page

Now, the home page should display the greeting "Welcome to your new app." at the top of the page. If the user is signed in, it will show them their profile page. Otherwise, it will ask them to sign up or sign in:

![Home page with UserButton in navigation](./c911b489b95a6551e04b58ac4f8c1f59f6aec94b-1848x1080.png)

Update the `pages/index.js` file to use the `<SignedIn/>` component to conditionally render the child components if the user is signed in and use the `<SignedOut/>` component to render the child components if the user isn't signed in.

Inside the `<SignedIn/>` component, use the `<UserProfile/>` component provided by Clerk to show the user's profile details and allow them to edit their information.

You can also use the `<UserButton />` component in the top `<nav />` to allow users to manage their accounts and sign out of the application. The `<UserButton />` will render as a button with the user's avatar.

Inside the `<SignedOut/>`, render two `<Link />` components to send the user to the sign-in or sign-up page:

```jsx {{ title: 'pages/index.js' }}
import { SignedOut, SignedIn, UserProfile, UserButton } from '@clerk/nextjs'

import React from 'react'

import Link from 'next/link'

const Home = () => {
  return (
    <div
      style={{
        display: 'flex',

        justifyContent: 'center',

        alignItems: 'center',
      }}
    >
      <main>
        <nav
          style={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
          }}
        >
          <h1>Welcome to your new app.</h1>

          <UserButton />
        </nav>

        <div>
          <SignedIn>
            <UserProfile />
          </SignedIn>

          <SignedOut>
            <Link href="/sign-up">
              <a>Sign up </a>
            </Link>
            or
            <Link href="/sign-in">
              <a> Sign in </a>
            </Link>
            to get started.
          </SignedOut>
        </div>
      </main>
    </div>
  )
}

export default Home
```

If the user isn't signed in, the page will look like this:

![Signed-out state prompting sign-in](./c182a98f893cb71730e3ef707c27115f02b0e773-3840x2160.png)

Or if the user is signed in, it will look like this:

![Signed-in state with profile visible](./4c14e59c88acddeea0c8f6456b78f5816ddcac40-3840x2160.png)

When you click the user avatar at the top-right of your screen, a pop-up will appear containing buttons for **Manage account** and **Sign out**:

![User menu with Manage account and Sign out](./de1410f96d3edfc362d2a6cb29a1189dc80ff242-800x780.png)

### Implementing the User Switcher

Before moving on with this tutorial, you need to navigate to your [Clerk dashboard](https://dashboard.clerk.com) and enable the **Multi-session handling** feature inside the **Sessions** settings:

![Enable multi-session handling in Clerk dashboard](./c8b7ca26cacdedf785d37d16fa473ab7593f9b42-3840x2160.png)

After enabling multisession handling, go back to your application window and click on the user avatar again. Now you'll see that a new option, **Add account**, is available:

![Add account option shown in user menu](./103957875f33abe0dbf86e25a23739c7773a9468-914x1018.png)

Clicking on the **Add account** button will redirect users to the sign-in page, where they can sign in or sign up for a new account.

After signing in, Clerk will redirect the user back to the application with the new session, and the avatar pop-up menu will show all active sessions. The user can now switch between accounts by selecting them from the list:

![Multiple active sessions list in user switcher](./2b2198763b20ac5321eb634010ae6b3b907b7223-892x1310.png)

### Authenticating API Endpoints

To prevent malicious requests from coming through, it's essential to authenticate your API endpoints. With Clerk, you can access the user's authentication status in the Next.js API handlers. To do that, you must wrap your API handler function inside Clerk's `withAuth` higher-order function to access the `auth` property on the request object.

The following example uses the `request.auth` property to check if the `sessionId` is available and then sends the `userId` in the response. Otherwise, it returns a `401: Unauthorized` response code:

```jsx {{ title: 'pages/api/getUserId.js' }}
import { withAuth } from '@clerk/nextjs/api'

export default withAuth((request, response) => {
  const { sessionId, userId } = request.auth

  if (!sessionId) {
    return response.status(401).json({ id: null, message: 'No user signed in!' })
  }

  return response.status(200).json({ id: userId })
})
```

On the home page, add a button to request the API endpoint and show the user ID in an alert:

```jsx {{ title: 'pages/index.js' }}
import { SignedOut, SignedIn, UserProfile, SignOutButton, UserButton, useAuth } from '@clerk/nextjs'

import React from 'react'

import Link from 'next/link'

const Home = () => {
  function showUserId() {
    fetch('/api/getUserId')
      .then((res) => res.json())

      .then(({ id }) => {
        alert(`User id: ${id}`)
      })
  }

  return (
    <div
      style={{
        display: 'flex',

        justifyContent: 'center',

        alignItems: 'center',
      }}
    >
      <main>
        <nav
          style={{
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
          }}
        >
          <h1>Welcome to your new app.</h1>

          <UserButton />
        </nav>

        <div>
          <SignedIn>
            <button onClick={showUserId}>Show user ID</button>

            <UserProfile />

            <SignOutButton />
          </SignedIn>

          <SignedOut>
            <Link href="/sign-up">
              <a>Sign up </a>
            </Link>
            or
            <Link href="/sign-in">
              <a> Sign in </a>
            </Link>
            to get started.
          </SignedOut>
        </div>
      </main>
    </div>
  )
}

export default Home
```

After you've added this button, your user switcher is ready:

## Conclusion

After completing this tutorial, you will have successfully built a Next.js application that supports multisessions. While doing so, you learned about creating a new Next.js application, setting up a Clerk account, and integrating the Clerk SDK with your Next.js application.

You used the UI components provided by Clerk to create an authentication flow and user profile page. You also learned about the importance of multisession support in your application and how easy you can implement it with Clerk. Lastly, you created an API route in Next.js and secured it with Clerk.

Using the Clerk SDK, you can expand on the example above to add social logins and multifactor authentication to your application.

---

# The New Wave Stack
URL: https://clerk.com/blog/new-wave-stack.md
Date: 2022-06-02
Category: Insights
Description: A custom Remix stack with Clerk, Fauna, and Netlify.

### Introduction

In March 2022, Remix introduced a feature of the Remix CLI called [Remix Stacks](https://remix.run/docs/en/v1/pages/stacks). This allows you to quickly and easily generate a Remix project from a pre-created template. These custom stacks are named after music genres. At Clerk, we’re all about making the developer experience as easy as possible, so we knew this was a perfect opportunity to combine the best of Remix’s development tools with Clerk’s easy-to-use user management and authentication platform.

New wave music was defined by the transition from the punk music scene to a more radio-friendly form of pop expressionism. In many ways, Modern Web tools like Clerk, Netlify, and Fauna represent much of the same thing for building web applications. Clerk’s custom Remix stack takes the DIY (Punk) freedom of designing an app the way you want and combines it with production-ready (Pop) resources to make your product accessible to a broader audience.

Welcome to The New Wave Stack. Let’s get happy.

[Check out the official New Wave Stack repo on GitHub](https://github.com/charles-clerk-dev/new-wave-stack)

The critical parts of any [custom Remix stack](https://remix.run/docs/en/v1/pages/stacks) are:

- Automatic deployment - Netlify
- Authentication - Clerk
- Database - Fauna
- Testing - Cypress, RTL, Jest
- Linting/Formatting/TypeScript - ESLint, Prettier

### Deployment with Netlify

![New Wave Stack guide illustration](./23c422b3064e352482ca1ef726c956c37cd2c6f1-2512x1956.png)

While it may seem odd to start with deployment, it can often be much more challenging to configure later on in the development process. If you can get deployment set up early and test deployment often, it can save you an incredible amount of stress in the future.

This stack is all about making things easy for developers, so Netlify was an obvious choice for deployment. Netlify allows you to connect a git repository and set up deployments to happen when a branch is merged into production. Or, if you prefer to deploy manually, Netlify has an easy-to-use CLI. It also provides deployment previews, rollbacks to previous deployments, and many other great features that make getting your application in front of users incredibly simple.

### Authentication with Clerk

![New Wave Stack guide illustration](./797d984b2bba456fef1e9c742471727709885a7c-2512x1956.png)

The New Wave Stack comes with Clerk’s complete authentication and user management features. This means that developers don’t have to worry about building many of the things users have come to expect from web applications. Components like Sign In, Sign Up, and User Profiles are available from the ground up. Clerk’s wide variety of authentication options will allow you the flexibility to manage your users the way you want. Setting up an account on Clerk takes seconds, and configuring your New Wave app is as simple as adding your API keys.

### Database management with Fauna

> \[!WARNING]
> Fauna’s service [ended on May 30, 2025](https://web.archive.org/web/20250319171743/https://fauna.com/blog/the-future-of-fauna), but they’ve [open-sourced their codebase](https://faunadb.org).

![New Wave Stack guide illustration](./ec7408c71780a250c4cd2a01a20782c992e50d5f-2046x1796.png)

Fauna is an excellent cloud-based database solution that combines the flexibility of NoSQL systems with the relational querying capabilities of SQL databases into a transactional database with GraphQL support and other modern features, such as real-time streaming.

Although Fauna offers built-in identity, authentication, and password management functionality, it requires that you manage the user data yourself. Clerk provides features like social SSO, passwordless authentication, multi-session management, and more without the hassle of managing your user and identity service. Clerk’s JSON Web Token (JWT) Templates feature makes it easy to authenticate queries to your Fauna database.

We have [a comprehensive walkthrough](/tutorials/build-movie-emoji-quiz-with-remix-fauna-and-clerk#set-up-fauna-database) on building an application with Remix, Clerk, and Fauna. Working through this tutorial will give you a solid understanding of how to set up your first Fauna database and how to query the database from your Remix application. It’s also quite a lot of fun.

### Styling with Tailwind

Like everything else in this stack, our approach to styling is to provide the tool that allows you to build your application quickly and efficiently, which is why we configured The New Wave Stack to use Tailwind out of the box. Tailwind is possibly the most common solution for developers styling their Remix applications, and there are plenty of good reasons why. It allows for inline styling, which can speed up development time, and the generated CSS file for even large applications is usually less than 10kb.

### Testing with Cypress/Testing Library

Developer experience is always a primary focus for Clerk, so we chose to configure this stack with Cypress for end-to-end testing. Cypress is commonly used in Remix stacks as it provides an effortless and straightforward testing environment. We’re sticking with the classic Testing Library/Jest combo for lower-level tests.

### Formatting

Finally, we arrive at the last few touches that make your code sparkle. We’ve set up Prettier for formatting, ESLint to take care of linting, and TypeScript to manage typing.

### Conclusion

It was a lot of fun building out the New Wave Stack and discovering how each piece seemed to fit naturally with the others. One of the benefits of Modern Web tools is the intuitive way they can be made to work together. It’s getting faster and easier for talented developers to take an idea and immediately begin putting it together. We want people to spend as little time as possible on setting up their app so that they can focus their effort on what makes their project special and unique.

---

# Three best practices for building React REST SDKs
URL: https://clerk.com/blog/best-practices-for-building-react-rest-sdks.md
Date: 2022-05-20
Category: Engineering
Description: For optimal developer experience, React SDKs require completely different patterns than Node

## 1. No secret keys allowed

This may be obvious but it must be stated as #1. Since React hooks run in the browser, SDKs cannot use secret keys for API access.

Instead, the API should be designed to scope access based on the currently signed-in user. Then, a session token or a JWT can be used to authorize requests to the API.

## 2. GETs should be Hooks

This is the critical change that turns a Node SDK into a React SDK. In Node SDKs, GET methods usually return a Promise:

```jsx
import { getUser } from '@clerk/nextjs'

const user = await getUser()
```

Unfortunately, using a Promise in a React Component is a big headache. The developer needs to add `useEffect` and `useState` hooks to render a loading state while the Promise is pending, then update when the resolves.

The end result is quite verbose and hard to decipher:

```jsx
import { useEffect, useState } from 'react'
import { getUser } from '@clerk/nextjs'

const UserProfile = () => {
  const [user, setUser] = useState(null)
  useEffect(() => {
    const load = async () => {
      const res = await getUser()
      setUser(res)
    }
    load()
  }, [getUser, setData])
  if (!user) {
    return <div>Loading</div>
  }
  return <div>Name: {user.name}</div>
}
```

The solution is to provide developers with a hook instead of a Promise. Hooks are composable, so under the hood this is still built with `useEffect` and `useState`, it just doesn't burden the developer with setting them up manually:

```jsx
import { useUser } from '@clerk/nextjs'

const UserProfile = () => {
  const { user } = useUser()
  if (!user) {
    return <div>Loading</div>
  }
  return <div>Name: {user.name}</div>
}
```

That's much easier to use!

## 3. Hooks should "react" to mutations

There is still a problem in the example above. What if the user wants to change their name and the developer triggers an update:

```jsx
user.update({ name: 'Ben Bitdiddle' })
```

By default, the `useUser` hook in our `<UserProfile/>` component won't automatically refresh. But since it's a hook, developers will expect it to – that's the whole point of React!

There are two high-level strategies to refreshing the hook, **eager refresh** and **sequential refresh**.

### Eager refresh

Eager refresh is the most performant solution, but it can be hard to configure. The idea is that the API endpoint behind `user.update()` should return the updated User object in its response body. When the response is received, `user.update()` can propogate the new value to any components calling `useUser()`.

This example isn't particularly hard to configure because the mutation is contained to the `User` object. It can be achieved with a globally-scoped React context to store the value of the User object, instead of using `useState` directly in the `useUser` hook.

Eager refresh is much harder to configure when there are side effects involved. For example, at Clerk we have a `SignIn` object that is responsible for tracking the state of a sign-in flow while the user completes their first and second factor. Once the user successfully signs in, it has the side effect of generating a `Session` object, which changes the application's state from signed-out to signed-in.

We achieve eager refresh when a sign in completes by returning both the `SignIn` object and `Session` object from the server, but establishing a clean pattern for this has proven easier-said-than-done.

### Sequential refresh

The alternative to eager refresh is sequential refresh, which is less performant but can be easier to configure, especially when side effects are possible.

Using our example above, instead of trying to return both `SignIn` and the new `Session` at once, the final `SignIn` endpoint can instead return only the `SignIn` resource.

When the client sees that the `SignIn` is complete, though, it knows that a new `Session` must exist. So, it can trigger a second request to retrieve the new `Session`.

Two round-trips to the server will inherently be slower than one, but the development burden of eager refresh may make sequential refresh a viable alternative – especially as edge compute has made round-trips less costly over time.

---

# Next.js SSR authentication with Clerk
URL: https://clerk.com/blog/next-js-ssr-authentication-with-clerk.md
Date: 2022-05-07
Category: Engineering
Description: Next.js SSR authentication is easy with Clerk – just a few lines of code to get started.

## What is Server-Side Rendering (SSR) for React?

\*\*\
Update (11/2/2023):\*\* The code examples shown in this post have been deprecated. For the most up-to-date code examples on how to use Clerk with Next.js SSR, [read the documentation](/docs/references/nextjs/read-session-data#pages-router). Additionally we have a [new blog](/blog/nextjs-auth-clerk-streamlined-ssr-efficiency).

*Updated: 25/08/2022*

React was originally built to run on the client. Once React started, hooks would run to load data, and eventually a full page would be generated.

But since React is just a Javascript library, there was no reason it couldn't run on a server, too. **Server-side rendering** (SSR) runs React on the server to generate a page's initial HTML (a.k.a the first *render*), then runs React again on the client to provide reactivity.

Server-side rendering is particularly helpful for pages that must be indexed in search engines, since search engines cannot index pages that are rendered client-side.

The bigger benefit, though, is that server-side render can reduce the complexity of an application and lead to fewer lines of code. This is especially true in the modern era, where frameworks like Next.js provide helpers that drastically reduce the setup time for SSR.

### How to use SSR with Next.js?

Server-side rendering can be used by including `export async function getServerSideProps()` on any pages you need SSR. Below is an example of how a page would look when using SSR:

```jsx
function Page({ data }) {
  // use the data on our page
  ;<div>{data.content}</div>
}

export async function getServerSideProps() {
  // Fetch data from an API
  const res = await fetch(`https://api.example.com/data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}

export default Page
```

This example retrieves some data from api.example.com. Then, we pass the data to the UI using the return statement. Finally, in the UI, we present the data to the user.

*You can read more about Server-side rendering in the [Next.js SSR documentation](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props).*

Now that you have a basic understanding of how SSR works, let us investigate how Clerk and SSR can work together.

## How to use Clerk with SSR?

Clerk allows you to verify a user is authenticated and to retrieve user profile data when using SSR. Below, we will cover how to implement both.

### Securing pages with SSR

With Clerk and Next.js SSR, we can check if the user is authenticated and, if not, redirect them to the sign-in page without rendering anything on the client. Clerk provides a helper called `withServerSideAuth`, which allows you to access authentication information:

```jsx
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(({ req, resolvedUrl }) => {
  const { sessionId } = req.auth

  if (!sessionId) {
    return { redirect: { destination: '/sign-in?redirect_url=' + resolvedUrl } }
  }

  return { props: {} }
})
```

The snippet above checks to see if there is a session. If not, it will redirect the user to the sign-in page. Once the user has successfully authenticated, Clerk will redirect the user back to the page they were initially trying to access. Here is a quick GIF showing the process in action.

![Next Js Ssr Authentication With Clerk guide illustration](./5db59e3e9f8e6c84c2ff95e84b0c21439ceeea8c-2560x1440.png)

*If you want to see it in action, check out this [fully functional example](https://replit.com/@perkinsjr/Clerk-SSR-Auth-Example) that contains all the code you need for SSR authentication with a redirect back.*

### Retrieving userId or JWT templates

Server-side rendering gives you the opportunity to retrieve data from 3rd party APIs and your own database. When using Clerk, you have the option of retrieving the user ID, or if you are using a Clerk integration such as Hasura you can also retrieve the JWT ready for use.

Below is an example of retrieving a Hasura JWT token ready to retrieve data before sending the page to the user.

```jsx
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(async ({ req }) => {
  const { sessionId, getToken } = req.auth

  if (!sessionId) {
    return { redirect: { destination: '/sign-in?redirect_url=' + resolvedUrl } }
  }
  // use a token for your Clerk integrations
  const hasuraToken = await getToken({ template: 'hasura' })

  // retrieve data from your Hasura integration

  return { props: {} }
})
```

If you aren’t using a JWT template or a Clerk integration you can just retrieve the userId and use that against your own database.

```jsx
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(async ({ req }) => {
  const { sessionId, userId } = req.auth

  if (!sessionId) {
    return { redirect: { destination: '/sign-in?redirect_url=' + resolvedUrl } }
  }

  // use the userId to retrieve your own data

  return { props: {} }
})
```

## Enabling Full SSR Mode

In some cases, you may need the full User object available to you. For example, if you need to retrieve the user’s primary email address

Clerk can use an additional network request to retrieve the full User object. To enable it, you will need to add `{ loadUser: true }` to your SSR request. Then, the complete User object will be available:

```jsx
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(
  async ({ req, resolvedUrl }) => {
    const { sessionId, getToken, userId } = req.auth
    // retrieve the user object
    const { user } = req
    // return the users primary email address.
    const email = user.emailAddresses.find((email) => {
      return email.id === user.primaryEmailAddressId
    })

    // retrieve data using the email address.
    const data = getDataFromEmail(email)

    return { props: { data } }
  },
  { loadUser: true },
)
```

## **Ready to add auth to your app?**

Be sure to visit our [Next.js authentication with Clerk page](/nextjs-authentication) today to better understand how you can add authentication in minutes - not weeks.

Want to connect with other developers? Join our [Discord community](https://clerk.com/discord) or follow [@clerk on X](https://x.com/clerk) to stay up to date with the latest features, improvements, and sneak peeks to Clerk.

---

# How to skip CORS preflights and speed up your API with polyfills
URL: https://clerk.com/blog/skip-cors-options-preflight.md
Date: 2022-04-29
Category: Engineering
Description: CORS preflights add unnecessary latency to requests. Learn to use "simple" requests to skip the preflight entirely.

At Clerk, we have an API that is directly accessible from the frontend (we call it the Frontend API). It exclusively handles cross-origin requests, but none of those requests trigger a CORS preflight.

This is by design. CORS preflights do not add security for modern applications and they add an extra network round-trip, so we made sure that every API request is considered a "simple request."

## What do you mean CORS preflights do not add security?

It's a common misconception that CORS preflight requests add security to modern applications. Why else would they exist?

Surprisingly, CORS preflights exist to protect old applications, not new ones.

Specifically, the CORS designers were concerned about old applications that incorrectly assumed that browsers would never allow request methods besides GET or POST, or would never allow custom HTTP headers.

When browsers added the capability to send alternative request methods and custom headers via `fetch` (and its older sibling, `XMLHttpRequest`), suddenly applications that made this assumption were at risk.

To mitigate the risk to old applications, an extra "preflight" request was added to requests with PATCH, PUT, DELETE methods, and to requests with custom headers. The idea is that, if those applications fail to respond to the preflight in a very specific way, then the actual request will never be dispatched.

It's dirty and it adds latency, but it works.

The annoying part is: modern applications that anticipate PATCH, PUT, DELETE requests and custom headers don't gain any security from CORS preflights, it's just extra latency they need to incur to protect legacy applications. In 2022, it's like robbing Peter to pay an exceptionally stubborn Paul who won't update their decades old codebase, but we digress...

## How can CORS preflights be skipped?

Certain cross-origin requests are classified as "simple requests" and do not require a successful preflight before being dispatched.

Based on the section above, it might be easy to guess which requests qualify as simple: GET or POST requests without custom headers. *(Note: This is a slight simplification, the full details are [available on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests).)*

To build an API that doesn't trigger preflights, we need to design polyfills for modern request methods and custom headers.

### One critical point first

The polyfills below assume you have configured your CORS middleware to outright reject requests that should not be processed.

As an example, consider CORS middleware running on `api.example.com` that is configured to allow the Origin of `https://www.example.com`.

Now, consider a request comes in with the Origin of `https://randomattacker.com`

Does your CORS middleware reject this request, or does it allow the request to be processed?

**It depends on your middleware.**

Some middleware might simply add an access-control header (below), then allow the request to continue:

```text
Access-Control-Allow-Origin: https://www.example.com
```

This header doesn't stop the request from being processed, but it does stop the browser from reading your server's response.

**This is not the behavior you want.**

Instead, you want your middleware compare the received Origin to the allowed Origin, and immediately cancel the request if they don't match.

**The only way to confirm your middleware's behavior is to write your own tests.** Clerk needed to write our own middleware to reject requests with undesirable CORS options (origin, credentials, etc).

### Polyfilling request methods

Polyfilling the request method is trivial - and we were fortunate to have inspiration [from Ruby on Rails](https://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-patch-put-or-delete-methods-work-questionmark). Every mutation request to our frontend API is dispatched as a POST, but the method can be overridden using a query string like `?_method=PATCH`. In our backend, we run middleware to ensure that the request is treated as a PATCH when this query string is present.

### Polyfilling custom headers

Custom headers can be more challenging to polyfill. It really depends on what type of content you're putting in the header:

- If the header *does not* contain sensitive information, the polyfill can use the same query string approach used above for request methods
- If the header *does* contain sensitive information, the polyfill should be handled within the the request body

Typically, developers want to customize two headers:

1. **Content-Type:** To set it to **application/json**. This is not sensitive and can be polyfilled using the query string.
2. **Authorization:** To include an access token. This is sensitive and should not be polyfilled using the query string.

It's important that sensitive information is not added to the query string because the request path is often logged to tools like bug trackers and analytics software. To obscure this information from those tools, it's better to add the field to the request body. In the case of the Authorization header, an extra form value or JSON attribute will suffice.

## That's everything!

These simple changes will eliminate CORS preflight requests from a frontend talking to a frontend API. In the process, it eliminates a round trip, which can easily take over 100ms if your user is geographically far from your server. Even in the best case of edge computing, this strategy will likely shave off \~20ms from your overall response time.

For the modern web, every millisecond counts!

---

# The future of authentication is both stateful and stateless
URL: https://clerk.com/blog/future-of-auth-stateless-and-stateful.md
Date: 2022-04-28
Category: Insights
Description: Stateful authentication is more secure. Stateless authentication is faster. A hybrid approach delivers the best of both worlds.

Should session authentication be stateless or stateful? This simple question has been the source of many spirited debates, especially on Hacker News where it's [spurred](https://news.ycombinator.com/item?id=21783303) [many](https://news.ycombinator.com/item?id=18353874) [lively](https://news.ycombinator.com/item?id=16517412) [threads](https://news.ycombinator.com/item?id=18804875) over the years.

The discussion always boils down to two primary points:

1. Stateful authentication is more secure
2. Stateless authentication is faster

In this post, we'll first discuss the reasons behind both, and explain how a hybrid approach offers the best of both worlds.

## What is stateless? What is stateful?

This post will be hard to follow if you don't know the difference between stateful and stateless authentication, so let's start from there.

### Stateful authentication

Stateful authentication relies on a database (or another mechanism for storing "state") to determine if a session is still active.

When a session begins, a unique identifier is created to be passed along with future web requests.

When requests are received, that unique identifier is used to query the database and check if the session is still active.

### Stateless authentication

Stateless authentication relies on cryptography to determine if a session is still active.

When a session begins, a cryptographically signed token is generated (usually in the form of a JWT) that encodes the user's ID and the timestamp when the session expires. This token is then passed along with future web requests.

When requests are received, the backend confirms that the token's signature is valid and that it has not expired. This is considered stateless because no database is involved, just cryptography to verify the signature.

## Stateful authentication is more secure

The first issue to consider is security. Love it or hate it, security is the most important feature on any authentication system.

Stateful authentication is considered more secure because the database can be updated at any time to mark the session as inactive. When the next request is received, the backend will notice the change and refuse the request.

Since the database is checked on every request, instantaneous session revocation is possible. This is ideal if an attacker has access to a system and needs to be stopped immediately.

With stateless authentication, though, the backend only inspects the token. No database is involved, so the token will remain valid until the hardcoded expiration is passed. So, if an attacker gets hold of a token, their attack can continue until the expiration passes.

## Stateless authentication is faster

Since stateful authentication is more secure, you might expect it's the easy choice. It's not, because stateless authentication is *significantly* faster.

The cryptography required for stateless authentication can consistently be performed in under 1 millisecond.

On the other hand, the database query required for stateful authentication can take 10-20 milliseconds.

Authentication happens on every request, so the performance hit of a stateful approach quickly starts to look like a common bottleneck. As a result, developers start looking for faster alternatives, and naturally gravitate toward stateless authentication.

## Hybrid stateful and stateless authentication offers the best of both worlds

Fortunately, authentication is not a zero-sum game. Developers can build a hybrid approach of stateful *and* stateless authentication that is both fast and secure.

The magic comes from a simple sleight-of-hand: instead of setting stateless tokens to expire with the session, they can be set with a short expiration and refreshed periodically.

When a token is refreshed, the database is checked to ensure the session is still active. As a result, sessions can still be revoked before they end. The potential delay before revocation completes depends on how frequently tokens must be refreshed. If a 60 second expiration is assigned to each token, then revocation will take a maximum of 60 seconds.

Refreshing stateless tokens is a stateful process, since it requires querying the database to confirm the session is still active. But with the optimal approach, refreshes will happen asynchronously, except for the very first load after an application is closed.

### In practice

Clerk changed to a hybrid approach for session management 6 months ago. The functionality is built-in to our SDKs without any configuration, so most developers don't even recognize it's happening.

Our stateless authentication tokens are set to expire every 60 seconds. In practice, here's how it works:

1. When a user signs in, a stateless authentication token is created immediately.
2. An asynchronous poller is started on the frontend to refresh the stateless token every 50 seconds. The 10 second difference is to account for potential network delays and clock-skew between our token generator and the developer's backend.
3. While the user is active on the application, every request to the developer's backend will include an active, stateless authentication token.

But now, let's say the user closes the application for a few minutes so our poller stops. When the user revisits the application, the latest token has already expired since 60 seconds have elapsed.

In this case, we need to update the token synchronously before requests to the backend can be processed. It's the one exception to an otherwise completely stateless authentication experience.

In the end, Clerk's authentication solution is both stateful and stateless. Sessions can be revoked within 60 seconds, yet the vast majority of requests use stateless authentication and can be verified in under 1ms.

## Conclusion

To address speed and security concerns, we believe the future of authentication is both stateful and stateless. We've been using this solution for over 6 months and it has proven to be robust and resilient over many millions of refreshes and authentications.

Our [session management](/features/session-management) is included free in every Clerk plan - give it a try to test for yourself, or watch your network tab while signed in on [clerk.dev](/) to see it in action!

---

# It's the little things: Three developer experience delights of our Remix authentication package
URL: https://clerk.com/blog/remix-delightful-developer-experiences.md
Date: 2022-04-23
Category: Engineering
Description: We integrate with a lot of React frameworks - here's what we love most about our Remix authentication package

Clerk is building authentication for the **modern web**. That means we don't just offer SDKs for different programming languages (Javascript, Ruby, Go), we offer SDKs for each modern React framework, too. We're proud to have first-class support for Next.js, Redwood, Gatsby, Expo, and – of course – Remix.

For every framework, we challenge ourselves to make Clerk's installation and usage as straightforward as possible. In this post, we highlight two developer experiences of [our Remix authentication package](/solutions/remix-authentication) that we absolutely love.

## 1. getAuth(request)

When handling a server-side request, there's only two theoretical ways developers can retrieve authentication data from Clerk.

First, with a wrapper function like `withAuth()`:

```javascript
const handler = withAuth(async (request) => {
  // withAuth adds the `auth` property to request
  const { auth } = request
})
```

Second, with a direct accessor like `getAuth()`:

```javascript
const handler = async (request) => {
  const auth = await getAuth(request)
}
```

Call us crazy, but we like `getAuth(request)` better. It feels more natural to access the data "just-in-time" instead of configuring it as a wrapper.

Fortunately, Remix enables us to provide this developer experience. After some upfront [configuration in root.tsx](/docs/get-started/remix#initialize-clerk-in-app-root-tsx), developers can use [`getAuth()` in the loaders and actions](/docs/get-started/remix#loaders-and-actions) of any route. It's really easy!

## 2. Splats ($.tsx)

We like to say splats are money.

Name a route $.tsx and it will serve all sub-routes. Clerk relies on this wildcard functionality when developers [mount our components directly in their application](/docs/get-started/remix#using-clerk-components).

Specifically, when a developer mounts `<SignIn/>` on the **/sign-in** route, we actually mount several sub-paths:

- **/sign-in** - to start the process
- **/sign-in/factor-one** - to verify the first factor
- **/sign-in/factor-two** - to verify the second factor
- **/sign-in/verify** - to handle [magic links](/blog/magic-links) clicked in an email

These subpaths are nice because it helps us handle the refresh button. If a user is entering the second factor and they click the refresh button, we don't want them to lose their progress. Storing some state in the URL bar helps us provide the best possible user experience.

We love splats because their fun, intuitive, and easy. Next.js calls the same concept "Optional Catch-all Routes" and has the syntax **/\[\[...slug]].tsx**, which we find a little cumbersome in comparison.

## 3. Edge-by-default

We'd be remiss if we didn't include that Remix runs on the edge by default.

In this case, it's really the absence of code we're showing off. There is no "adding" edge support, it all just works.

This is great because Clerk is optimized for the edge. We include [stateless authentication with JWTs](/features/session-management) in every plan, which are perfect for edge-based applications where georgraphy-bound network requests should be avoided. (Don't worry, our JWTs expire every minute so sessions are still revocable!)

The edge is the future, and we're thankful Remix's defaults will help speed up adoption and the web.

---

# Unicorn or Chameleon? Two strategies for exporting customizable React components
URL: https://clerk.com/blog/exporting-customizable-react-components.md
Date: 2022-04-15
Category: Engineering
Description: React Components are the future of APIs – but how can developer tools companies enable robust customization? We explore two strategies.

Clerk's React library exports [`<SignUp/>`](/components/sign-up), [`<SignIn/>`](/components/sign-in), and [`<UserProfile/>`](/components/user-profile) components. They come styled and fully-featured so developers can focus on building their application:

![Exporting Customizable React Components guide illustration](./3faabd62184abfaa71b1a810d56f82596e3d93ac-2000x1016.png)

Unsurprisingly, this leads to the question: *How can I customize the components to match my brand?*

Whitelabeling software is a famously hard and unsolved problem - it's extremely common to find widgets or portions of websites that have completely different styling.

One example is this chat widget from Alaska Airlines, which shows different form field styling (rounded vs square), different buttons (huge text, not capitalized), and a different font (Arial vs Circular).

![Exporting Customizable React Components guide illustration](./63848098c405f0be95f8a961520e83c4be036f83-2880x1596.png)

This quarter at Clerk, we're revisiting our customization strategy. We want to truly solve this problem with perfect matching styles instead of just "close enough" styles. Internally, we say we're switching from a "unicorn" strategy to a "chameleon" strategy.

While we haven't finalized the chameleon strategy yet, we do have a proof of concept running and are excited about the developer experience it produces.

## Unicorn strategy

![Exporting Customizable React Components guide illustration](./3b751ff2967063abbbfe2320254217991a362204-656x350.png)

Our initial approach to theming is a "unicorn" strategy because we came up with the system ourselves – developers have to learn our specific way of applying styles.

We drew inspiration from others, so you've probably seen something like it before. Developers simply pass a `theme` prop to `<ClerkProvider>` to customize aspects of the design:

```json
{
  "general": {
    "color": "#f1f1f1",
    "backgroundColor": "#f2f2f2",
    "fontColor": "#f3f3f3",
    "fontFamily": "Inter, sans serif",
    "labelFontWeight": "500",
    "padding": "1em",
    "borderRadius": "20px",
    "boxShadow": "0 2px 8px rgba(0, 0, 0, 0.2)"
  },
  "buttons": {
    "fontColor": "#f4f4f4",
    "fontFamily": "Inter, sans serif",
    "fontWeight": "300"
  }
}
```

While this system is great for quickly getting close styles, it suffers in the last mile. There simply aren't enough options to provide developers with the complete customization capabilities they desire.

## Chameleon strategy

![Exporting Customizable React Components guide illustration](./bde3d2490d7628fb2febd01d27101894bfae501c-1000x680.png)

Our next iteration approaches customization with a new mindset. Instead of asking developers to learn our strategy, we will integrate with any strategy they already use.

If you've been around the frontend ecosystem for a while, you know there are *several, very popular* styling systems that all work *completely differently.* Because they're so diverse and we want to blend in with all of them, we call this a "chameleon" strategy.

Let's work through an example to explain how it works.

Consider that one of our components has a "primary button." By default, that button renders to this HTML:

```html
<button class="clerk-button-primary">Action</button>
```

*Note: in this post, we're only focused on styles. We'll discuss customizing the "Action" string in the future.*

To change the style of this button, developers will still pass a `theme` prop, but now the selector will be for this specific element:

```jsx
<SignIn
  theme={{
    primaryButton: customButton,
  }}
/>
```

In this snippet, `customButton` can have one of three values:

1. A string with one or many class names. If passed, the value will replace the default `clerk-button-primary` class.
2. A React component that renders a `<button>` and forwards all props (including children). If passed, the default element will not be rendered at all, and instead the passed component will be rendered.
3. A dictionary that adheres to the `CSSStyleInterface` type. This is for completeness more than anything else. If passed, the value will be forwarded to the `style` prop, and the the default `clerk-button-primary` class will be omitted.

Now, let's see how it works for different styling libraries.

### Tailwind

Simply pass in Tailwind classes as a string:

```jsx
<SignIn
  theme={{
    primaryButton: 'p-4 rounded',
  }}
/>
```

### CSS modules

When a CSS module is imported, the object automatically returns class names. Simply pass it in:

```jsx
import styles from './Styles.css'
;<SignIn
  theme={{
    primaryButton: styles.customButton,
  }}
/>
```

### styled-components

styled-components works by returning a React component that automatically forwards props to a `<button>` element – exactly as specified by Clerk:

```jsx {{ prettier: false }}
const CustomButton = styled.button`
  border-radius: 1rem;
  padding: 1rem;
`

<SignIn
  theme={{
    primaryButton: CustomButton
  }}
/>
```

### Chakra

Chakra also provides React components, but they are modified with props, which makes the setup slightly more complex, but still quite simple:

```jsx
<SignIn
  theme={{
    primaryButton: (props) => <Button size="lg" {...props} />,
  }}
/>
```

Since the chameleon strategy ultimately hooks into HTML and React primitives, we're confident that we can make *every* styling library work with this strategy, not just the four we've listed above.

Thoughts, comments, questions? We're eager for your feedback! Please reach out to [@clerk on X](https://x.com/clerk) or [contact support](https://clerk.com/contact/support).

---

# Just-in-time API requests are replacing webhooks
URL: https://clerk.com/blog/just-in-time-api-requests-are-replacing-webhooks.md
Date: 2022-04-08
Category: Insights
Description: Only 30% of Clerk customers use webhooks to sync user data into their own database. Instead, most skip syncing and use our APIs to retrieve data in real-time.

Clerk is building the next-generation of Authentication and User Management APIs. We offer webhooks so developers can sync user records in their own database, but the majority (70%) don't use them. Instead, they simply don't have a users table at all.

The ability to function without webhooks was a baseline technical requirement for Clerk. We knew our developer experience must be easier than our open source competitors, and we felt (and still feel!) this would be impossible if we required using webhooks. The overhead of spinning up a listener process in development is just too significant.

Ultimately, there is only one way a service like Clerk can eliminate the need for webhooks: **ask developers to request data in real-time instead of syncing data as it changes.**

This isn't a novel idea, but it's rarely considered viable because of two overwhelming developer concerns:

1. **Reliability** - If Clerk's API has downtime, many developers are more comfortable having a copy of user data in their own database. The idea is that they can better degrade the experience with stale data than having no data at all.
2. **Latency** - Loading time is a significant concern for everything from UX to Google Search rankings. If a page cannot be rendered until an external API request completes, overall loading time can be much slower.

Reliability concerns are the number one reason customers use our webhooks today. It's a legitimate concern and a prudent mitigation, but it does take significant engineering resources to consume the webhooks and design a failover to the stale-data experience. We expect that over time – as Clerk grows more stable and mature – the percent of customers who opt for webhooks will decline.

On the other hand, latency concerns are a problem we've been able to address head-on, with a little help from cutting-edge technology and the latest software development trends:

## React moved data-loading to frontend, so we moved our API to the frontend

A major reason real-time API requests raise latency concerns is because APIs are usually only requested from the backend. As a result, two hops are required before data can be accessed:

![Just In Time Api Requests Are Replacing Webhooks setup guide](./f7808741d74b8b8d1dd20e482f79274b2118fe7d-1471x895.png)

When the presentation layer was composed into HTML on the server – as it was in MVC frameworks like Rails – it made a lot of sense for APIs to only be exposed to the backend.

But now this paradigm has changed: **with React, data is loaded and composed directly from the frontend\*:**

![Just In Time Api Requests Are Replacing Webhooks setup guide](./f6f8e78fb270be7e98f6310805f8d56494f066a9-1471x723.png)

Eliminating this extra hop ensures that Clerk data can load just as quickly as the developer's own data, as if it lived directly in the developer's database.

*\*: To be more precise, data is loaded and composed directly from React, which may be running in the frontend or the backend with SSR (server-side rendering). If SSR is being used, accessing Clerk's API directly will reduce development overhead, but will not have significant impact on latency.*

## Edge computing reduced the latency of simple plumbing

Although we'd love to make Clerk's entire API accessible from the frontend, it simply is not possible to do so. In general, we can only return endpoints that the current user has permission to access. This is usually limited to:

- Their own profile information
- Information about the organizations they belong to

For other data – like information about other users – we rely on the developer to authorize the API request on their backend first.

In the past, this authorization step very clearly added a hop. But with the recent rise of distributed "edge" computing, the latency cost of this hop has been drastically reduced.

Since edge compute runs close to users, this authorization hop **can readily be executed in under 15ms**, barely impacting latency!

We already see a significant percentage of our customers leveraging the low latency cost of authorization on the edge, and it's growing as edge resources become more more accessible and widespread. In the past few years (and months!), we've seen significant strides from:

- [Next.js launching Edge Middleware](https://nextjs.org/docs/middleware)
- [Remix loaders and actions running natively on the edge](https://remix.run)
- [Netlify Edge making it easy to run arbitrary code on the edge](https://www.netlify.com/products/edge)
- [Cloudflare Workers making it easy to run arbitrary code on the edge](https://workers.cloudflare.com)

## JWTs enabled sub-millisecond session authentication

Although we've primarily discussed user management so far, a unique differentiator of Clerk is that we also manage sessions. We *love* managing sessions because it helps us build better developer experiences. In particular, knowing which user is currently signed in enables us to offer:

- `useUser()` to retrieve the current user's profile information
- `<UserProfile/>` to render a self-serve user profile for the current user

While managing sessions is helpful for our own product, it's essential that we're also able to inform our customer's backend which user is currently signed in - and quickly!

If developers made just-in-time requests to Clerk's API for session authentication, it would add significant latency to every one of their endpoints. This is unacceptable, so we needed to find another way.

Enter JWTs (JSON Web Tokens): cryptographically signed JSON objects that enable us to secure pass the current user's ID to the developer's backend. To verify the user ID, developers simply need to verify the JWT's signature with their public key (for convenience, we've abstracted this logic away in our SDKs).

Incredibly, JWTs allow developers to perform session authentication in **under 1ms**, so latency is not a concern.

But our favorite part about JWT authentication is that it's not just limited to the developer's backend: **Clerk can also generate custom JWTs to integrate with third-party services!** We offer prebuilt "JWT templates" for several database solutions, including [Supabase](/docs/guides/development/integrations/databases/supabase), [Hasura](/docs/guides/development/integrations/databases/hasura), and [more](/docs/guides/development/integrations/overview).

If a service is not listed among our prebuilt JWT templates, developers can build completely custom JWTs using our editor:

![Just In Time Api Requests Are Replacing Webhooks setup guide](./6cc62685b9e5eb1207b58fceabf881a7e68cdc4f-2036x864.png)

## In summary

While webhooks will always have their place, Clerk has demonstrated that developers are willing and eager to adopt low-latency webhook alternatives *today*.

The strategies outlined here are agnostic, and we believe it's inevitable that more APIs will become frontend accessible and leverage JWTs in the future. We're confident that webhook alternatives will lead to faster API integrations and happier developers, and we're thrilled to sharing our findings.

*Interested in problems like these? [Follow us on X](https://x.com/clerk) for more day-to-day insights, or [check out our openings](https://apply.workable.com/clerk-dev) to help us solve them!*

---

# Introducing Web3 Authentication
URL: https://clerk.com/blog/introducing-web3-authentication.md
Date: 2022-01-21
Category: Engineering
Description: Clerk is saving Web3 developers from the greatest evils of the Web2 platform: cookies, multifactor authentication, and profile enrichment

Clerk is launching our first Web3 authentication factor - Sign in with Metamask!

![Introducing Web3 Authentication guide illustration](./adca3d5c16282cbf8af17d8664bd4bb82d2ece4a-1040x452.png)

This launch is the result of dozens of developer interviews, focused on understanding if and how Clerk can contribute to the Web3 ecosystem.

It didn't take long before we discovered three common challenges that we can help Web3 developers with immediately: securing sessions, multifactor authentication, and profile enrichment.

## Securing Sessions

First and foremost - we found a near ubiquitous challenge around securing sessions. It's a two-part problem:

1. How do you verify a user is owns of a Web3 account?
2. How do you safely create a session to persist the users account information?

### Verifying the owner of a Web3 account

It's easy to *insecurely* determine the Web3 account address of a user visiting your website with Metamask connected, just run the following Javascript:

```javascript
await ethereum.request({ method: 'eth_accounts' })
```

Verifying that the returned address is accurate, however, is significantly more challenging. Developers must ask users to sign a transaction, which is a process that undergone several protocol revisions and is hard to keep up with.

This is where Clerk steps in - all the work of signing a transaction and verifying the signature is abstracted away, and wrapped in a `<SignInWithMetamask/>` button.

### Persisting a session

Let's be honest - persisting a session isn't a Web3 challenge at all, but it is one that Web3 developers must endure by virtue of operating in Web2 browsers.

We know Web3 developers don't want to think about things like httpOnly cookies, XSS attacks, and session revocation. Web2 developers don't either, and that's why built-in [session management](/features/session-management) is the foundation of every authentication product at Clerk. We use stateless JWTs that enable authentication in under 1 millisecond, and revocation in under 1 minute. We follow all the best practices so you can focus on building your application.

## Multifactor authentication

Sign in with Metamask uses the same abstraction as our other authentication factors like passwords or [magic links](/blog/magic-links).

Because of this, Clerk's [multifactor authentication](/features/multifactor-authentication) works with Metamask users out-of-the-box. Users simply need to navigate to their user profile and opt-in to multifactor authentication. There's no extra work for the developer beyond mounting a [`<UserProfile/>`](/docs/components/user/user-profile) component, or redirecting to the Clerk-hosted component.

## Profile enrichment

Off-chain profile enrichment is a topic of much debate in the Web3 community. Ideally, data like email addresses and phone numbers could be retrieved on-chain following the principles of [self sovereign identity](https://en.wikipedia.org/wiki/Self-sovereign_identity).

But as of today, there are no standard protocols for retrieving this data. As a result, top Web3 companies like OpenSea still collect and verify email addresses off-chain.

Clerk enables developers to easily do the same. We provide simple helpers for collecting and verifying phone numbers, email addresses, and even OAuth accounts. Beyond that, we accept structured data likes names and profile photos, as well as provide a generic metadata field for bespoke profile enrichment.

In the future, we look forward to enriching profiles in a more decentralized manner. Today, we hope this mechanism can help bridge the gap from Web2 to Web3.

## Try it out!

[Try out our guide for installing Web3 authentication.](/docs/users/web3-wallets) The guide will get you started with a new Next.js application with Metamask authentication.

## Future roadmap

This launch is the first of many upcoming Web3 launches. Our rough roadmap ahead includes:

- **Sign in with Ethereum** - we know that Metamask alone is a little limiting and will add generic Ethereum support soon
- **Authorization via Token Gating** - In the Web2 realm, our team is actively building authorization via Roles and Permissions. We see many corollaries to Web3 authorization via token gating are approaching the primitives in a way that will support both.
- **Multi-chain support** - Because we fully anticipate a multi-chain future

Need something we didn't mention? Please reach out through any of our [support channels](https://clerk.com/contact/support) - we're still defining this roadmap and very interested to hear how we can better meet your needs.

---

# How to compete and WIN in a software economy [Part 2]
URL: https://clerk.com/blog/how-to-complete-and-win-in-software-economy-2.md
Date: 2022-01-13
Category: Insights
Description: In this next part, we'll discuss how to identify 'core' vs. 'non-core' development & how thoughtful outsourcing can be used to develop process power.

[In the previous part of this article](/blog/how-to-complete-and-win-in-software-economy), the discussion centered around the importance of increasing product velocity, especially in today’s software-centric environment. The section concluded with the idea that you should ‘focus on your core and outsource non-core services’.

Although outsourcing non-core development \[via developer tools, i.e. infrastructure services] is clearly essential to increasing product velocity, understanding *how* to determine if something is non-core development is often less clear. Accordingly, in this next part, we'll discuss how to identify 'core' vs. 'non-core' development, and how thoughtful outsourcing can be used to develop [process power](https://www.nfx.com/post/seven-powers).

First, let’s look at how one determines which elements are core or non-core to a business. Instead of proposing my own methodology I’ll instead lean on someone with more perspective and experience, and return to Jeetu Patel (referenced in part 1).

The excerpt below is from his [techcrunch article](https://techcrunch.com/2016/06/07/software-is-eating-the-world-5-years-later), where he provides a framework for designating an element ‘non-core’, by asking three questions: Will the service provide your application an innovation tailwind? Can the service be substituted by another supplier? Will the service provide a neutral to incremental experience improvement for my customers?

> 1. Will the \[non-core] service provide your application an innovation tailwind? This could happen for several reasons, such as the sheer innovation velocity by the specialist supplier in a highly competitive market where your company benefits by shelling out only a fraction of the cost. If an innovation tailwind occurs, then use specialists who provide such a service. For example, by using the payments stack from [Braintree](https://www.crunchbase.com/organization/braintree-payment-solutions), Uber is able to benefit on all the payments advancements made by Braintree on their platform.

> 2. Can the service be substituted by another supplier? By definition, the ability to swap the service means it doesn’t warrant it to be your core competency. Uber switching between Google Maps and [Mapbox](https://www.crunchbase.com/organization/mapbox), for example.

> 3. Will the service provide a neutral to incremental experience improvement for my customers? Most importantly, even if the answer is that its impact is neutral (the experience the specialist provides is just as good as the one you would’ve been able to provide had you built the service yourself), it makes little sense to keep doing it yourself.

The first question - Will the service provide your application an innovation tailwind? - is often the only question product owners (e.g. developers, PMs, founders) ask, because you get the best of all worlds. Not only does consuming these types of services improve your product indirectly, by allowing you to offload a non-core element \[and focus on differentiators], but they also directly improve your product due to their own product velocity. This speed of innovation also makes the decision more obvious for product owners, as it’s clear to them that they are not going to build something better internally (at least not on any reasonable timeline).

The second question - Can the service be substituted by another supplier? - is considered less often, but is usually applied in practice consciously or unconsciously. Unconsciously because if there is a clear market for a certain service, it implies that there are already many buyers, and since people generally just follow the crowd, they believe they should just buy \[as opposed to a build] as well. Consciously, if there are multiple services that can be interchanged, then using a service here would create optionality. You can quickly get the product element you need up and working with a service, and if for any reason your business needs change you can easily replace it. Whereas, if something built in-house doesn’t work well, it can manifest in much greater loss aversion - a logical fallacy which results in an unnecessary waste of resources. Additionally, If you can swap services, then that market is already fairly well defined and is likely becoming commoditized, and rebuilding a well-defined commodity product doesn’t make good sense.

Ultimately, the driving force behind innovation and commoditization is competition, and in competitive markets, innovation is driven up and/or cost is driven down, and it’s relatively easy to understand how you benefit from this dynamic. Understanding these two questions and applying them to every product decision will get you far, but to fully benefit from outsourcing, applying the last question - Will the service provide a neutral to incremental experience improvement for my customers? - is critical.

This third question is the one that product owners rarely ask, but is key to developing a competitive advantage around product velocity. Essentially, the question prompts you to outsource any element that would not have a negative impact on user experience. This is the question that unlocks outsourcing, and drives the highest degree of focus to the few items that will truly differentiate your business. We’ll revisit this last question later.

For now, let’s touch on how to turn this question framework into a durable competitive advantage. Returning to Geoff Charles who we referenced in part 1, “Simply put, having the best product is not a competitive advantage. Everything can, and will, be bested. Having the best product development velocity and culture is what it’s all about.”

Importantly, the key here is to build a process around these three questions – staying abreast of developer tool innovations, regularly reviewing/reevaluating core vs non-core services, and then, fully leveraging dev tools where appropriate. Creating a process around this allows you to consistently maintain an advantage over time, and consistency over time is what generates true power.

Specifically, the ability to consistently maintain faster product velocity relative to competition, while also decreasing (or at least, without increasing) the cost of development is what [Hamilton Helmer](https://www.linkedin.com/in/hamilton-helmer-42983) calls process power - an embedded company organization and activity sets which enable lower costs and/or a superior product - which confers a massive advantage.

Helmer notes that the major barrier to developing process power is hysteresis - the phenomenon in which outputs significantly lag inputs. In this kind of system the inputs must be sustained, in order for the resulting output to become realized.

Returning to the framework we outlined earlier, this is another reason why question #2, and especially #3 are harder to grok. In the first case, where you adopt a highly innovative product there is usually an immediate and understandable short-term benefit (no lag in output). In the second case, much of the benefit comes from the creation of optionality, which is less obvious, but still fairly evident, since the benefits of optionality can come into play at any time (short, medium, or long term). In the third case, the benefits of increased focus and a reduction in technical debt and maintenance are longer-term and compound over many years. This hysteresis is what makes question #3 non-obvious, and since your peers are likely not thinking about outsourcing to this degree, it’s also what makes it a potential competitive advantage that can be sustained.

In summary, you should outsource an element if the service: is as good as (or better than) what you would have built yourself, can be substituted for another, or provides an innovation tailwind. And most importantly, by turning this 3-question framework into a process that is regularly applied to product decisions, over time you can develop process power.

---

# How to compete and WIN in a software economy [Part 1]
URL: https://clerk.com/blog/how-to-complete-and-win-in-software-economy.md
Date: 2022-01-11
Category: Insights
Description: Software has eaten the world and almost every business is a software business. How do you develop a competitive advantage and win in today's software economy?

> *As a disclaimer, the strategy outlined in this article is necessary, but not sufficient. There are still other things that have to go right in your business and other ways to develop competitive advantages, but this strategy will significantly move the needle for any businesses. It will also help you develop these additional advantages (more quickly).*

If it wasn’t already obvious, we are now in a software economy. [10 years ago, software *was* eating the world](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460); today, software has clearly eaten the world, and almost every business is a software business (at the very least, highly enabled by software) or has been bought by a software business.

It's no longer Netflix vs. Blockbuster or Uber vs. taxis. Now, it's Netflix vs. Disney+ vs. HBO Max vs. Hulu, and Uber vs. Lyft vs. Juno vs. micro-mobility (scooters, bikes, etc.) ... not to mention the 100+ other software startups vying for a piece of each of these markets.

And there is no sign of stopping. There are more startups, venture investors, and venture dollars than ever before. Sure, it may not be a perfectly smooth curve forever (there could be another [SaaSacre](https://kellblog.com/tag/saasacre), the web3 bubble could burst any moment, and the metaverse may only ever exist in [books](https://en.wikipedia.org/wiki/Snow_Crash)), but at this point, even a serious correction would be a metaphorical bump in the digital road.

Assuming we agree that we are in a software economy, then we can also agree that simply leveraging software is no longer a differentiator or competitive advantage.

In the previous paradigm of 10 years ago, simply building decent software was enough. The power of software relative to a manual or non-digital process was so massive that even mediocre software enabled huge leverage for an end customer and beat out other possible solutions. Processes enabled with C+ software (no pun intended) beats A+ non-digital processes almost every time. Today, the playing field is more even. Sure, there are still opportunities to digitally transform antiquated products and industries, but these are becoming more and more rare, and no longer representative of a typical competitive landscape.

So, how do you develop a competitive advantage and win in a *software* economy?

If we revert to Marc, the man who proclaimed software is eating the world in the first place, he would likely tell you: “[Cycle time compression may be the most underestimated force in determining winners and losers in tech](https://a16z.com/2014/06/03/pmarca-tweetstorm-cycle-time-determines-winners-and-losers-in-tech).”

And most of the venture community would agree. For example, here's a similar thought from Sunil Dhaliwal, GP at Amplify Partners, "[Product velocity is a reliable signal for early-stage companies that end up winning. Ship regularly. Ship quickly. Constantly improve.](https://twitter.com/dhaliwas/status/1445870724594372610)"

Importantly, operators are aligned with investors on this. Geoff Charles, Head of Product at Ramp, writes: “[In today’s startup environment, speed is everything. It’s not just about building things faster, it’s about decreasing the cycle time of learning and reducing the cost of being wrong](https://geoffcharles.medium.com/how-to-increase-product-velocity-8d0979a67c22).” Ok great, makes sense: increasing product velocity allows you to get more features out the door (or iterate on features faster) relative to your competitors. Additionally, moving faster than your competitors confers further benefits, such as being able to take more risks (i.e. be more innovative), since the cost of doing something new is lower. Oh and let’s not forget that [being fast is fun](https://jsomers.net/blog/speed-matters) \[for more thoughts on this, see [Speed Matters](https://jsomers.net/blog/speed-matters) by [James Somers](https://twitter.com/jsomers), which Charles references in his article].

Now the next question is, how do you increase product velocity?

Before examining how to increase product velocity specifically, let’s take a step back and think about how one increases productivity generally. A counter intuitive, but a common piece of advice is to ‘do more, by doing less’. [Google this phrase](https://www.google.com/search?q=do+more+by+doing+less\&rlz=1C5CHFA_enUS937US942\&oq=do+more+by+doing+le\&aqs=chrome.0.0i512j69i57j0i22i30l3j0i390l2.3000j0j9\&sourceid=chrome\&ie=UTF-8), and you’ll get a series of articles from HBS to Forbes explaining why this is. In essence, by taking on fewer things, you can dramatically increase your effort, and more importantly, your focus on each one. Thus, narrowing your focus and saying ‘no’ to things that are not at the top of the priority stack, allows you to accomplish more without actually increasing net effort or focus. Now, let’s apply this to software development. By narrowing the product focus to items that truly move the needle for one’s business and differentiate them in the market (i.e. ‘core development’), development productivity, and thus product velocity, is increased without increasing costs. As Steve Jobs famously said, “[Deciding what not to do is just as important as deciding what to do](https://www.quora.com/What-do-you-think-of-Steve-Jobss-quote-deciding-what-not-to-do-is-as-important-as-deciding-what-to-do).”

However, many of the items which you say ‘no’ to still need to get done. Filing your taxes doesn’t make you better at making money, but it still needs to be done. In these cases, we outsource this work \[to a CPA or software service like turbotax]. The same methodology should be followed in software development.

As [Jeetu Patel](https://techcrunch.com/author/jeetu-patel), Board Member at [HackerRank](https://www.linkedin.com/company/hackerrank) and Chief Product & Strategy Officer at Box, puts it, "[focus on your core, and outsource non-core services… Focusing on your core when it comes to technology makes it easier to focus on your end-customer experience — and that’s what makes a great software company.](https://techcrunch.com/2016/06/07/software-is-eating-the-world-5-years-later)”

As Jeetu elaborates further in the article, outsourcing in software is done via developer tools \[i.e. infrastructure as a service that provide the same or better functionality as what you could build in-house]. And that’s how you compete and win in a software economy.

*[In the next part of this article](/blog/how-to-complete-and-win-in-software-economy-2), we'll discuss how to identify 'core' vs. 'non-core' development & how thoughtful outsourcing can be used to develop [process power](https://www.nfx.com/post/seven-powers).*

---

# The Ultimate Guide To JSON Web Tokens (JWTs) and Token-Based Authentication
URL: https://clerk.com/blog/guide-JWT-authentication-JSON-Web-Tokens.md
Date: 2022-01-07
Category: Insights
Description: JSON Web Tokens, more commonly known as JWTs, are encoded and cryptographically signed data that allows for the secure transfer of information.

Security has become a primary concern in today's digital world. With almost every business process going digital, individuals and businesses must ensure fool-proof security and keep their data safe. Not just their information — they also need to protect their clients' data from any digital fraud. That's where JavaScript object notation (JSON) web tokens (JWTs) and JWT token authentication come in handy.

JWT is a compact and URL-safe way of transferring claims or information between two parties. The JWT authentication method makes sure that the information reaches the party it was intended for.

If you want to know all the ins and outs of this token-based authentication method, keep reading to find out how it works, what a JWT token is, its benefits, and much more.

## **Understanding User Authentication**

User authentication allows a device to authenticate the identity of someone trying to connect to a network. Typically, it requires the user to sign-up with a unique ID and password, which they can use to sign in later. This way, the computer recognizes the user and lets them in after the authentication process instead of asking "who are you?" every time they sign in.

Simply speaking, the user authentication process allows a user to access their account while blocking all the unauthenticated entries from the same account. The user's ID and password serve as their identity.

When your system's user authentication process isn't secure, it increases the chances of cybercriminals breaching into your system and reaching essential data.

### **Types of Authentication**

To access any network, the user needs to provide the information secured between the network's server and the user, such as a pin, code, or password. This information is referred to as an authentication factor. There are several ways a server ensures user authentication, including the following.

#### **Multi-Factor Authentication**

A multi-factor authentication method verifies users' identities via multiple authentication methods. For example, when users enter their ID and password, they won't access the resources directly. Instead, they must verify themselves via a one-time link or a security code sent to them through text messages or emails.

The multi-factor authentication strategy ensures multiple layers of protection over the user's account and private data. Even if a hacker determines any users' ID and password, they also have to go through additional authentication methods — or else, they will be denied access.

#### **API Authentication and API Tokens**

Application programming interface (API) authentication is the process of verifying the identity of users trying to get access to the resources on a server. The authentication process includes the use of API tokens.

API tokens are sort of a unique identity of a user or an application trying to access a service. When an application wants to access a service, it generates an API token for the application that it can use when requesting that particular service. Later, when the application makes an access request, the service matches the token provided by the app to the one it had stored to authenticate.

Simply put, an API token works just like a username and password combination. However, the API tokens provide a second layer of security, and the users have substantial control over each action and transaction.

#### **Token-Based Authentication**

Token-based authentication is a process that allows a user to verify their identity through a unique access token. As long as the token remains valid, the user can access the website or app from which they received the token. They don’t need to re-enter their credentials, meaning their username and password, each time they try to access the same website or app.

The signed tokens are just like stamped tickets.

## **What Are JSON Web Tokens (JWTs)?**

The compact method of JWTs is an open standard for the safe transmission of information — encoded as a JSON object — between two or more parties.

For example, if you want to sign in to Tinder with your Facebook profile, Tinder will contact Facebook's Authentication server and ask for your username and password. As soon as Facebook's server verifies your credentials — your username and password — a JWT will be created and sent to you. Tinder gets this JWT and allows you to access its services.

### **What To Know About Token-Based Authentication**

Token-based authentication is a process that ensures the security of applications by using signed tokens as a form of verification.

Tokens, like JWTs, eliminate the need for contacting any third-party service for the authentication process. Instead, the server performs the verification by looking at the signed tokens.

The JWTs carry a special message authentication code (MAC) that confirms the authenticity of their contained claims.

JWTs are either signed using a special HMAC algorithm or a private and public key pair. This key pair ensures the protection of the claim or information.

### **Token Expiration**

When a user receives an access token, it comes with details about a refresh token and an expiration time. For example:

```
{
  "access_token": "AYjcyMzY3ZDhiNmJkNTY",
  "refresh_token": "RjY2NjM5NzA2OWJjuE7c",
  "token_type": "bearer",
  "expires": 3600
  }
```

The refresh token means that the access token has a defined expiration period, and the user will get a new one.

The "expires" number is the number of seconds for which the access token will be valid. Usually, it's the service that specifies the token expiration period.

If you're using a JSON-based API and the token has expired already, you will likely get a JSON error response with the `invalid_token` error.

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"
  error_description="The access token expired"
Content-type: application/JSON

{
  "error": "invalid_token",
  "error"_description": "The access token expired"
}
```

When your code identifies this error, it initiates a request to the token endpoint by using the refresh token it received before and will obtain a new access token that it can use to make the original request again.

## **JSON Web Token Structure**

JWTs consist of three parts: header, payload, and signature. A dot (.) is used between each part to differentiate it from the other. The structure looks like this: Header.Payload.Signature. Let's break down each of them in detail.

### **Token Header**

The header is divided into two parts: the token's type (JWT) and the hashing or signed algorithm used (HMAC SHA256 or RSA).

The JSON is Base64Url encoded to form the header.

### **Token Payload**

The payload is the part where the actual claim or information is stored. Claims are divided into three types: registered, public, and private.

#### **Registered Claims**

These are a set of claims which are pre-defined and not mandatory, but they are recommended to provide useful claims, including:

- exp (expiration time)
- iss (issuer)
- sub (subject)
- aud (audience)

Since JWTs need to be compact, the size of a registered claim is only three characters long.

#### **Public Claims**

The users can define public claims and are open for public consumption. But these claims can cause collisions with other names. To avoid such name clashes, the JWT user must define public claims in the [IANA JSON Web Token Registry](https://www.iana.org/assignments/jwt/jwt.xhtml) before using them.

#### **Private Claims**

Private claims are customized claims shared between two or more parties that agree on using them mutually. These claims are neither registered nor public.

### **Token Signature**

The token signature ensures the authenticity of a message that it didn’t get changed before reaching its destination. The private key ensures the privacy of a token it is signed with. These keys can also verify the sender of the JWT.

A signature consists of:

- The encoded header
- The encoded payload
- A secret
- The header's algorithm (HMAC SHA256)

## **How Do JWTs Work?**

A JSON Web Token is returned in authentication when the user succeeds in signing in with their credentials. It's recommended that users not keep tokens for an extended period as it can pose severe security issues.

Whenever the user intends to access a protected server, they need to send the JWT, generally in the Authorization header, with the help of the Bearer schema. The Bearer schema or Bearer authentication is an HTTP authentication scheme that includes security tokens called bearer tokens. The name "Bearer authentication" actually refers to "give access to the bearer of this token."

The Authorization Bearer header looks like this:

```
Authorization: Bearer <token>
```

In some cases, this step can be a stateless authorization method. The server's routes will evaluate whether the JWT is valid in the Authorization header. If the route identifies a valid JWT, the user will access the protected resources.

CORS is a browser mechanism that allows access to resources outside a specific domain. To ensure security, many browsers prohibit cross-origin HTTP requests. When the token is sent in the Authorization header, cross-origin resource sharing (CORS) is no longer a problem since it doesn't use user credentials, such as cookies, on requests.

## **How Can JWTs Be Used?**

JWT should be used for API or user authentication and server-to-server or user authorization.

### **User Authentication**

User authentication is the process that involves the verification of the user. It ensures that the user is the same person they claim to be.

At this step, the user needs to validate their credentials through providing ID or passwords, answering security questions, or using facial recognition. User authentication is generally performed before user authorization.

### **User Authorization**

It is the process that involves the verification of the resources the user can or cannot access. This step comes after user authentication, where the user is granted access to certain resources based on policies and rules.

## **The Benefits of JWTs**

The use of tokens, especially JWTs, has many benefits.

### **Increased User Security**

JWTs ensure fool-proof user security. These tokens use a public and private key pair for a double layer of protection and provide users with an improved authentication experience.

Moreover, both signed and encrypted JWTs ensure the integrity of the claims. They also carry a special MAC that makes sure that the claim or information isn't changed before reaching the destination.

### **Compact and Self-Contained**

The JWT is a compact and self-contained token that has all the authentication information, expiration time, and other pre-defined claims signed digitally.

## **Security Risks and Security Issues With JWTs**

The JSON Web Token is a secure way of transmitting information, but this doesn't mean that it won't have any security risks. Some security risks and issues with JWTs are the following.

### **Problems in Verifying the Signature**

Most JWT libraries offer one method for the decoding and the other for the verification:

- **decode():** It doesn't verify the signature but decodes the token from base64url encoding.
- **verify():** It verifies the signature as well as decodes the token.

Often, developers mix up these two methods, and the signature doesn't get verified. This means the application can now accept any type of token in a valid format. In addition, some developers may also forget to enable signature verification. Such things can make the token vulnerable to security breaches.

### **Allowing the None Algorithm**

JWTs accept different types of algorithms, such as RSA or HMAC, to create a signature. However, it also allows the None algorithm, which refers to an un-signed token.

When JWTs permit this algorithm, the user can easily pass through the signature checking by changing an existing algorithm to None. When this happens, the hacker can smoothly replace the valid algorithm and remove the signature.

## **JSON Web Tokens vs. Simple Web Tokens (SWTs)**

When JSON is encoded, its size becomes way smaller than XML. This makes JWTs more compact and precise than security assertion markup language (SAML) tokens and SWTs. Apart from that, the compact size of JWTs also helps them pass in HTML and HTTP environments easily.

When it comes to security, SWTs only use a single key. JWTs use a public and private key pair, primarily an X.509 certificate, for improved authentication. In addition, signing JSON is way simpler than signing XML. JSON parsers are also used more in most programming languages as they have a document-to-object mapping, which others fail to offer. Usage-wise, JWTs can be easily used on multiple devices, such as mobiles and laptops.

All of this makes JSON web tokens easier to work with across various platforms.

## **JWT-Based Authentication Summarized**

All in all, JWT-based authentication is an excellent and secure way of transmitting information between multiple entities. The signatures on JWTs ensure the protection of the claims or information. All the user has to do is remember the correct key. Apart from that, JWTs are easier to understand and more compact than other web tokens like SAML or SWTs.

JWTs are also used at the internet scale, making them easier to process on a wide range of devices. However, it also poses some security vulnerabilities, like allowing the None algorithm and passing an unsigned and unverified token. If you want a better option, you can opt for a [session management software](/features/session-management) that manages your session's lifecycle and authentication requests on its own.

---

# 2021: The Year Authentication Saw a Resurgence (and Why)
URL: https://clerk.com/blog/2021-the-resurgence-of-authentication.md
Date: 2021-12-31
Category: Insights
Description: Authentication saw a resurgence as developers demanded better tools for Modern Web frameworks like Next.js, and users demanded easier sign-in options

Authentication is a historically boring problem. Developers used to just choose the popular open source library for their framework and run with it (like Devise for Rails).

But in 2021, authentication saw a resurgence as developers and investors alike recognized that this strategy is missing the mark. Past authentication solutions are failing to provide an efficient developer experience for Modern Web frameworks like Next.js, and they're struggling to keep up with the number of sign-in options that today's users demand.

## The Modern Web needs a new solution

Authentication for old **M**odel **V**iew **C**ontroller frameworks was relatively easy. In Rails, Devise provided a `current_user` variable to both views and controllers, and that was all developers needed. Conveniently, views and controllers ran in the same thread of the same Ruby process, so `current_user` in both contexts actually referred to the same object in memory.

Compare that to Next.js, the leading Modern Web framework, which already has **five** different contexts where authentication should work today, plus one more on the way:

1. API routes
2. Edge middleware
3. React during client-side rendering
4. React during server-side rendering
5. React during static generation
6. React server components *(Coming soon)*

Beyond the quantity of contexts, it's striking how little logic can be shared between them:

- API routes use a Node runtime while edge middleware uses V8 isolates, so memory is not shared between them and the same NPM dependencies may not work in both environments.
- Determining the current user while rendering React on the client-side requires a completely separate security model than determining the current user while rendering React on the server-side.
- Static generation happens at build time so there will never be an authenticated user, but utilities (like a `useUser()` React hook) still need to gracefully handle the edge case.

And that's *still* not everything. Unlike Rails which provides ActiveRecord for database access, Next.js developers often use new, standalone solutions like [Hasura](https://hasura.io) and [Supabase](https://supabase.com). These tools are unique because they allow frontend developers to query data directly, instead of relying on backend developers to query data for them. You might be wondering, how is data kept secure if it can be accessed directly from the frontend?

Queries to Hasura and Supabase are authenticated *per-query, by-user* instead of *per-connection, by-secret-key.* So, instead of backend developers authorizing queries separately (as they did with ActiveRecord), authorization checks are built-in to Hasura and Supabase with [Access Control](https://hasura.io/docs/latest/auth/authorization/quickstart) and [Row-Level Security](https://supabase.com/docs/guides/auth/row-level-security) respectively.

As a prerequisite for performing these authorization checks, Hasura and Supabase must know which user is currently signed in. And - you guessed it - that means the developer's authentication solution must provide a way to securely inform these tools of the current user. It's a seventh context, on top of the six that already exist just for Next.js.

Considering the proliferation of disparate contexts, it should be no surprise that developers using a Modern Web stack need a robust solution. Authentication is necessary for nearly every project and developers rightfully expect it to work everywhere out-of-the-box.

Clerk is very much focused on the Modern Web, and we already have native authentication solutions for [Next.js](/nextjs-authentication), [Hasura](/docs/integrations/databases/hasura), and [Supabase](/docs/integrations/databases/supabase), as well as other Modern Web frameworks like [Expo](/expo-authentication) and [Gatsby](/docs/quickstarts/gatsby). In 2022, we'll continue to invest more in this stack.

## Users want easier sign-in options, or conversion suffers

Open source libraries tend to come with a standard set of features:

1. Sign-up with email and password
2. Sign-in with email and password
3. A forgot password flow
4. A change password flow

This was a great default for years past, but today's users want more options. Even if you're not receiving user requests, the data overwhelmingly shows that extending on these basics will lead to improved conversion.

Across all applications built on Clerk, when a ["social sign-in"](/features/social-sso) option is available, like Sign in with Google, just over 50% of all users will choose that option.  And since social options are also more performant (meaning, sign-ups and sign-ins take less time), we always recommend that social sign-in is offered.

With Clerk, adding a social vendor only takes a few clicks, and we provide all the logic to ensure users always access the same account regardless of how they sign in (meaning, users can sign-up with email and password then sign-in with Google).  With traditional open source tools, adding a social vendor often requires adding an extra open source library, and then building the "account linking" logic in-house.

While our recommendation to add social sign-in is ubiquitous, a small portion of developers have also found that replacing the default email and password option can be helpful. This is very application-specific, but a few developers have found success with each of our alternatives:

- Email AND phone sign-ups instead of just email
- Only phone sign-ups
- "Passwordless" with email [magic links](/blog/magic-links)
- "Passwordless" with SMS one-time passcodes
- "Passwordless" with email one-time passcodes

Similar to adding social sign-in, experimenting between those options with open source tools can require a lot of code-side changes and developer hours. At Clerk, we've developed our [`<SignUp/>`](/components/sign-up) and [`<SignIn/>`](/components/sign-in) components to promote experimentation, and allow applications to switch between authentication options with no code-side changes.

While our list of authentication options is already quite lengthy, we have many more on the way. 2022 will bring Enterprise SAML, Apple's FaceID and TouchID, Web3 solutions like Sign in with Ethereum, and more social sign-in vendors.  We're excited to encourage more experimentation and to help applications provide the best authentication solution for their users.

## In summary

2021 cast a spotlight on the shortcomings of traditional authentication tools, particularly as developers change how they build applications, and as users change how they prefer to sign in to applications. As authentication challenges continue to grow in scope, we expect more and more development teams to outsource their solution instead of handling it in-house.

---

# Consider dropping your users table
URL: https://clerk.com/blog/offload_user_table.md
Date: 2021-12-01
Category: Insights
Description: The same way we would rather let Stripe handle your credit card, we'll let Clerk handle your phone numbers, emails and sessions.

In 2021, all of the computers are on a cloud and [developer experience matters at least as much as database internals](https://planetscale.com/blog/nonesql-all-the-devex). We can choose more and more of our tech stacks [omakase](https://dhh.dk/2012/rails-is-omakase.html), and use the right tools for the right jobs. At phonetoroam we combine ruby on rails and a postgres database with [user management as a service from Clerk](/). This gives us the power of ruby and rails to create a well-tested, flexible API to receive and process messages, while harnessing experts to deal with user management. Using a dedicated service for all of our user management has a few benefits:

- We don't have to store sensitive user data in the same severs as your messages now. The same way we would rather let Stripe handle your credit card, we'll let Clerk handle your phone numbers, emails and sessions. Clerk spends spends all day thinking about how to securely store user credentials, and will continue to keep them secure over time.
- Our user experience for adding and verifying phone numbers is now better. In the same way that Stripe is really good at making forms to handle credit cards, Clerk is really good at making user management forms.
- Our user management is domain wide now. We can now launch serverless [Jamstack](https://www.gatsbyjs.com/docs/glossary/jamstack) sites with full user authentication on any subdomain.

Thoughtful orchestration of cloud infrastructure allows us to center our code around the problems we're seeking to solve, and delete the rest.

We recently refactored our rails app to use Clerk for user management, and have a few tips to share:

- This type of refactor will get more complex over time — the complexity of a User object is likely super-linear because it cuts horizontally through (an increasing number of) other models — so get buy in as quickly as possible
- If you have a hodgepodge of user analytics code, clean it up before this refactor. You will likely move most of your signup related analytics to a webhook receiver. If you are using ruby/rails, one service object to wrap your analytics provider (mixpanel or segment work well) should do the job. If that service is well tested, it is relatively easy to move it around.
- The end goal of the refactor will be to delete your User model and drop your users table, so a good first step is to make your User model as small and condition-less as possible. This will make the surface area of your refactor smaller.
- The refactor will still be relatively large (our small rails app was about +1500/-2500 lines over \~50 files), so you need the code confidence and [engineering maturity](https://en.wikipedia.org/wiki/Capability_Maturity_Model) to make such a change. We recommend high unit test coverage, some level of integration tests, and/or a well thought-out, strongly-typed system.
- The migration itself will comprise of replacing most references to a user with a `clerk_id`, and wrapping some API calls to Clerk in a service object. Clerk provides a writable JSON object they call `private_metadata`, which can be used as a key/value store for state about a user.

*This article has been republished. You can [find the original post on the phonetoroam website](https://www.phonetoroam.com/blog/why-and-how-to-drop-your-users-table).*

---

# Trading Experts | Case Study
URL: https://clerk.com/blog/trading-experts.md
Date: 2021-11-20
Category: Insights
Description: A case study of how Trading Experts used Clerk to quickly implement the authentication and user management features they needed.

*“I was super impressed. I thought I was just going to build a very basic email/password flow using something open source — knowing that I'd have to deal with issues my customers face down the line. With Clerk, I was able to give my users passwordless auth, seamless UIs, and a complete user profile in much less time than it would have taken to go the open source route. The free MFA was just icing, and some of our users have opted-in to it."*

*- Ben Zamani, Founder of Trading Experts*

## About Trading Experts

Trading Experts is an online financial education and training platform, founded by Ben Zamani and Shake Pryzby. Combined, they have over a decade of experience working at investment banks and proprietary trading firms on Wall St. Through their website and mobile app, they offer training and consultations, host an online chat community, and provide real time alerts and tips to their premium members.

## Tech Stack

- Frontend: Next.js and Swift
- Backend: Golang / Postgres, hosted on Render

## Before Clerk

When Trading Experts first started, the user management functionality was extremely basic. There was no user profile, and user management was managed manually. Password-change requests were common, as was people struggling to access their account. Resolving these issues was time consuming, and very manual for the Trading Experts team.

*“There was a laundry list of improvements that were needed and new features to build, we knew authentication was an issue, but it seemed like such a pain for what we needed... I’d used and struggled with Auth0 before, and didn't want to deal with that again. I really wanted something that 'just worked', and was easy to setup for both our web and mobile app.” - Ben*

As Ben started his new NextJS web app, and iOS app, he didn't want to embark on the couple week project to setup all of the "boilerplate" user management code using either Auth0 or an open source solution. He also needed a way to unify authentication across both platforms, as well as a number of 3rd party services.

That's when he found Clerk, and thought it would fit his needs.

## After Clerk

Trading Experts was able to remove a lot of the old auth-related backend code, and even their 'Users' table, deciding to leverage Clerk's metadata fields for simplicity. They store Stripe IDs for billing, Device IDs and tokens for push notifications, and subscription information in the appropriate user metadata fields.

Trading Experts eliminated 100% of user account issues (a number we're quite proud of at Clerk!) - receiving no forgot password requests, and no account access complaints since the release date. Instead of manually editing their database and using some adhoc scripts, Trading Experts now uses Clerk’s out-of-the-box admin panel to easily change subscription information for their users as needed. These 2 features have dramatically reduced the amount of support needed to maintain their user base, and their apps.

*“I didn't think it could be as easy as it was. I'm looking forward to Clerk's integrations, so I can remove our payments and push notification code, and actually have a source of truth for our user data. They need to hurry up with that!” - Ben*

---

# Clerk is hiring a senior frontend team to expand our full-stack component library
URL: https://clerk.com/blog/hiring-frontend-full-stack-components.md
Date: 2021-11-19
Category: Company
Description: Seeking experts in CSS, browser APIs, JS bundling, React, react-native, Swift, Kotlin and Developer Experience

We're trying something new and combining the job description for several frontend roles into a single post. This is meant to highlight the diversity of expertise we're seeking, and to give applicants an idea of how the team will be structured.

**\*Public referral bonus:** Introduce us to a candidate who joins Clerk and we'll send you $5,000\*

## About us

Clerk is a developer tools company in the Authentication and User Management space. We provide developers with full-stack components like [`<SignUp/>`](/components/sign-up), [`<SignIn/>`](/components/sign-in), and [`<UserProfile/>`](/components/user-profile) to handle common but hard-to-get-right flows.

In 2022, we're expanding our scope to include Organization Management and everything that comes with it:

- Organization onboarding
- SAML SSO
- Roles and permissions
- Subscription management
- Authorization

With this new scope, Clerk will provide all the boilerplate necessary to start a B2B SaaS - except the application logic itself.

## Full-stack components?

![Hiring Frontend Full Stack Components guide illustration](./fea05669feec890cc6138436c5b3854371ebfa77-1959x664.svg)

While the last generation of developer tools was characterized by APIs targeted at backend developers, we believe the next generation will be full-stack components targeted at frontend developers.

A component is "full-stack" when it provides UI for end-users to interact with, and when it is powered by a third-party API exposed directly to the frontend. **Critically, frontend developers can implement full-stack components without needing backend developers to plumb data from a backend-only API.**

Besides our own `<SignUp/>`, `<SignIn/>`, and `<UserProfile/>` components, we look at Stripe's recently launched [Payment Element](https://stripe.com/docs/payments/payment-element) as a good example of a full-stack component.

## Who we're looking for

We're looking for developers who can help improve and grow our full-stack component library as Clerk expands into Organization Management. We're seeking expertise across many frontend disciplines:

### HTML & CSS experts

We have our fair share of debates deciding between `grid` and `flex`, but our challenges go much deeper. Customization of our full-stack components is unsolved, and it's our top customer request.

- How can we offer a customization experience that feels natural to developers using any of Tailwind, Bootstrap, CSS modules, or a global stylesheet?
- Should we deploy our components as [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components), iframes, or directly into our customer's DOM?

Help us build a design system whose key feature is blending in with others.

### Browser API experts

Full-stack components are filled with challenges where the solution depends on an intimate understanding of browser APIs.

- How can we allow multiple users to be signed in at once, with different users on different tabs, and *without* URL decoration?
- How can we provide parity between production (same-site to our API) and development (cross-site to our API) environments?
- How can we offer multi-page components if the developer's usage of the History API is abstracted behind React Router or Next.js? Or if they don't use the History API at all?

Puzzles like these are essential to delivering high-quality developer experience, and we need more experts to help us solve them.

### JS bundling experts

There is no Next.js for building full-stack components, so all the challenges of bundling, code-splitting, and lighthouse score impact fall on us. Help us improve our Webpack configuration or choose to rip out Webpack completely.

### React experts

Although our components can be used in any framework (or vanilla JS), they're developed with React behind the scenes. We need more React experts to help us build and optimize them, and we're especially interested in leveraging new tools like React Server Components.

### Mobile experts (react-native, Swift, Kotlin)

Help us port our Javascript core to native app developers. While a few developers have reverse engineered Clerk to support mobile, we’re excited to begin building first-class SDKs for mobile applications.

### Developer Experience experts

Every project at Clerk is focused on improving developer experience. Our key innovation is our frontend-facing API that powers our full-stack components. Instead of authenticating with a secret key that authorizes any action, it authenticates with the user’s session and only authorizes actions the user is permitted to take.

We've found this pattern to be incredibly empowering for frontend developers, who use Clerk to take control of User Management without ever being blocked by backend developers. As we expand into Organization Management, we need help defining the new APIs that will unlock the functionality for the frontend.

## Current stack

Clerks components are written in Typescript using React and CSS modules. They are packaged with tsdx and deployed on npm. The backend is written in Go and Postgres.

## Requirements

- 2+ years of experience in your frontend focus area
- A passion for developer tools and high quality developer experience
- Able to write clean, secure, and performant code with attention to detail
- Able to perform well in a fast-paced, remove environment

## Culture

The team at Clerk is small but our aspirations are big. To drive our mission forward, our culture emphasizes frequent deploys and high quality developer experience, both externally and internally. We recommend reading our [weekly changelog posts](/blog) to get an idea of our pace.

Clerk is a remote team, with timezone concentration in the US and the EU (especially Greece). We believe everyone should work their own hours, but also drive a lot of value from our overlapping hours each day.

## Benefits

- Competitive salary and equity
- Gear of your choice for your home office
- Health, dental, and vision insurance (US)
- Unlimited vacation policy - 25 days recommended per year plus national holidays in your country of residence. Take time when you need it.

## Apply

Even if you don't fit these descriptions exactly, we encourage you to apply if you find these challenges interesting. We look forward to hearing from you!

Please apply [through Workable](https://apply.workable.com/clerk-dev/j/C5BBBAB4AA) or email our cofounder directly at [colin@clerk.dev](mailto:colin@clerk.dev)

---

# The Ultimate Guide to BCrypt and Authentication Protocols
URL: https://clerk.com/blog/bcrypt-hashing-authentication-encryption.md
Date: 2021-11-02
Category: Insights
Description: Learn why bcrypt is the industry standard hashing algorithm for authentication - including its history and how it compares to other protocols. 

Digital security becomes more critical every day. As financial accounts and other sensitive data continues to move online, it’s vital for companies to keep their clients’ information safe. It’s not enough to simply provide password protected accounts. Those passwords must also be stored safely and effectively.

That’s where bcrypt becomes important. [Bcrypt](https://www.npmjs.com/package/bcrypt) is an algorithm designed to hash and salt passwords for safe storage. It’s an industry standard that’s time-tested and proven to resist threats from hackers and other malicious agents. Keep reading to learn the fundamentals of bcrypt, why it’s so effective, and how it compares to different password protection algorithms.

## **What is hashing?**

Hashing is a critical first step in the password storage process. Programs use hashing to condense data of a random size into a fixed size. Essentially, the program uses a mathematical formula to take an arbitrarily long text or data input and convert it into a hash. Hash algorithms apply a procedure to the input many times to obscure the original data.

The hash itself is a series of letters and numbers that will always be the same length. For example, a 10 character input and a 40 character input might be stored as 16 character hashes.

It’s important to note that this process can lead to multiple strings generating identical hashes. Since the process converts all input into strings of identical length, it’s inevitable that some will be the same. Usually, this is fine, but it can lead to vulnerabilities. For example, if too many inputs lead to identical strings, then it’s easier for hackers to brute-force their way into users’ accounts. There are more potential random inputs the hacker could guess that would lead to the hash matching.

Good hashing programs must meet a few qualifications:

- A specific input will *always* generate the same output
- Modifying an input will change the resulting output
- Different inputs should usually provide different outputs

Hashing can be applied to data of any kind, but it’s particularly useful for passwords. The hashing process is a one-way conversion. Once data has been hashed, it’s essentially impossible to unhash or reverse the process to come up with the original input. This property reduces the risk of leaks or attacks and makes hashed passwords safe to store.

Furthermore, since a specific input will always provide a specific output, a hashed password will always match the stored hash. The program will take the user’s input password, run it through the hash function, and compare it to the hash connected to their account. If they match, the user has entered the correct password and is allowed to access their account.

## **Hashing vs. encryption**

Hashing is different from encryption, another method of storing passwords and sensitive information. When information is encrypted, it can be unencrypted with the right program and encryption key. The encryption process is a two-way street, while hashing only goes one way.

Since encryption can be reversed, it can serve a wider variety of use-cases, but it’s less secure than hashing. A hashed password can’t be converted back into the plaintext input, but encrypted passwords can. If a hacker can steal the encryption key, then the plaintext input is theirs.

## **Limitations to hashing**

Of course, hashing alone isn’t a perfect solution. It has a few limitations that prevent it from solving all password storage problems up front. These weaknesses include:

### **Brute force attacks**

Brute force attacks are a significant problem for sites that rely entirely on hashing. These attacks are performed by trying many different passwords with a particular username until one of them finally works. It’s considered a “brute force” attack because there’s no actual attempt to figure out the user’s password. The hacker is just using a program to guess many different passwords very quickly until one matches the hash.

Without other security methods, brute force attacks will eventually work — it’s just a matter of time and computing power. And if a hacker uses a list of common passwords first, they will likely be able to log into a large number of the users’ accounts with little effort in a short amount of time.

### **Rainbow table attacks**

A rainbow table attack is more sophisticated. This method doesn’t bother with figuring out the actual password; it focuses on finding text that will produce the correct hash.

The rainbow table is a collection of common passwords and the hashes they generate under specific circumstances. If a hacker can access the stored hashes against which the authentication technology checks hashed password input, they can use the rainbow table to look up the resulting hash. If the hash is on the table, they can simply input the associated plaintext password and log in to the user’s account.

### **Collision and pre-image attacks**

“Collisions” are different inputs that both result in the same hash. If a hacker can find a fake password that generates the same hash as an actual password, it works just as effectively against a hashed database. Collision attacks focus on finding *any* strings that generate identical hashes and then look for hashes that match what they’ve generated. These attacks work best if the hacker has uncovered a large number of password hashes — they have more hashed passwords they can potentially match.

For example, in the [now-outdated hashing program MD5](https://www.mscs.dal.ca/~selinger/md5collision), it was discovered that anyone could easily generate multiple strings that would lead to identical hashes. This was a significant vulnerability that hackers used to violate secure computer programs and spoof security clearances and letters of recommendation.

Similarly, a pre-image attack is an attempt to match a *specific* hash. Instead of trying to find any match to any hash, the hacker tries to brute-force the discovery of a match of a particular password. Pre-image attacks work best against short passwords and hashes that can be performed quickly, so lengthening both passwords and the hashing time are essential to keep accounts safe.

### **Phishing and spoofing**

Finally, hashing can do nothing to protect users from phishing or spoofing attempts. These types of attacks don’t rely on any kind of technical attack. Instead, they are aimed at the user.

In a phishing attack, the hacker uses a fake website or email address to convince users that they’re interacting with a trustworthy source. This “spoofed” site or email will look exactly like the real version, with something small like a single letter changed. The hacker then convinces the user to “log in” to a fake site and instead just steals their account information. Hashing can’t prevent this because the attack happens entirely “offsite”.

## **The two halves of bcrypt**

The term “bcrypt” is a reference to two programs: crypt, the hashing function used by the [UNIX password system](https://docs.python.org/3/library/crypt.html), and Blowfish, a specific cipher that’s useful for password hashing. Here’s how each of these elements works in the context of bcrypt.

### **The root “crypt”**

The crypt algorithm is one of the original password protection solutions, reaching back to the earliest days of the digital revolution. In the 1970s, when crypt was first implemented, only about four passwords could be hashed per second. This essentially made brute force and pre-image attacks irrelevant.

However, by the late 1990s, a computer could use crypt to hash more than 200,000 passwords per second. In minutes, an attacker could successfully brute-force their way into any system using the same crypt algorithm.

Accordingly, the crypt function needed to be updated.

### **Blowfish: Where the “b” comes from**

What sets bcrypt apart from crypt is its use of the [Blowfish cipher](https://en.wikipedia.org/wiki/Blowfish_\(cipher\)#:~:text=bcrypt%20is%20a%20password%20hashing,threats%20from%20brute%20force%20attacks.). Blowfish is considered a “fast block cipher,” with one exception. The cipher uses a “key” to encrypt text. This key is not a password for the platform, but rather a filter that can be used to encrypt and decrypt files.

When Blowfish changes keys, it slows down dramatically because it needs to perform the pre-processing equivalent of encrypting four kilobytes worth of text. However, bcrypt uses Blowfish in an “off-label” way. Instead of saving keys to unencrypt any data, it hashes these keys to make breaking a hash more difficult. The keys can’t be used to unencrypt the hash because they’re actually part of the hash. So, instead of being used to “unlock” an encryption, these keys are used to “jam the lock,” thus making the hash harder to break.

Basically, bcrypt uses the Blowfish slowdown as a way to make other tasks longer — slower speeds are actually a good thing for password hashing. Niels Provos and David Mazières, the pair who created bcrypt, took advantage of the Blowfish slowdown by creating a key setup algorithm they called “eksblowfish.” This program, which stands for “expensive key schedule Blowfish,” is run to generate subkeys from the primary key, or the user’s password.

This is how the eksblowfish function performs something called “key stretching.” Many users will choose passwords that are short or common, meaning they’re easy to guess. The more times the hashing function is run, the longer it takes for the password to be checked against the original hash. Essentially, key stretching makes the calculation process take longer, so it “costs” more to crack the password.

The eksblowfish function also “salts” the password by adding random information to it. This extra information makes it stronger. Salts can be any length, but the longer they are, the more secure the password will be.

## **How bcrypt works**

So how does bcrypt actually work? That’s a good question. Like all hash programs, the fundamental method is simple: passwords are hashed, and the hashes are stored to compare against user inputs. It’s the hashing process where the difference is found.

The bcrypt hashing process heavily relies on the eksblowfish function. When the user inputs their password for the first time, the site uses “EksBlowfishSetup” to set two important parameters. First, it adds the “salt” to the password. A salt is a string of random characters used by the site to make passwords more complex.

Second, the EksBlowfishSetup will indicate the desired “cost” of the password. Remember, “eks” stands for “expensive key schedule.” The “expensive” part is the number of times the key schedule is run. Each iteration slows down the password process a little more.

Once the setup is complete, the eksblowfish function runs. The program then encrypts the value “ [OrpheanBeholderScryDoubt](https://news.ycombinator.com/item?id=24310173)” with the final state from the last run of the key schedule 64 times. The final string is a deeply secure hash that results from the combination of the cost, the salt, and the hashed input.

## **Why bcrypt is so secure**

Bcrypt is still a hash, so why is it more robust than other hashing functions? It’s because of the extra steps involved. The salt and key stretching function make bcrypt more secure against pre-image attacks. Since the salt is a random string, hackers can’t just run a rainbow table attack and hope for the best. The random element blocks these dictionary attacks from working.

Meanwhile, bcrypt is also resistant to brute force attacks — both now and in the future. The “cost” to hash a password is malleable and can be updated. So can the length of the salt. That means that as computers get faster, the cost can be set higher to keep the hashing process slow. This keeps hackers from simply guessing passwords by running the entire hash thousands or millions of times per second. Given that Moore’s Law still seems to be holding, this flexibility is critical.

## **How bcrypt compares to other hashing methodologies**

As well as bcrypt works, it’s not the only hash security function. Several other functions are regularly used to protect passwords against malicious users. Here’s how bcrypt compares to some of the most common alternatives.

### **MD5**

The widely used function [MD5](https://www.w3.org/TR/1998/REC-DSig-label-19980527) is a hashing function that was first developed in 1991. The function is considered cryptographically broken. An algorithm that’s regarded as broken is one that can be hacked without any preexisting information. In the case of MD5, it’s possible to generate collision hashes within minutes. Data security systems that rely on MD5 can be easily hacked by anyone with a basic understanding of the function.

On the other hand, bcrypt is not broken. As a result, it’s still able to keep passwords and information safe. Modern passwords should never be stored behind MD5.

### **SHA, SHA-256, and SHA-512**

There are three primary varieties of the SHA hash function. SHA, like MD5, is cryptographically broken. However, SHA-256 and SHA-512 are still considered secure. In particular, SHA-256 is one of the most common hashes for current website certificates.

Why is SHA-256 so popular? It’s because it’s a fast hash. It’s quick to run, so it’s able to quickly hash large amounts of data in a short timeframe. That’s precisely why it’s not ideal for passwords. The slower bcrypt is better for passwords because it’s more resistant to brute force attacks for the short amount of data in question.

### **PBKDF2**

PBKDF2 is considered secure. However, it’s another old solution to password hashing. While it does salt passwords, it’s also a lightweight program that can be run on a single core. When it was first developed, this made it secure but functional. Today, however, it’s a liability. It’s possible to parallelize PBKDF2 on multicore systems, dramatically cutting down the amount of time it takes to brute force passwords.

In contrast, bcrypt is not as easily parallelized. Consequently, it’s far more secure as an alternative.

### **Scrypt**

Scrypt is a follow-up program to bcrypt. It uses many of the same functions, but it’s significantly newer than bcrypt. In terms of security, this is not actually a good thing. Scrypt has received half as much scrutiny and testing as bcrypt has just because of the time it’s been in use. While there have not been significant vulnerabilities discovered yet, it simply isn’t as well-tested. Though scrypt claims to be more secure, organizations looking for a better-tested option will still see more reliable results from bcrypt.

### **Argon2**

[Argon2](https://www.argon2.com) is a new, award-winning password hashing function that’s become well-known in the cryptography community. However, for online and web-based passwords it has some weaknesses. If users are looking for short hashing times, Argon2 and its derivations are actually weaker than bcrypt. The way Argon2 functions relies on longer runtimes for security, while bcrypt just relies on iterations. Argon2 is also newer, so it hasn’t been tested as thoroughly. Essentially, bcrypt is considered more secure for any application designed to allow sign-ins in less than a second.

## **Final Thoughts**

The bcrypt function may be older than some, but it’s stood the test of time. Though there may be modern algorithms that might have some theoretical advantages, they have not been as extensively tested. Until they have, or until bcrypt shows meaningful vulnerabilities for a password hashing or security use-case, it’s likely to remain the industry standard.

[Clerk](/) uses bcrypt to keep passwords secure, and can help it implement highly-secure, well-tested user authentication that will keep your user information safe.

---

# History and Rise of "Passwordless"
URL: https://clerk.com/blog/passwordless-history-popularity.md
Date: 2021-09-29
Category: Insights
Description: Learn the history of passwordless, and how it became popularized. From OTPs to MFA to mobile.

## **A Brief History of Passwordless**

Passwords have been our main digital authentication method since 1961, though its [vulnerability to hacking](https://itd.sog.unc.edu/knowledge-base/article/hacking-hacked-password-security-digital-age) was demonstrated within one year.

Encrypted password storage and public-key cryptography were developed in the late 60s to early 70s.

But the 1980s brought the first version of “passwordless” authentication. This came in the form of dynamic, one-time passwords (OTP) held on physical fobs.

OTPs eventually developed into two protocols: time-based OTPs (TOTP) and cryptographed hash-based message authentication codes or HMAC OTPs (HOTP). Dynamic OTPs are still widely used as an authentication protocol.

The late 1990s brought single sign-on (SSO) into use. SSO helped organizations manage user authentication across an entire network of applications. However, fobs and other hardware tokens remained in use and popular throughout the 1990s and 2000s.

Smart cards are one hardware token that emerged in the early 2000s. These physical electronic authorization cards are sometimes used as passwordless [security tokens](https://en.wikipedia.org/wiki/Smart_card#Computer_security).

The 2000s also saw the combination of these various passwordless and password-based authentication methodologies with the rise of *multifactor* authentication. AT\&T actually holds the earliest recognized patent dating to 1998, but multi-factor auth (MFA) and single sign-on (SS0) really took off when organizations like Google began building them into their applications as a form of password-independent authentication.

The financial sector adjusted quickly. In 2005, the Federal Financial Institutions Examination Council (FFIEC) set out new user authentication guidelines. These included multi-factor authentication, biometrics, OTPs, and token-based authentication.

## **How Passwordless was popularized**

As MFA and thus passwordless authentication strategies became more popular, passwords and authentication itself became a popular topic again.

The first sign of media interest was at a [2004 IT security conference](https://news.microsoft.com/2004/02/24/gates-details-security-related-technology-investments-and-innovationsat-rsa-conference-2004), where Bill Gates publicly advocated for making passwords obsolete. Gates went over several of the security threats inherent to knowledge-based passwords. He then advocated for newer authentication technologies, including a tamper-resistant biometric ID card.

In late 2011, IBM [predicted](https://www.networkworld.com/article/2184221/ibm-predicts-five-big-technologies-of-the-future.html) that “multi-factor biometrics” would become the dominant authentication protocol, creating a completely passwordless world. Their influential thought leadership spawned many other predictions and thought pieces.

Google pushed things further in 2013, when [Eric Grosse, VP of security engineering, stated](https://www.computer.org/csdl/magazine/sp/2013/01/msp2013010015/13rRUx0xPgv) that "passwords and simple bearer tokens, such as cookies, are no longer sufficient to keep users safe." The company then made multi-factor authentication protocols standard within the organization, and that same year, Google’s information security manager, Heather Adkins, put it bluntly, “Passwords are done at Google.”

Then in 2014, after Russian hackers [accessed the login credentials](https://www.computerworld.com/article/2490980/russian-credential-theft-shows-why-the-password-is-dead.html) for over 1.2 billion internet users, Avivah Litan, VP Analyst at Gartner, reiterated the need to go passwordless. In her words, “Passwords were dead a few years ago. Now they are more than dead.”

Finally, the rise of mobile has boosted the popularity of passwordless authentication. In 2013, Apple introduced Touch ID (and Face ID has since followed) making passwordless biometric authentication ubiquitous today. Additionally, passwordless strategies (i.e. sending an SMS-based [magic link](/blog/magic-links)) allowed mobile-first businesses, like Uber and Lyft, to authenticate users and perform account verification in a single easy step.

## **Is “Passwordless” here to stay?**

There is no doubt that authentication technology and methodology will continue to evolve. The number of viable authentication methods will continue to grow, and inherently, most of these will be a passwordless one. Since two-factor and multi-factor authentication are widely popular (and in many use-cases a requirement), passwordless is definitely here to stay.

However, this doesn’t mean that password-based methods will be completely replaced or that passwordless is right for everyone. To learn more about specific passwordless technologies and if they’re the right choice for you, keep reading our series on passwordless authentication.

---

# Session management: What it is and why your security depends on it
URL: https://clerk.com/blog/what-is-session-management.md
Date: 2021-09-21
Category: Insights
Description: Learn about session management, its components, and security concerns.  

Constantly having to log back in to your online accounts is a frequent annoyance — but this irritating problem stems from an inefficient solution to a genuine security concern.

Web applications need to make sure that your accounts are safe from hackers, and some handle that by requiring frequent re-authentication. Still, that’s not the best solution. Proper session management can help apps like yours keep users safe *without* needing to constantly log back in.

Below, we’ll cover the fundamentals of session management, what’s required to implement it, and how it can help you keep your users safe without creating a frustrating user experience.

## What is session management?

[Session management](https://www.packetlabs.net/session-management) is the process of facilitating private interactions between users and web applications. It specifically refers to managing different “sessions,” or periods when the user is logged in and active in the application. The session management process lets users access their unique and potentially sensitive information securely without letting others get into their account, without forcing users to constantly re-authenticate.

Session management can take two forms: short-lived and long-lived. Short-lived sessions last only as long as the user remains in the application. Every time they leave the app, they need to re-authenticate to get back in.

Long-lived sessions keep the user logged in to the app even if they leave. These sessions store session IDs on the user’s device, allowing them to reopen the app and start using it without needing to re-authenticate.

Long-lived sessions typically offer the best user experience, since they let people get into their accounts with no hassle. But this approach also has drawbacks. Anyone who accesses the device can also access that account as long as the session is still active, which is a security risk. For apps that contain sensitive information, short-lived sessions may make the user experience slightly more complicated, but will be more secure.

## The elements of session management implementation

Proper session management implementation involves three functions: creating session IDs, storing session cookies or tokens, and enforcing session expiry dates.

Here’s what that means:

### Session IDs

When the user first logs into the website or app, the server creates a unique session ID associated with the authenticated user. However, with each new request, the server still needs a way of identifying if the request came from that authenticated user without needing re-authentication. Which is where cookies or [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) tokens come in.

### Session cookies vs. tokens

When the server creates a unique session ID, it also creates a cookie that is stored in the user’s browser. The information contained in that cookie is sent along with each new request so the server understands it comes from the same authenticated user.

Session cookies are most commonly used with websites or web-based platforms. When it comes to modern web applications, a JSON Web Token, or JWT, is used instead.

When the user logs on with the right credentials, a JWT is created instead of a session ID and sends it to the user. The JWT is stored in local storage and the header of the token is sent with every new request.

This means that the user’s state is not stored on the server, but inside the token, making this option more scalable and more useful for mobile device authentication.

### Session expiry

Sessions are temporary states and expire under certain circumstances, such as the mobile app being closed, a set period of inactivity, or a maximum session duration that cannot be exceeded. Long-lived sessions may expire when the user hasn’t interacted with the app in a certain number of days or weeks. The cookie or token storing the session ID should automatically delete itself at the end of those periods.

## Security concerns addressed by well implemented session management

The purpose of session management is to help keep user data secure. Without appropriate session management, you can run into several security problems, putting your users at risk. Common vulnerabilities caused by a lack of or poorly implemented session management include:

### Session hijacking

The cookies that you use to store session IDs need to be truly secure. Insecure session cookies are easy for hackers to predict or to use for brute-force attacks. If a hacker can spoof your users’ session IDs, they can impersonate users and take over their accounts. This is known as session hijacking, and it can lead to the loss of sensitive information connected to the account.

### Session fixation

If a specific session token can be used across platforms and without proper expiry protocols, it can be “fixated” by hackers. Essentially, the hacker tricks a user into logging in with a specific session ID, often by adding to the session ID in the URL argument, and then uses those credentials to log in to the user’s account.

### Session resources

Session management systems should beare resource-light, so that attacks, such as denial of service (DDoS) that flood the system with new session requests, don’t consume huge amounts of resources.

### Anomaly detection

Every application runs the risk of hacking attempts. If your session management tool doesn’t have a way to detect abnormal patterns like brute force session ID guessing or DDoS attacks, you’re more likely to fall victim to these attacks.

### Session expiry unset or too long

Session expiration has two potential problems. If you don’t set the timeout period, many programs may leave the cookie or token on the device forever, leaving the account vulnerable to anyone else with the device. Also, a set timeout period that’s too long has the same issue.

## Stay secure with session management

Proper session management addresses all these concerns. It keeps your users and accounts safe by providing secure cookies or tokens, setting appropriate protocols and timeouts, and implementing anomaly detection.

Session management is a fundamental part of running a secure, trustworthy web application. By keeping a handle on your users’ sessions, you can help them avoid the hassle of constant re-authentication without putting them at risk.

You can address all your session management needs by implementing a [user management](/) service, or you can write your own. Either way, your users will thank you for protecting them without making their lives more difficult.

---

# Don’t underestimate the value of a secure, seamless ‘forgot password’ flow
URL: https://clerk.com/blog/forgot-password-sspr.md
Date: 2021-09-16
Category: Insights
Description: Learn about “forgot password” flows, how they work, and the best practices to keep in mind.


Just about every software application today relies on individual user accounts to provide people with a personalized and private experience. However, as [“software eats the world,”](https://a16z.com/2011/08/20/why-software-is-eating-the-world) the average user is managing an increasing number of accounts. Practically every online store, social media platform, SaaS product, newsletter, game, and group requires users to create an online account with a username and password. The average American adult has a total of [130 online accounts](https://www.prweb.com/releases/dashlane_study_us_internet_users_drowning_in_online_accounts_with_further_tidal_wave_approaching/prweb12860738.htm) — and they all need to be kept secure, which exacerbates an already all-too-common problem: lost and forgotten passwords.

That’s why most apps offer password reset flows. This essential workflow allows users to reclaim their accounts while maintaining their security and privacy. Keep reading to learn about “forgot password” flows, how they work, and the best practices to keep in mind.

**‌**

## **What is a ‘forgot password’ flow?**

If you’ve ever had to reset a password, you’ve gone through a “forgot password” flow. Users go through this self-service process to reset their passwords and reclaim their accounts. Any website, app, or other account that relies on passwords for security should have some kind of reset flow.

**‌**Why? Because users are prone to forgetting their passwords. It’s also common for people to forget their accounts entirely or change devices and lose their saved passwords. Without some way for users to quickly and easily reset passwords and reclaim accounts, you may lose users, have to support multiple accounts for the same user, and/or deal with an overwhelming number of “forgot password” support requests.

**‌**

## **How ‘forgot password’ flows work**

Password resets can be manual or automatic. Manual resets rely on the user reaching out to support by email or phone. The support team member asks them some kind of security or verification questions and resets their password accordingly. However, manual flows mean that a significant percentage of your support tickets will be password resets, taking up your staff’s valuable time. Additionally, manual verification is often less secure than an automated process, and can be especially frustrating for a user that needs access quickly.

**‌**The alternative is to implement a [self-service password reset](https://www.hcinnovationgroup.com/home/article/13005329/password-resetting-goes-the-selfservice-route) (SSPR) process. These automated workflows allow users to reset their passwords or reclaim their accounts without human intervention. They’re used by most websites, apps, and other password-protected systems to streamline the security process. Your support staff won’t need to spend time answering password reset claims and can focus on more important work.

Each type of self-service flow works a little differently. For example:

- **Temporary passwords:** This process will send the user a temporary password that they can use to access the system. The user then resets their password themselves once they’re logged in.
- ‌**Email verification:** The system emails the user a link at their primary email address, and the link takes them to a dedicated password reset page.
- ‌**SMS verification:** The system sends the user a text to confirm they want to reset their password, with instructions to follow the reset link.
- ‌**Passwordless logins:** Passwordless flows send a one-time link to the user’s email or phone, allowing them to log in without resetting their password at all. This can be great for a user that just wants to log in from a different device one time, or is pressed for time and wants to reset their password later on
- ‌**Two-factor authentication (2FA) reset:** Two methods are used instead of one to confirm the reset. The user confirms their identity one way and then resets their password with another method. The system may have them check their email or phone for the reset link, and then on the reset page, it may ask for a code that was sent to their phone or a sign-in authentication app.

**‌**

## **Why implementing SSPR workflows can be challenging**

While password reset systems are essential, they can be a complex feature to implement on your own. Rolling your own password reset process means dealing with:

- **The constant evolution of best practices.** [Best practices](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Forgot_Password_Cheat_Sheet.md) regarding SSPR workflows are constantly evolving - from manual reset to security questions to email reset to SMS workflows to passwordless logins. You should keep up with these changes to make sure your password reset process stays secure and up-to-date.
- ‌**Security maintenance.** Password resets need to be a secure workflow end-to-end, or your users’ accounts are at risk of being hacked. If you write your own, designing a secure process is your responsibility.
- ‌**Design and integration.** Your reset process might be technically sound, but it should also be frictionless and well-designed, as an easy sign in experience is critical to retention.
- **Complexity**. Adding more authentication features and options can provide an excellent user experience when everything works right, but it also creates the potential for more “edge cases” where problems occur.

**‌**‌**‌**

## **Get started with password resets today**

If your team has more pressing features to focus on than creating a password recovery flow, [Clerk](/) can eliminate the guesswork (and real work) of user management and authentication. Clerk makes it easy to add complete user management to your app in minutes today, while allowing you to easily make changes and add new features in the future.**‌**

---

# Add authentication to your Gatsby app
URL: https://clerk.com/blog/add-authentication-to-your-gatsby-app.md
Date: 2021-09-15
Category: Guides
Description: Learn how to add authentication and user management to your Gatsby app with Clerk. 

At Clerk, our mission is to empower every developer to easily add authentication and user management to their apps.

We know it's hard to keep up with all the exciting new options out there, so we strive to create more tools and integrations, so you, the developer, can spend more time building what really matters: your app.

With that in mind, we're happy to announce our `gatsby-plugin-clerk`.

## What it does

The plugin wraps our `ClerkProvider` component around the entire Gatsby app.

This has two benefits: keeping the layout component cleaner while grouping all the configuration in `gatsby-config.js`, alongside other plugins.

## How to use it

First and foremost, install the necessary packages:

```sh
yarn add gatsby-plugin-clerk @clerk/clerk-react

# or

npm install gatsby-plugin-clerk @clerk/clerk-react
```

Now, let's configure the plugin on `gatsby-config.js.`

For this step, you'll need the `frontendApi` key of your Clerk application. To find it, go to your [dashboard](https://dashboard.clerk.com), choose the application and the instance you're working on, and locate the key on the **Home** tab.

```javascript
// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: 'gatsby-plugin-clerk',
      options: {
        // OBS: Don't push your frontend API key to version control.
        // A safer approach is to set it as an environment variable for each environment your app will run in.
        frontendApi: 'YOUR_FRONTEND_API_KEY',
      },
    },
  ],
}
```

From here onwards, everything should work just the same. You can start using components like `SignedIn` and `SignedOut` from the root of your app.

```jsx
// src/pages/index.js

import React from 'react'
import { SignIn, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'

function IndexPage() {
  return (
    <>
      <SignedIn>
        <UserButton />
      </SignedIn>

      <SignedOut>
        <SignIn />
      </SignedOut>
    </>
  )
}

export default IndexPage
```

And that's it, in just a few steps, we added easy and secure authentication with beautiful and complete user management to your Gatsby app.

## Bonus: Clerk + Gatsby starter

To make it even easier for you, we went ahead and created a [Clerk + Gatsby starter repository](https://github.com/clerkinc/clerk-gatsby-starter). It has Clerk integrated with [Gatsby's default starter](https://github.com/gatsbyjs/gatsby-starter-default).

Inside `src/api` you can also find the new Gatsby Functions in action. We added a couple of examples there, so you know how to use Gatsby's serverless functions with Clerk's backend API.

Fork it, clone it and build it!

## Bonus 2: Deploy the starter on Gatsby Cloud

Ok, we owe this one to the Gatsby team. They did a great job in building a super easy deployment flow.

So, if you want to deploy the [Clerk + Gatsby starter](https://github.com/clerkinc/clerk-gatsby-starter) on Gatsby Cloud, just click [here](https://www.gatsbyjs.com/dashboard/deploynow?url=https://github.com/clerkinc/clerk-gatsby-starter).

Once there, you can configure a number of things, like the Gatsby Cloud workspace the project should live in, the repository name that will be created in your GitHub account, and even add more integrations.

But here's the one thing you can't forget: your need to add your environment variables, like in the image below. If you don't know where to find them, check the [README](https://github.com/clerkinc/clerk-gatsby-starter/blob/main/README.md) file of this starter.

![Add Authentication To Your Gatsby App guide illustration](./37292ec19825df500deddefc4a6d70f7c3a52a72-1782x928.png)

Once you're done, head over to your GitHub account, find the newly created repository, clone it and start building.

And just like this, you can use all the benefits and performance that Gatsby Cloud provides to Gatsby apps.

*If you encounter a permissions error while doing the steps above, here's what's happening: Gatsby Cloud requires permissions to create and manage future repositories on your GitHub account in order to create a new repository for you.*

*To fix it, go to your [GitHub installations page](https://github.com/settings/installations), and configure Gatsby Cloud as such:*

![Add Authentication To Your Gatsby App guide illustration](./12c63a17a9f99acbc4fa4a623c613da79efac314-1498x546.png)

## Need help?

If you're unfamiliar with how our prebuilt UI components or other details described in the guide work, you can always go to our [documentation](/docs) to find out more or reach out to us on our [Discord](https://clerk.com/discord) server.

Happy coding!

---

# Authenticated data access using Clerk, Prisma, and MongoDB - A post-making fullstack app
URL: https://clerk.com/blog/clerk-prisma-mongodb-fullstack-post-app.md
Date: 2021-08-17
Category: Guides
Description: Add a complete authentication workflow with authenticated access to your Prisma API layer to your web application.

Prisma is a server-side library that helps your app read and write data to your database in an intuitive and safe way. As a next-gen ORM, Prisma lets you increase your productivity and simplify your codebase, by allowing you to write much less code for CRUD operations, and by giving you the protection of type safety.

Prisma has supported connectors for relational databases like PostgreSQL, MySQL and SQLite for quite some time. Recently though, the good folks at Prisma developed and released their MongoDB connector, which combines the type-safe Prisma TypeScript generator with the flexibility of a document store like MongoDB.\*

*\*Currently in Preview mode*

At Clerk we've been really excited about this release, so, we decided to showcase how you can easily add a complete authentication workflow to your web application along with authenticated access for your Prisma API layer.

## The post publishing application

You can find the full source code of this example in our Clerk-Prisma [starter repository](https://github.com/clerkinc/clerk-prisma-starter).

This example application will let you create an account, create posts for others to see, and browse existing user posts. Random idea sharing at its best!

![Clerk Prisma Mongodb Fullstack Post App tutorial illustration](./61c4e714599615331383c0058a018bf5b7226bf7-1497x911.png)

## Setting up the application

To run this application properly you need to configure your Clerk application, create a [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) database instance, then follow the [instructions in the repository](https://github.com/clerkinc/clerk-prisma-starter/blob/main/README.md).

### Creating a Clerk application

If you are new to Clerk you will first need to [create an account](/), then create a new application. For this application, choose the "Standard Setup". Creating a Clerk application will automatically create a development instance, which is what you'll be using.

### Creating a MongoDB Atlas database

MongoDB Atlas is the database-as-a-service solution provided by MongoDB, that gives you all the goodies of a world-class managed database service. From cross-regional resiliency, to security and performance monitoring, MongoDB Atlas is a great choice both for quickly spinning up a MongoDB instance in the cloud, and for companies that require enterprise level services.

Since we just need to get a database instance as quickly as possible for our example project, the forever-free tier is more than enough. Simply sign up, create a database, and **retrieve your secure connection URL**.

*Note: If you would rather use a local database instance, remember that [Prisma requires](https://www.prisma.io/docs/concepts/database-connectors/mongodb#example) your MongoDB instance to be in [replica-set mode](https://docs.mongodb.com/manual/replication). This comes by default with MongoDB Atlas.*

## Show me the code

To run the full example locally, you will need to follow a few small steps. First, go ahead and clone the example application.

`git clone git@github.com:clerkinc/clerk-prisma-starter.git`

Go inside your project folder and copy the .env.example file to a .env.local file.

`cp .env.example .env.local`

### Clerk Environment Variables

You will need the Frontend API value which can be found on the [dashboard](https://dashboard.clerk.com) on your development instance's home page. Add the following to your .env.local file: `NEXT_PUBLIC_CLERK_FRONTEND_API=<your-frontend-api>`.

Next you will need the Clerk API key which can also be found on your dashboard under **Settings ➜ API** **keys**. Add the following to your .env.local file:

`CLERK_API_KEY=<your-api-key>`

### Prisma setup

For Prisma to generate the required TypeScript bindings for our code and facilitate the database access layer, we need to setup a `DATABASE_URL` environment variable beside our application schema file.

Inside the `server/db/prisma` folder, create a `.env` file and there add the `DATABASE_URL` environment variable with the connection URL for your MongoDB instance. It will look something like this:

```bat
DATABASE_URL="mongodb+srv://username:pass@dblocation/myFirstDatabase?retryWrites=true&w=majority"
```

When this is set you can now run the Prisma code generator. The Prisma schema that we have used for this application, signifies to the Prisma ORM that we would like to create a collection of Post documents with the fields and connections as shown below:

```
model Post {
 id          String   @id @default(dbgenerated()) @map("_id") @db.ObjectId
 createdAt   DateTime @default(now())
 title       String
 body        String?
 views       Int      @default(0)
 author      String
 authorEmail String

 @@unique([authorEmail, title])
}
```

Prisma provides a [multitude of helpers](https://www.prisma.io/docs/concepts/components/prisma-schema) to apply on your data model definition which you can use to describe relationships, set constraints and configure attribute types. All that configuration is in code, making it a much more familiar environment for application developers with zero amount of boilerplate.

To generate the TypeScript API for accessing this model, inside the repository, execute the `npm run schema:generate` command. This will create all the required types and APIs to use your MongoDB Posts collection which you can import directly from the `@prisma/client` package. You can use the types both for the entities and the database operations, as shown in the `types/index.ts` file.

### Use Prisma Studio to add a few posts

Another great tool from the Prisma team is Prisma Studio. Prisma Studio gives you a visual data editor with direct access to your database.

![Clerk Prisma Mongodb Fullstack Post App tutorial illustration](./2772efaeae7e205dfabdcfa7a9720803766978b8-1999x438.png)

For this example you can add a couple of posts or more with the `authorEmail` attribute matching the email address with which you will sign up for the app.

## How Clerk provides authenticated access to your data

For the frontend part of our example, `@clerk/nextjs` provides access to the Clerk pre-built components and helpers to enhance your application with user authentication, as quickly and intuitively as possible. Below we will show some of the code snippets that guarantee user authenticated behaviours.

### Only allow interface actions to specific users

Our users should be able to delete only their own posts and capability should be as intuitive as possible on the interface level. To achieve this, we include a deletion button on our post cards, only visible to users which are signed in and their email matches the author of the post.

```tsx
function PostCard({ deletePost }) {
  /** Clerk hook */
  const user = useUser()
  /** Get the user email address */
  const primaryEmailAddress = user.primaryEmailAddress?.emailAddress

  return (
    <CardLayout>
      {/* ... */}
      {primaryEmailAddress === post.authorEmail && (
        <Image
          cursor="pointer"
          onClick={async () => await deletePost(post.id)}
          boxSize="20px"
          alt="delete"
          src="/images/trash.png"
        />
      )}
      {/* ... */}
    </CardLayout>
  )
}
```

As you can see, just by using the `useUser` hook from the Clerk package, we are able to get all the required properties for the signed in user, and achieve the functionality we described.

### Authenticated access to Prisma models

To safeguard your data from unauthenticated access and unauthorized operations directly at the  Prisma model API, you only need to add a thin middleware layer on top of the data model access code. This middleware for our application, will use the `@clerk/nextjs/api` in the API routes to determine the authentication status and recognize the signed in user.

```typescript
async function handler(req: RequireSessionProp<NextApiRequest>, res: NextApiResponse) {
  /** On how this works visit https://nextjs.org/docs/api-routes/dynamic-api-routes */
  const postId = req.query.id as string

  /**
   * For this example, we want to identify the email of the person trying to modify some post.
   * We do this through the Clerk cookie ;)
   */
  const primaryEmailAddress = await getClerkUserPrimaryEmail(req.session.userId as string)

  /** We check if the persisted post email matches the requesters. */
  const persistedPost = await getPostById(postId)

  if (primaryEmailAddress !== persistedPost?.authorEmail) {
    /** If it does not match, he will get a 401 */
    res.status(401).end()
  }

  switch (req.method) {
    case 'PUT':
      /** The client will send the post object in the PUT request body. */
      const modifiedPost = req.body
      const updatedPost = await updatePost(postId, modifiedPost)

      res.status(200).json(updatedPost)
      break
    case 'DELETE':
      await deletePost(postId)
      res.status(200).json({ completed: true })
      break
    default:
      res.status(405).end()
      break
  }
}

export default requireSession(handler)
```

The requireSession helper guarantees that an authenticated user is accessing the endpoint, and also populates the `req.session` attribute on the request object coming from Next.js.

In this endpoint, we retrieve the primary email address of the authenticated user and compare it with the author of the post to be deleted or updated. For the sake of the example, we only check for the primary email address of the user, but since Clerk also supports multiple email addresses per account, you could adjust the logic accordingly.

## Wrapping up

This showcase application demonstrates how, with little effort, you can add authentication and authorization to both the frontend and the backend layers using Clerk and Prisma. Prisma has proved to be an excellent addition to an engineer’s arsenal, especially when it comes to simplicity, productivity and type safety. Folks at Prisma have done an excellent job listening to community feedback, adding new features and solidifying their place as one of the top ORMs out there.

At Clerk we strongly believe that you as an engineer should not have to spend so much time and effort building and maintaining authentication workflows. Authentication, user management and security are hard, and we focus exclusively on giving you the best in class solution. All this so you can focus on what really matters, which is realizing the idea that makes your product unique.

If you have any feedback, are running into trouble, or just want to share what you've built - we'd love to hear from you! Reach out to us on X [@clerk](https://x.com/clerk), on our [Discord community](https://clerk.com/discord), or through any of our other [support channels](https://clerk.com/contact/support).

---

# Generating sortable Stripe-like IDs with Segment's KSUIDs
URL: https://clerk.com/blog/generating-sortable-stripe-like-ids-with-segment-ksuids.md
Date: 2021-08-06
Category: Engineering
Description: Learn how Clerk generates resource IDs with inspiration from Stripe and Segment.

On the shoulders of giants, as they say...

Early in Stripe's lifetime, they launched a new style of resource ID's to a great deal of fanfare. The ID's are prefixed with an abbreviation of the object they represented. For example:

- `ch_` represents a Charge object
- `cus_` represents a Customer object

Patrick Collison offered the motivation for these prefixes on [Quora](https://www.quora.com/How-does-Stripe-generate-object-ids):

![Generating Sortable Stripe Like Ids With Segment Ksuids guide illustration](./a47bef704ff445167a03ad95e862e34ef8f7ca52-1222x440.png)

At Clerk, we're big fans of the prefix and knew early on that we wanted to offer their convenience to our own customers.

That was the easy part of generating our IDs— we spent a lot more time deciding what to put *after* the prefix.

For the uninitiated, ID generation is a surprisingly wide subject area with many important considerations.

For a long time, it was commonplace for developers to rely on sequential IDs generated by the database. There are artifacts of this practice all over the web - but these days, they tend to be avoided because:

1. They can easily be guessed. This isn't inherently a problem, but many, many security vulnerabilities are made worse when attackers can guess resource IDs. On a more light-hearted note, a "bug" involving guessable IDs [led to the creation Reddit self-posts](https://news.ycombinator.com/item?id=20453120).
2. They can reveal a lot of information about application usage. Again, this isn't inherently a problem, but it can lead to unintended side effects. One example came recently when StackOverflow sold to Prosus, and there was [discussion on HackerNews about the low user IDs](https://news.ycombinator.com/item?id=27370507) some users had, indicating they were an early user.
3. They're impossible to generate in distributed systems. For modern applications, this should be the primary concern. If a system has servers in two different regions, it's impossible for them to generate sequential IDs independently, without coordination - but coordination would likely undermine the point of distributing the system in the first place.

To mitigate these issues, randomness has been introduced into ID generation. Enough randomness ensures that IDs cannot be guessed and that collisions are avoided in distributed systems.

But purely random IDs - like [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier) - also eliminate a trait of sequential IDs that developers love: they're sortable.

Finding a middle ground between sortable and unique IDs was a primary motivation behind two newer ID generators:

- Twitter's [Snowflake](https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake)

  > To generate the roughly-sorted 64 bit ids in an uncoordinated manner, we settled on a composition of: timestamp, worker number and sequence number.

- Segment's [KSUID](https://segment.com/blog/a-brief-history-of-the-uuid)

  > It borrows core ideas from the ubiquitous UUID standard, adding time-based ordering and more friendly representation formats.

## Clerk's ID generator

After considering both Snowflake and KSUID, we decided to use KSUID for our primary ID generator. The key factor in our decision was that Twitter's Snowflake included a worker number, which we did not need.

Combined with Stripe-like prefixes, our IDs look like this:

![Generating Sortable Stripe Like Ids With Segment Ksuids guide illustration](./44bfae985e59215dc8839f8e96c82093ec5d6825-1344x239.svg)

---

# Serverless authentication with Clerk and Firebase
URL: https://clerk.com/blog/serverless-auth-with-clerk-and-firebase.md
Date: 2021-07-30
Category: Guides
Description: Protect your Firebase Cloud Functions with user authentication using Clerk.

As modern ventures have a need for architectures and infrastructure that support constant innovation and dynamism, development agility and convenience must be at their core. One of the computing categories that support this paradigm and has solidified its place on our engineering efforts is *Function as a service* (FaaS).

A leading product in this space is Firebase and its [Cloud Functions](https://firebase.google.com/docs/functions) solution. Cloud Functions give you all the power of a serverless framework with both callable and event-driven architectural capabilities.

In this article we are going to show you how to secure your Cloud Functions in an easy and seamless way using Clerk, making sure only authenticated Firebase user clients can access the results of your serverless functions. To do that, we are going to be using the Firebase [web SDK](https://firebase.google.com/docs/web/setup?sdk_version=v8) and the latest Firebase [Callable Functions](https://firebase.google.com/docs/functions/callable).

The Clerk-Firebase integration is **natively** authenticating a user as a Firebase authenticated user, recognized by both systems. This fact provides tremendous flexibility without going through any backend code or calling *yet another API* to continuously verify with both platforms. You as a web developer do not have to do **anything** additional for authentication to work the same way as it would with a standard Firebase authentication provider.

## Prerequisites

This article assumes that you have already:

1. Set up a Firebase Web project with the ability to use [Cloud Functions](https://firebase.google.com/docs/functions/get-started)
2. Correctly set up the [Firebase integration](/docs/integrations/databases/firebase) on your Clerk account

If you need a refresher with an example of the whole process, you can check out our showcase of [how to integrate Firebase with Clerk](/blog/build-a-premium-recipes-app-clerk-firebase).

## Creating and Securing your Function

For the sake of the example, we are going to create a function which receives a message from the Firebase web SDK and returns the message with the user identifier attached. A pretty trivial example but it showcases how to configure your Functions to only be accessible by your authenticated users.

```typescript
import * as functions from 'firebase-functions'

export const returnAuthenticatedMessage = functions.https.onCall((data, context) => {
  /** Prevent unauthenticated Firebase users from receiving the result. */
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'The function must be called while authenticated',
    )
  }

  /** Any kind of data parameter sent from the web client. */
  const text = data.text

  /** Authentication / user information is automatically added to the request through context. */
  const clerkFirebaseUserId = context.auth?.uid

  return {
    message: `Authenticated message '${text}' from user ${clerkFirebaseUserId}.`,
  }
})
```

What this function does is first of all check if the request is coming from an authenticated user, *Firebase callable functions store this information in the context argument*, and if the user is not authenticated, a self-explanatory error is returned. If all goes well, the message is returned, along with the user identifier.

As you can see, there are no external calls on any Clerk related API. The user is **automatically recognized** as being authenticated and also shares the same user identifier in both Clerk and Firebase.

## Invoking the Function from the client

The second part of the equation is to call the function from your client application using the Firebase web SDK.

In your application, after you successfully sign in using the[ Clerk-Firebase integration](/docs/integration/firebase), you can normally call the Cloud Function from your client code.

```typescript
try {
  const returnAuthenticatedMessage = firebase
    .functions()
    .httpsCallable('returnAuthenticatedMessage')

  const response = await returnAuthenticatedMessage({
    text: 'some message',
  })

  /**  Read the result of the Firebase Function. */
  const authenticatedMessage = response.data.text
  // ...
} catch (error) {
  /**  Get the Error details. */
  const code = error.code
  const message = error.message
  const details = error.details
  /** Handle the error in any way you want. */
}
```

That's it! Using the Clerk-Firebase integration, user authentication works native to Firebase, while also providing consistency between user identifiers.

## How does this work ?

The way Clerk integrates with Firebase, authentication is ensured through a token representing a valid Firebase user. This token is retrieved through the Clerk JavaScript library and then handled internally by the Firebase web SDK.

The SDK then automatically attaches the required authentication token on the *HTTP Authorization* header. From there the Firebase platform can provide any user credentials on the `context` object in the callable Firebase Function.

![How does this work ? screenshot](./377971a37ee2b38b6ce0738418a8fcc1f6c6fa4d-774x384.jpg "Low-level abstraction by the Firebase web SDK")

## Parting Words

This brief example showed how you can authenticate your Firebase serverless Functions while using Clerk for authentication. There are no extra steps compared to using Firebase's standard authentication system, but you get all of the benefits of Clerk's best-in-class user management system, including beautiful UIs and a complete user profile page.

We would love to hear your feedback and use cases for integrating Clerk and Firebase. If you have questions or ideas for improvement, reach out to us on X [@clerk](https://x.com/clerk), or through any of our [support channels](https://clerk.com/contact/support). We also have a [Discord server](https://clerk.com/discord) and we would love to see you there. Cheers!

---

# Row-level access for your Airtable-powered application with Clerk
URL: https://clerk.com/blog/row-level-access-for-your-airtable-powered-application.md
Date: 2021-07-17
Category: Guides
Description: Learn how to add complete user authenticated access to your Airtable data API using Clerk.

Airtable is an online platform to access, manage, and collaborate on relational or spreadsheet-like information. The folks at Airtable have done an amazing job on both the user experience and the technical aspects of the product. As a no-code tool – for most use cases – it can help with your whole team’s efficiency around data management.

Airtable is a great choice as a database for any kind of resource you want to serve in an application, but lacks the granular access management capabilities that most web applications need.

As an example, consider an apartment hunting application where realtors need to add and manage the most attractive apartments for each of their clients. Each apartment will be listed in a single table, and you need to make sure that clients can only access the apartments selected for them. To achieve that, we can leverage some Clerk magic to provide **authenticated user access** to only certain rows in your Airtable apartment hunt database.

## Setup for the Apartment Hunt

To kickstart the apartment hunt project, you can start by creating your Airtable account and then use the [Apartment Hunting Template](https://airtable.com/templates/everyday-life/expPfTzGnfpwjgWlS/apartment-hunting) from the template gallery.

![Row Level Access For Your Airtable Powered Application tutorial illustration](./abca3d93141f8442e077610f4b14c9afd905e040-705x599.png)

In the created dataset you will need to add a column that represents the email that the Apartment has been assigned to. Go ahead and create the Email column of type *“Email”.*

![Row Level Access For Your Airtable Powered Application tutorial illustration](./d17297b7d9f1d68ad2d14e5bc2a6ee9916bf987c-1284x423.png)

*Setting the column's type to Email adds more capabilities to the field, including validation that the email address is valid.*

For the sake of our example, you can go ahead and fill the Email column with the email address you will use to access your Apartment Hunting application. For me, it's *[peter@clerk.com](mailto:peter@clerk.com)*.

## Create a Clerk application

If you are new to Clerk you will need to [create an account](https://dashboard.clerk.com/sign-up) on our platform, then follow the steps to create a new application.

After you create an account and a new application for this example, you can move on to the repository setup.

## Show me the code

To run the full example locally, you will need to follow a few small steps. First, go ahead and clone the example application.

```sh
git clone https://github.com/clerkinc/clerk-airtable-apartment-hunt.git
```

Go inside your project folder and copy the `.env.example` file to a `.env.local` file.

```sh
cp .env.example .env.local
```

#### Clerk Environment Variables

You will need the Frontend API value which can be found on the [dashboard](https://dashboard.clerk.com) on your development instance's home page. Set this value as the `NEXT_PUBLIC_CLERK_FRONTEND_API`.

Next you will need the Clerk API key which can also be found on your dashboard under Settings ➜ API keys. Add that as `CLERK_API_KEY` in your .env.local file.

#### Airtable Environment Variables

For Airtable you need to go to the Airtable [account page](https://airtable.com/account) to generate an API key and retrieve your base key. The base key can be found after selecting the newly created database on the [API page](https://airtable.com/api). These variables should be set as `AIRTABLE_API_KEY` and `AIRTABLE_BASE_ID` respectively.

Finally your .env.local file should look something like:

```text
AIRTABLE_API_KEY=keyojbaeZ5KBe9JMR
AIRTABLE_BASE_ID=appBMXDYAGWAgvH8S
NEXT_PUBLIC_CLERK_FRONTEND_API=clerk.2ct1o.leet.lcl.dev
CLERK_API_KEY=test_avDIYjpk0SqaTGF1Wx8MdrEHZIkg2zSObU
```

Now you just need to install the project dependencies with `yarn install` inside the project folder, then `yarn dev` to start the application locally.

## How Clerk provides authenticated access to your data

To authorize Airtable data access with Clerk, we introduce a thin and customizable access management layer over the Airtable API in our backend.\*

*\*The Airtable Rest API does not restrict us from calling it directly from the browser, but it is not recommended since we would need expose sensitive information. For more information, please see [this community forum answer](https://community.airtable.com/t/can-i-use-the-airtable-api-securely-from-the-browser/28810).*

In the Apartment Hunting application, `@clerk/nextjs` takes care of the frontend of user authentication. For apartment data access, we use Next.js API routes to interact with the Airtable API in a secure manner. These routes use `@clerk/nextjs/api` to determine the signed in user.

### Only showing apartments assigned to the current user

To make sure users only have access to the properties assigned to them, we create a `/api/apartments` endpoint to fetch this information. The code for this endpoint can be seen below:

```typescript
async function handler(req: WithSessionProp<NextApiRequest>, res: NextApiResponse) {
  switch (req.method) {
    case 'GET':
      /**
       * Get the user email from the userId attached on the request.
       */
      const userId = req.session?.userId as string
      const user = await ClerkInstance.users.getUser(userId)
      const primaryEmailAddress =
        user.emailAddresses.find((emailAddress) => emailAddress.id === user.primaryEmailAddressId)
          ?.emailAddress || ''

      /** Use the email to retrieve the assigned apartments. */
      const apartments = await getApartmentsByEmail(primaryEmailAddress)
      res.status(200).json(apartments)
      break
    default:
      res.status(405).end()
  }
}

/**
 * Only allow authenticated access or respond with status code 403 Forbidden.
 * Add the req.session attribute on the NextApiRequest object
 */
export default requireSession(handler)
```

The `requireSession` helper guarantees that an authenticated user is accessing the endpoint, and also populates `req.session` attribute on the request object coming from Next.js.

In this endpoint, we retrieve the primary email address of the authenticated user and use it to fetch only apartments assigned to this email. Here, we only check for the primary email address of the user, but since Clerk also supports multiple email addresses per account, you could adjust the logic accordingly.

### Only allow assigned users to modify the apartment status

In a similar manner, we want to restrict editing the apartment status to only the assigned user. The logic for restricting that access can be seen below:

```typescript
async function handler(req: WithSessionProp<NextApiRequest>, res: NextApiResponse) {
  switch (req.method) {
    case 'PUT':
      const apartment = req.body
      const userId = req.session?.userId as string

      /** We make sure prevent a user with different account to update the visitation status. */
      const user = await ClerkInstance.users.getUser(userId)
      const primaryEmailAddress = user.emailAddresses.find(
        (emailAddress) => emailAddress.id === user.primaryEmailAddressId,
      )?.emailAddress

      /** We check if the persisted apartment email matches the requesters. */
      const persistedApartment = await getApartmentById(apartment.id)

      /** If the emails do not match, return 401 Unauthorized */
      if (primaryEmailAddress !== persistedApartment.fields.Email) {
        res.status(401).end()
        break
      }

      const results = await updateApartment(apartment)
      res.status(200).json(results)
      break
    default:
      res.status(405).end()
      break
  }
}

export default requireSession(handler)
```

In the same manner as the apartment fetch, we only allow authenticated access by using the `requireSession` middleware. We perform an extra check with the signed in user's email address to ensure they are assigned to the apartment.

## In summary

This was just a simple example of how Clerk can be used to add row-level access an to application that uses Airtable as it's database. While we built this example, we were really impressed with how powerful Airtable can be at managing project data, with little to no code involved.

In the same manner, Clerk abstracts away the intricacies of authentication and user management, allowing a robust solution to be deployed with little code, and users to managed with no code through our dashboard.

If you have any feedback, are running into trouble, or just want to share what you've built - we'd love to hear from you! Reach out to us on X [@clerk](https://x.com/clerk), on our [Discord community](https://clerk.com/discord), or through any of our [support channels](https://clerk.com/contact/support).

---

# Build a premium recipes app with Clerk and Firebase
URL: https://clerk.com/blog/build-a-premium-recipes-app-clerk-firebase.md
Date: 2021-07-10
Category: Guides
Description: Learn how to connect Clerk as an authentication and user management solution together with your Firebase as your backend.

Firebase is among the top Platform-as-a-Service (*PaaS*) providers for web and mobile applications. It packs tons of powerful and well designed features for developers to spin up a fully fledged application with minimal effort, like storage, analytics, and authentication.

Clerk integrates directly with Firebase, so developers can easily add our beautiful Sign Up, Sign In, and User Profile UIs to their Firebase application. The integration allows developers to use all the full feature set of Firebase without compromising on user management.

In this post, we are going to show you a full example integrating Clerk with Firebase, to make a "premium" recipe showcase application. The recipe data is going to be stored in Firebase Firestore and will only be available to authenticated users.

![Premium recipes app hero UI with Clerk and Firebase](./359e8539774d761e686f5eb611472e72efcc59bf-1131x701.png)

The full code for this guide is available in the [clerk-firebase-starter repository](https://github.com/clerkinc/clerk-firebase-starter), and includes instructions for how to set up Firebase and connect it to Clerk. The application demo is live at [https://fir-clerk.web.app/](https://fir-clerk.web.app).

If you would like to read the documentation before getting started, please refer to our Firebase integration documentation.

## Setting up the Firebase project

To start off, we need a Firebase Web project. Go to the [Firebase Console](https://console.firebase.google.com) and create a new project:

![Create Firebase project in Firebase Console screenshot](./94736e4366e190ee7431337ce1104c1ae5812333-333x236.png)

After giving it a valid name and confirming, you will find yourself in the Firebase dashboard.

## Enabling Firestore and adding recipes

From the Firebase dashboard, you can go ahead and create a new Firestore Database for our example project. The Firestore instance will serve as our database where we will store and retrieve our recipes. If you want to learn more about Firestore, you can take a look at the starter [documentation](https://firebase.google.com/docs/firestore).

During the database instance creation, you should choose the storage location somewhere close to your users. Also keep in mind that by selecting the production mode ruleset, by default you have disabled any reads/writes to your database from outside the platform. We are gonna change that right after!

If you are not familiar with Firebase [Security Rules](https://firebase.google.com/docs/rules), they are basically a Domain-specific language to limit the access to important data in Firebase storage solutions. As we mentioned previously, since these are premium recipes, only authenticated users will be allowed to view them.

To allow authenticated users to read any database but not write, you can use the security rule shown below:

```text
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if request.auth != null;
    }
  }
}
```

## Adding recipes

To add a few recipes, go ahead and create a `recipes` collection with recipes of your liking, but please conform to the same attribute schema shown below:

![Firestore recipes collection document schema screenshot](./749dbbe0c48ae8b85e95e44fb25d6509122cd722-1256x524.png)

If it's helpful, here is the TypeScript type for each recipe document:

```typescript
type Recipe = {
  /** The recipe description */
  description: string

  /** The amount of guilty you should feel */
  calories: number

  /** Cooking time in minutes */
  cookingTimeMin: number

  /** Publicly accessible image full URL */
  imageUrl: string

  /** Number of ingredients needed */
  ingredientsNum: number

  /** The title of the recipe */
  title: string
}
```

After adding a few recipes, you are all set from the data side.

## Enabling Firebase Authentication

Since this is a new project, you will need to enable the Authentication feature. No further action is needed, since Clerk will handle the rest.

![Enable Firebase Authentication feature screenshot](./05531358eaa7d8152a46ee80bb1e9734ca10e810-1438x482.png)

## Enabling the Firebase integration on Clerk

If you are new to Clerk you will need to [create an account](https://dashboard.clerk.com/sign-up) on our platform, then follow the steps to create a new application.

After you create an account and a new application for this example, you can follow [these instructions](/docs/integration/firebase) to enable the Firebase integration on Clerk for your application instance.

With that done, you are now able to authenticate Clerk users in your Firebase application.

## Show me the code

To run the full example locally, you will need to follow a few small steps. First, go ahead and clone our Firebase starter application.

```sh
git clone git@github.com:clerkinc/clerk-firebase-starter.git
```

Go inside your project folder and copy the .env.example file into a .env.local file.

```sh
cp .env.example .env.local
```

Take the Frontend API value which can be found on your [application dashboard](https://dashboard.clerk.com) and add it as the `NEXT_PUBLIC_CLERK_FRONTEND_API` value. Your .env.local file should look something like:

```text
NEXT_PUBLIC_CLERK_FRONTEND_API=clerk.sample.api.lcl.dev
```

The final configuration step is to replace the firebase.web.ts config file with one from your own Firebase project. You can find a specification for the [config object](https://firebase.google.com/docs/web/setup?sdk_version=v8#config-object) in Firebase's documentation.

After you create new Firebase Web project, you will be able to find the required values under Project settings ➜ General.

![Firebase web app configuration settings screenshot](./874f36e6473bd7a22559394ceb65bd9cbed15c8a-936x720.png)

Now you are ready to install the dependencies and run your project in development mode. Go to the root directory of the project and run:

```sh
yarn install
```

and after completion

```sh
yarn dev
```

Your application is now running in your local environment and you can experience the same functionality as the live demo.

## Where the magic happens

If you take away the application setup, the integration is seamless and works out of the box with just a few *copy & paste* steps across Clerk and Firebase. Here's how it works:

Let us go over the way the integration works in your web application code and what are the actions you need to authenticate a Firebase user with Clerk.

The `firebase` application object houses the `.auth()` namespace which includes methods to authenticate a user. One of those methods is `signInWithCustomToken`, which allows third-party providers like Clerk to pass authenticated user data to Firebase.

*Where does this "custom token" come from ?*

After setting up Firebase integration on Clerk, you can retrieve the necessary "custom token" by calling the `getToken` method on the Clerk User object.

Combined, it's just two lines of code:

```typescript
const firebaseToken = await clerkUser.getToken('firebase')
await firebase.auth().signInWithCustomToken(firebaseToken)
```

From that point on, your user is authenticated and can complete all the actions that require privileges of an identified Firebase user.

You can see this in action in our [useRecipes](https://github.com/clerkinc/clerk-firebase-starter/blob/main/client/hooks/useRecipes.ts) hook implementation. If you remove these two lines, the request will fail since the Clerk user will not also be authenticated in Firebase. (*Remember, we set a Security Rule - `allow read: if request.auth != null;` - which restricts access to authenticated users.)*

## Moving forward

This end to end example showed how you can use Clerk and Firebase together for a new web project. Firebase is an exceptional development platform and we are really excited to see what you build with this integration.

If you have any feedback, and running into trouble, or just want to share what you've built - we'd love to hear from you! Reach out to us on X [@clerk](https://x.com/clerk), on our [Discord community](https://clerk.com/discord), or through any of our [support channels](https://clerk.com/contact/support).

---

# Clerk raises $4 million to build the next-gen authentication and user management platform for developers
URL: https://clerk.com/blog/clerk-raises-for-next-gen-auth.md
Date: 2021-07-01
Category: Company
Description: Clerk raised a $4 million seed round led by S28 capital with Andrew Miklas (cofounder, PagerDuty) joining our board.

## Announcing our seed round

Today, we're thrilled to announce that Clerk has raised a $4 million seed round, led by S28 Capital with Andrew Miklas (cofounder, PagerDuty) joining our board. Additional participants include Fathom Capital, Calvin French-Owen and Ilya Volodarksy (cofounders, Segment), Darragh Buckley (first employee, Stripe), Max Stoiber (creator, styled-components).

## The business case

Developers love Clerk because it's fast to set up and maximizes conversion through their Sign Up and Sign In forms. Development speed and conversion rates have become bigger challenges in recent years, as users are demanding more variety in the ways they authenticate.

While privacy-focused users are demanding to create accounts by email, convenience-focused users are demanding "single sign-on" through vendors like Google and Facebook. Subsets of both groups are also security-focused, and are demanding multi-factor authentication.

Creating a cohesive user experience that caters to every group is challenging, and developers often make mistakes that leave their users frustrated or unable to access their accounts. A few examples we commonly see are:

- Disallowing single sign-on for users who originally created their accounts by email
- Allowing single sign-on for users who originally created their accounts by email, but accidentally creating them a second account
- Accidentally reducing multi-factor authentication to single-factor authentication during a "forgot password" flow

Clerk's prebuilt Sign Up and Sign In forms eliminate these mistakes and more. They are built by a team of designers and engineers who focus on maximizing conversion without sacrificing security. If custom designs are preferred, Clerk's API can be used instead to speed up development while still mitigating these errors.

## The path ahead

With this funding, we will focus our efforts on five product areas that are crucial to our customers' success:

1. **Security -** Applications built on Clerk automatically receive critical security features like multi-factor authentication, password breach protection, and device management. We will continue to invest in new protections **for our customers' applications**, as well as begin applying for formal security certifications like SOC-2.
2. **Optimization -** Most businesses simply do not have the resources to focus on optimizing the conversion of their Sign Up and Sign In forms. This is our core competency, and we will continue to analyze our prebuilt components to ensure they are as streamlined as possible.
3. **Customization -** In the months since launch, we've heard a chorus of requests for more customization capabilities in our prebuilt components. More flexibility is on the way, as well as better documentation for creating completely custom user experiences with Clerk's API.
4. **Extensibility -** Another frequent request is the ability to connect user data with other applications. We already deployed webhooks so developers can build connections in-house, and now we will focus on prebuilt connections for common vendors, [starting with Hasura](/docs/integrations/databases/hasura).
5. **Developer Experience -** We launched Clerk with SDKs for React, Next.js, and Express - but beneath those SDKs are language-agnostic REST APIs. The coming months will bring official support for more languages and frameworks.

These five focus areas are driven by the requests of our early adopters, whose enthusiasm and steadfast support made this funding possible. We're excited to continue servicing their needs, and also to welcome the next wave of developers in need of better [tools for authentication and user management](/nextjs-authentication).

## Growing our team

Our team has grown from 2 to 15 in the past eight months, and we're excited to continue that growth across engineering, design, developer advocacy, and more. If you're interested in complex design and engineering problems, or the challenge of creating a ubiquitous developer tool, [come join us](/careers).

With immense gratitude,

Colin and Braden Sidoti\
Cofounders, Clerk

---

# Designing fast sign in forms— diving into the data
URL: https://clerk.com/blog/designing-fast-sign-in-forms.md
Date: 2021-06-17
Category: Insights
Description: Key insights on building fast sign in forms with Social Sign In, password-based, and passwordless authentication.

The modern web is obsessed with speed. Just this week, Vercel [launched Next.js 11](https://nextjs.org/blog/next-11) with a special focus on [Core Web Vitals](https://web.dev/vitals), a new set of Google metrics that are measured in tens of milliseconds to determine page speed. Google has noticed that faster websites mean better user experiences, and has incorporated these metrics into their search ranking algorithms.

At Clerk, we're focused on a speed challenge that's equally important but often neglected: **how quickly can users sign in?**

In working to optimize our prebuilt Sign In UI, we've had a few surprising insights we thought are worth sharing.

## Social Sign In deserves the top spot

After much qualitative debate - Clerk originally launched with Google and Facebook sign in buttons *below* the option to sign in by email.

After collecting a few months of data, we realized we should make an adjustment. There was a near-perfect 50/50 split between users who preferred Social Sign In vs email and password. But, Social Sign In was faster: \~5 seconds on average compared to \~8 seconds for email and password. So we made the switch:

![Designing Fast Sign In Forms tutorial illustration](./714c2cb7ab631cfa11a4ea560116103434d62de5-2400x1600.png)

In the months since making this change, Social Sign In usage has started to outpace email and password, with the last month seeing a 52/48 split.

As expected, since more users are now using a faster authentication strategy, the change has also resulted in a faster overall sign in speed.

## Passwordless should remain a fallback

The passwordless concept has existed in authentication systems for decades. If a user forgets their password, a code or link is emailed to them for authentication without a password.

Recently, there has been a lot of buzz about promoting passwordless flows to the primary authentication mechanism. While Clerk allows developers to configure their Sign In this way, we recommend against it, and suggest leaving it as a fallback.

On average, we see passwordless flows take \~35 seconds to complete. Despite using Sendgrid to deliver our emails quickly and with high inbox rates, the process of checking email is simply *slow* in comparison to Social Sign In or email and password.

## The "edge cases" of Social Sign In are surprisingly common

While building Clerk, we cataloged our frustrating sign in experiences across the web and made sure we had a resolution. The source of much frustration was Social Sign In - even amongst the web's biggest properties, we came across sign in flows that were blocking users from using Social Sign In if they hadn't originally signed up that way:

![Designing Fast Sign In Forms tutorial illustration](./9730f6175509949c5ee0d18fb99e83cc7b61b424-2000x1333.png)

While it's obvious that roadblocks like these slow the sign in process, handling them elegantly takes a lot of development time. Many developers are comfortable pushing off a proper solution because these scenarios feel like edge cases.

In practice, we've learned that these "edge cases" are surprisingly common. In fact, 15.9% of users who have used Social Sign In have also used another method. Of those:

- 2 in 3 originally signed up with a password then later chose Social Sign In
- 1 in 3 originally signed up with Social Sign In, then later tried signed in without (they were sent a code to their email)

At Clerk, we've invested heavily in handling these scenarios as elegantly and quickly as possible. Regardless of a user's choice of sign in strategy, they will always be linked to the same underlying account.

## Clerk's prebuilt Sign In UI

Clerk enables developers to add beautiful, high-conversion Sign In form to their application in minutes. Our prebuilt UI can easily be themed to match any company's brand and style guidelines.

We're constantly analyzing the data in search of better user experiences, as well as evaluating new technologies for addition to our product. If you have questions or ideas for improvement, reach out to us on X [@clerk,](https://x.com/clerk) or [through support](https://clerk.com/contact/support).

---

# Build or Buy? A Look at User Management with Next.js: Part 1
URL: https://clerk.com/blog/build-or-buy-user-management-with-nextjs-1.md
Date: 2021-06-15
Category: Insights
Description: In this article, you'll learn the pros and cons of developing or buying a user management system so you’re ready to make the right choice for your project.

User management is one of the most critical components of any consumer-facing application. Put simply, a user management system is responsible for creating, managing, and removing users. It provides users a way to authenticate themselves so they can use the application, and it offers admins a way to manage the user store. Any application designed to serve users must have a user management system, either developed in-house or bought from a third-party vendor. However, with the advent of modern software development and the rise of various security threats, a simple username-and-password sign-up and sign-in often isn’t enough. The dilemma faced when building an application is if you should build or buy a user management solution.

## Features of a User Management System

A complete user management system comprises a multitude of different features. Here are some of the most important.

### Authentication

Authentication is an essential part of a user management system, as it acts as the entry point for your application. Although a simple username-password or email-password authentication is possible and works for small applications, there are alternative authentication mechanisms that can offer extra convenience and security. You can eliminate the need for passwords by using [email magic links](/blog/magic-links). Although magic links are 4.1x slower than using passwords, they can provide a seamless sign-in and sign-up experience, as well as prevent bot attacks and account takeover risks related to having a password that can be compromised. [Single sign-on (SSO)](/features/social-sso) is another important feature—one that’s 1.3x faster than using passwords. With SSO, users can use their existing account with a third-party service (e. g. Google, Facebook. etc.) to sign in to your application. In addition to being fast, SSO is the preferred method for authentication for 53% of users, resulting in higher conversion rates.

To offer the best possible experience for your users with social SSO, your application must support a wide range of social SSO providers: Google, Facebook, Twitter, GitHub, GitLab, and Discord are the most common. The more social SSO providers you support, the more likely it is that users can use their preferred provider to sign in to your application. Another recommended feature is automatic account linking—if a user signs in with SSO after creating their account with another form of sign in, a new account shouldn’t be created; rather, the SSO should be linked to the original account.

Another consideration is [multi-factor authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication). It's a way to add extra security to your application by requiring users to enter their password and a second factor such as a security code, and is strongly [recommended](https://www.getcybersafe.gc.ca/en/blogs/why-multi-factor-authentication-essential-part-cyber-security), especially for applications where users might store sensitive information.

### Session Management

When a user signs into an application, a session is created, which saves the user from needing to log in with every request. This session must be maintained for the duration of the user's active period, and be destroyed when they sign out. A robust session management system must provide security features like [XSS leak protection](https://clerk.com/docs/security/xss-leak-protection), [CSRF protection](https://clerk.com/docs/security/csrf-protection), and [session leak protection](/docs/security/fixation-protection). In some instances, a multi-session feature can also be useful, allowing users to have multiple accounts and switch between them seamlessly, without having to log out and log in again.

### User Profile Management

A centralized profile/settings page where users can edit their personal information, such as name, email, and profile picture, and security details, such as password, multi-factor authentication preferences, and connected accounts and devices, is another important component of a user management system.

### User Interface

The user-facing side of the user management system is equally important. A poorly designed user interface or clunky user experience can turn potential users away. Whether you buy or build a user management solution, make sure that interfaces like the sign-in page, sign-up page, profile page, etc., are well designed and match the rest of the application.

### Integrations with Other Components

Your user management system should be easy to integrate with the various parts of the application, and flexible enough to allow any future integrations. For example, if you're building a web app, it's quick and easy to build a user management system that is coupled tightly to the web app. But if you build a companion mobile app in the future, you’ll need to either separate the user management system into its own service that the mobile app can communicate with through APIs, or build a separate system for the mobile app—both time-consuming and costly solutions. So it's vital to start with a user management system flexible enough to handle both what you need right now, and what you might need further down the line.

## Pros and Cons of Building a User Management System

In-house development of a user management system offers some advantages. First of all, you get a highly customized system for your application. The design can be tweaked to match your requirements exactly. You also have complete control over how it works and integrates with other components. You can make changes to it when needed, and roll updates whenever and however you want.

Building a user management system might be tempting because of the control that it offers. But before you start building, take a look at the disadvantages.

As you can see from the number of features listed above, a complete, future-proof user management system is incredibly challenging to build, and you'll need a diverse team to tackle it. Developers with expertise in different domains—including database security, cryptography, cybersecurity, system engineering, and of course, developers familiar with the ever-changing landscape of user management—are needed for in-house development.

To build a user management system from scratch also requires you to invest a significant amount of time, money, and engineering power. Devoting resources to the development and ongoing maintenance of a user management system means fewer resources for critical business operations and the development of the core application features. Unless you have manpower to burn, you're probably better off having your developers and resources focused on developing the core application.

A user management system must also be secure enough that users are comfortable entrusting their data to your application. While hashing and salting passwords with an algorithm like [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt) is standard, the security measures shouldn't stop there. Standards like [NIST 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) should be followed to ensure the highest possible security standard. Proper protection against brute force attacks, dictionary attacks, and [credential stuffing attacks](https://owasp.org/www-community/attacks/Credential_stuffing), as well as using a service like [HaveIBeenPwned](https://haveibeenpwned.com) to prevent the use of previously leaked passwords, is a must. You’ll also need to rate limit the endpoints to protect the system from [DDoS attacks](https://us.norton.com/internetsecurity-emerging-threats-what-is-a-ddos-attack-30sectech-by-norton.html). Automated logging and monitoring of all activities must be implemented, and alerts for suspicious activities should be set up to prevent data breaches.

Since the user management system provides the entry point to your application, it needs to be highly available, and must scale in response to increasing load—it can’t crash because too many users are attempting to log in at once.

Even after successfully building a user management system, the hassle doesn't end there. You need to constantly monitor, audit, and improve the code to ensure security. Failure or delay runs the risk of the application being [compromised](https://www.contrastsecurity.com/security-influencers/a-week-of-web-application-hacks-and-vulnerabilities). You'll need an experienced DevOps team to deploy and maintain the user management system to ensure it stays up and running.

## Pros and Cons of Buying a User Management System

Buying a user management system alleviates many of the challenges of user management systems. You don’t have to worry about the scalability or availability of the system, since the service provider will handle them. The security concerns are also lifted from your shoulders, and best practices are already implemented. Reputable vendors, such as [Clerk](/), supports almost any available [social SSO providers](/features/social-sso). They also offer libraries for different programming languages and frameworks such as [Next.js](https://nextjs.org), as well as prebuilt [UI components](https://clerk.com/docs/components/overview). This means you can quickly integrate the user management system with your codebase. You'll also get full technical support from your service provider if you run into any issues.

Though buying a user management solution makes user management easier, there can be some downsides. The biggest concern lies in the fact that you don’t have complete control over the service. You can evaluate different service providers and choose the one that works best for you, but it may be hard to find one who meets all of your requirements. You can always provide feedback or request new features, but implementation is ultimately up to the service provider.

The service provider is also in control of your data, and it can be difficult to verify their level of security before entrusting valuable user data to them. To ensure your data is in good hands, you’ll need to evaluate the policies and practices of the service providers you’re considering. For example, [Clerk](/) has a [secure-by-default philosophy](https://clerk.com/docs/security/overview) and a [responsible vulnerability disclosure policy](https://clerk.com/docs/security/vulnerability-disclosure-policy), which ensures that all the data is properly protected.

The possibility of vendor lock in is another concern. Once your application is tied to a particular user management system, it becomes difficult to switch to a new vendor if the need arises. You'll have to manage the change of vendor without breaking your application.

## Build or Buy?

Now the advantages and disadvantages of each approach are clear, let's answer the title of the article. Should you build or buy? The answer is *it depends*.

You might opt for building a user management system if:

- You only require basic features, like username-password authentication. You can build this yourself using libraries like [next-session](https://www.npmjs.com/package/next-session). For a very simple project, you might be able to use one or two social providers and get away without a username/password database.
- You need a highly customized solution tailored to your needs, and can’t find a service provider that can meet the requirements.
- You have an expert team of highly experienced developers, and you have enough resources to continue developing core business features.
- You're on an extremely tight budget. Although [authentication providers like Clerk](/nextjs-authentication) are free for 500 MAU and only cost $10/month after that, sometimes budget restrictions might force you to make a compromise between buying a user management system and buying some other essential service.

For almost all other cases, it's preferable to buy a user management system. It's the sensible choice when:

- You want to get your product to the market as quickly as possible.
- You want a robust, future-proof user management system.
- You want to save on costs, and don't want to waste resources on reinventing the wheel.
- You don’t have a team of developers who are well versed in the technologies required to create and maintain a user management system.
- You want to focus the organization's resources on the core business operations.
- You have a small userbase that can fall under the free tier of your provider.

## Conclusion

To build or to buy—that's the age-old question of the software development world. Since user management is a crucial part of applications, it makes sense to have a comprehensive, robust system. In this article, you explored the various features of user management systems and looked at the pros and cons of building and buying user management systems.

In the [next part](#link-to-next-part) of this series, you'll follow a hands-on tutorial to build a user management system from scratch with [Next.js](https://nextjs.org), as well as see how to use [Clerk](/) to achieve the same result.

---

# How we use End To End tests to bulletproof our authentication flows across browsers
URL: https://clerk.com/blog/how-we-use-end-to-end-tests-to-bulletproof-our-authentication-flows-across-browsers.md
Date: 2021-06-11
Category: Engineering
Description: Due to the evolution of tooling and the shift towards high-velocity product development, End To End testing is a regular topic of discussion for small teams.

At Clerk, we use End To End testing to create bulletproof authentication flows across browsers.

## The 10,000 ft view of the End To End landscape

In the past, End To End testing was almost exclusively considered a Quality Assurance Engineering topic. For one reason or another, the development of test automation pipelines and application workflow validation tests were not so popular with software engineers. That view has gradually become dated and replaced with the growing popularity of tools like Cypress, Puppeteer, Playwright, and even the latest version of Selenium.

![How We Use End To End Tests To Bulletproof Our Authentication Flows Across Browsers guide illustration](./490c57865ef4011f0134f142c2fee8d3057ad7fd-2000x1404.png)

The growth of those tools should not be seen as a simple technical advancement, but instead as an immediate answer to the growing need to efficiently and effectively validate fast moving development cycles of agile teams. At Clerk we deploy multiple times per day, and without these tools it would be impossible to ensure reliability.

## The high bar for reliability at Clerk

As a solution for authentication and user management, Clerk must maintain exceptionally high reliability, even as our product and team expands rapidly around our core offering. It is critical that end-users can always access their accounts quickly, even through edge cases like originally signing up with Google, then trying to sign in with email instead.We also take great pride in our developer experience, and often jump through hoops to ensure that Clerk works consistently across development, staging, and production environments and across all browsers.To keep reliability high across our myriad of flows - and importantly, to give our own development team confidence that our product won't break with new deploys - it became clear the most pragmatic solution was to develop an End To End test suite.

## Laying out our needs

These were our initial needs exactly as voiced by our team:

- **Cross-browser**: The suite needs to cover all major browsers with as little external service interaction as possible.
- **Happy to code in**: Meaning TypeScript. Most probably Frontend Engineers should be able to write and maintain the tests for the suite (*with as much joy as possible*).
- **Support containerized deployment**: The suite needs to be portable enough to run on a GitHub Action workflow.

## Choosing a platform

After investigating Selenium, Puppeteer, WebDriverIO, Cypress, and Playwright and weighing each platform against our needs, we decided to go with Playwright.

Although it felt less hyped, as a team we felt really confident in the architecture behind Playwright, its stellar documentation and tooling, and the excellent community backing the effort.

We will describe our exact selection process in a later post, but most critically, we appreciate the ability to execute our workflows across Chromium, WebKit and Firefox with so much ease and fine grained control.

![How We Use End To End Tests To Bulletproof Our Authentication Flows Across Browsers guide illustration](./8a588e6ff8136f408783a02c95ef6cbd64c402ef-1048x433.png)

## How the suite is orchestrated

Our End to End test suite is a separate system from our main codebase as we have seen this pattern working really well for most applications.

Its main components as a Node.js application written in TypeScript are:- **Playwright** as the browser automation framework.

- **Jest** as the test suite runner.
- [jest-playwright](https://github.com/playwright-community/jest-playwright) to connect Jest and Playwright as it makes our lives so much easier.
- [Faker.js](https://github.com/faker-js/faker) to create API fixtures that fit our needs for the sign-up and sign-in processes fixtures.
- [Page Objects](https://playwright.dev/docs/pom) as the main pattern representing the interaction facade with our application views in code.

These components have proved to work together seamlessly while staying welcoming to our frontend engineering team. One of our main goals was to ensure that new teammates could understand the system quickly and create new tests, and so far this structure has exceeded our expectations.

## Delivery of the suite on our day to day efforts

To keep us safe from accidental regressions, the test suite must run (and pass!) before any merge to production. To automate this process, integration with our Continuous Integration (CI) tooling was essential.

We run our CI through GitHub Actions, and fortunately, the Playwright team has created GitHub Action tools to simplify triggering the test suite. Paired with Vercel preview deployments, which is where most of our CI tasks take place, both Actions fit the spot quite nicely for End to End suite scenarios. The Playwright team has also created a GitHub action to quickly bootstrap Playwright tests.

The final action file that triggers our End to End suite on every pull request looks something like this:

```bat
jobs:
  e2e:
    if: github.event.deployment_status.state == 'success'
    name: End to End
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.x]
    steps:
      - uses: actions/checkout@v2
      - uses: microsoft/playwright-github-action@74fbf9d1a7c5d8735dab59804da3fdd367a98020
      - uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - name: Run Playwright tests
        run: cd e2e && npm install && npm run test
```

If the action succeeds, we are good to go!

## Parting words

This was a really brief overview of how we went about designing our End to End test suite at Clerk. As Clerk and our customers' needs continue to evolve, we will continue to share our experiences with Playwright and any new tools we adopt.

***P. S.** We have open sourced a template for starting up your own End To End suite using Playwright, so feel free to try it out! [https://github.com/clerkinc/playwright-e2e-template](https://github.com/clerkinc/playwright-e2e-template)*

---

# Build a to-do app with Clerk and Hasura— and no backend code
URL: https://clerk.com/blog/build-a-to-do-app-with-clerk-and-hasura-and-no-backend-code.md
Date: 2021-05-27
Category: Guides
Description: Learn to build a to-do app using Clerk for authentication, Hasura for data storage and access, and Next.js for the frontend.

This guide is outdated. For a more up-to-date tutorial, check out [Build a Cookie Clicker App with Clerk and
Hasura](/tutorials/build-a-cookie-clicker-app-with-clerk-and-hasura).

## Introduction

While traditional applications require both frontend and backend developers, new technologies like Clerk and Hasura are making it possible to build robust backends without writing backend code.

In this tutorial, we'll leverage these new technologies to build a simple to-do list application without writing any backend code. The primary tools we'll use are:

- [Hasura Cloud](https://hasura.io/cloud), for creating a frontend-accessible GraphQL API
- [Heroku Postgres](https://www.heroku.com/postgres), for storing to-do list data
- [Clerk](https://dashboard.clerk.com/sign-up), for authentication
- [Next.js](https://nextjs.org), for frontend development
- [Tailwind CSS](https://tailwindcss.com), for styling

Before we get started, you can see the final result here:

- [Demo of the to-do app](https://clerk-hasura-todos-zeta.vercel.app)
- [Completed codebase](https://github.com/nachoiacovino/clerk-hasura-todos)

Let's begin!

### Create a Hasura project

Start by [signing up for Hasura Cloud](https://hasura.io/cloud).

If you already have a Hasura Cloud account, you will need to manually create a New Project. If this is your first time, a new project will automatically be created for you.

After your project initializes, you will see something like this (with a different name), go ahead and click the cog wheel to go to the project settings:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./9083441a012c0cff18e6ba1144c537db356951d5-842x253.png)

From here, you will need our project's GraphQL API URL. Please copy it, you will need it in a second:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./871909e71d7ce6afe01b48440352579a74254c45-915x729.png)

### Deploy the starter project

We prepared a starter project for this tutorial, the easiest way to get started is with the following "Deploy" button. The button will prompt you through cloning the repo, initializing Clerk, and deploying the app live on Vercel. The starter project uses Next.js, Tailwind CSS and Clerk. It's already setup with some styles using Next.js and Tailwind CSS but you don't have to be proficient in either of these to follow the tutorial.

This button will first prompt you to create a Vercel account if you do not have one. When signing up, Vercel may ask you to grant access to all of your repositories or just selected ones - feel free to choose either option.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./03e4ba61a5b5aff2fd3effd485f4d12835f64756-659x481.png)

The Next step will prompt you to integrate Clerk into your project, click **Install.**

If you do not have a Clerk account already, you will be asked to create one now.

Next, you will be asked to select an application name and a brand color. Then, click **"Create application"**:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./2afb0a154750312a1938002fb3b8e170356cbce1-800x599.png)

After the window closes, click Continue and you will be prompted to pick a Git provider. In this tutorial, we will use **GitHub:**

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./d43be00e7a7e9af7089ac464e0f6b4130a0fa79c-656x477.png)

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./1dbf12f443839f6e57f82e894d520b8e98c0962d-657x481.png)

This is where you will use Hasura Cloud's **GraphQL API** URL you copied earlier. Add it below and click **Deploy**.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./cb34702cda69ba093dce6c2eb9f9f891c9517c2e-663x742.png)

While you wait for Vercel to deploy our project, you can move to GitHub, where Vercel has created a new repository on your behalf. Go ahead and clone it locally.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./cd9946c2d21a8e8f86dcd58b81ab6b3d1b53bf20-1244x771.png)

To clone, go to your desired folder, open a terminal and paste:

```bat
git clone <repository-url>
```

Then, go inside the project folder and run:

```bat
yarn
// or
npm install
```

This will install the necessary dependencies.

After this, go ahead and launch your project:

```bat
yarn dev
// or
npm run dev
```

If you haven’t previously used Vercel on your computer, you will be asked to sign in when you launch the project.

You will be prompted to set up link this local project with the Vercel project. Respond \***\*Y\*\*** to each prompt.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./8fc953112b70f944728a7da4790fedcc9fc8425a-760x347.png)

Then, you will see your project running on [http://localhost:3000](http://localhost:3000).

### File structure

```bat
├── components
│   ├── AddTodo.js (Form to Add todo)
│   ├── Header.js (Header of our app with UserButton)
│   ├── Layout.js
│   ├── SingleTodo.js (One todo with toggle/delete methods)
│   └── TodoList.js (List to render all todos with get method)
├── lib
│   └── apolloClient.js (Apollo configuration wrapper)
├── pages
│   ├── sign-in (Clerk-powered sign in page)
│   │   └── [[...index]].js
│   ├── sign-up (Clerk-powered sign up page)
│   │   └── [[...index]].js
│   ├── user (Clerk-powered user profile page)
│   │   └── [[...index]].js
│   ├── _app.js (where Clerk is configured)
│   ├── index.js (first page you see)
│   └── todos.js (page we will work on)
├── public (images)
├── styles (all css styles for our app)
│   ├── globals.css
│   ├── Header.module.css
│   └── Home.module.css
├── .env.local (environmental variables pulled from Vercel)
├── postcss.config.js (postcss config, needed for Tailwind)
├── package.json (where your packages live)
├── README.md
├── tailwind.config.js
└── yarn.lock
```

### Activate Hasura integration

Hasura is one of the integrations that Clerk offers, with many more coming the future. To use it, you need to enable it. Go to your [Clerk dashboard](https://dashboard.clerk.com), click on your application -> Development -> Integrations and activate Hasura.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./8b8f6731437b043e67dc633131e6818c1ad4ac0e-1043x319.png)

Before leaving the dashboard, go to Home and copy your Frontend API, you'll need to to create the link between Clerk and Hasura.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./9386188495ad4b6f061a00364f397295fe6ed8f0-1046x454.png)

With your project already running, it's time to go back to Hasura and start setting up the database.

## Set up Hasura Cloud

Go back to Hasura, click the cog wheel, click **"Env vars"** and then **"New Env Var"**.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./6e87e29874cfedabb12fa0f18dd7bdb8243929bf-1144x556.png)

Pick HASURA\_GRAPHQL\_JWT\_SECRET from the list and then add this, replacing **%FRONTEND\_API%** with the Frontend API you copied from Clerk.

```bat
{"jwk_url":"https://%FRONTEND_API%/v1/.well-known/jwks.json"}
```

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./1b8ffd6da9dd20b504f25ef5f5f8edaad0ccb2e6-527x461.png)

Click "Add" and then, click "Launch Console".

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./1c5c860f88e543bcca4bc0326ca10d7ba5657bbd-1142x455.png)

This will bring us to GraphiQL. GraphiQL is the GraphQL integrated development environment (IDE). It's a powerful tool you can use to interact with the API.

After GraphiQL opens, the first thing you need to do is to create a table. Start by clicking Data on the top navbar:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./235d8400a644434f2a5e1f611f1dac4c48863ef7-1920x947.png)

For this tutorial, we recommend creating a Heroku database for free:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./1d96a16745ba10676e89944599b6ceadadef008e-792x331.png)

If you don't have a Heroku account, now is the time to create one.

Follow the steps and the database will automatically be created and linked for you.

After the database is created, click **"Public"** and then **"Create Table"**

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./8698910f66341bfe6c355d84552a113a6b0867b0-681x478.png)

Fill the table like this and **"Add Table"**.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./51f8cd6e28ac4ac497ae5ddc56426b0acfb31e66-1007x645.png)

This not only creates our table, but also triggers Hasura to create a GraphQL backend.

After creating the table, the next step is to restrict who can access the data. By default, Hasura is configured for all fields to be public. You need to set permissions and fix that.

### Set table permissions

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./592153500640383cd960b95b03667109bfbac9a6-609x191.png)

You need to create a new role called "user" and edit each of the four possible permissions they have. If you are familiar with **CRUD** (Create, Read, Update, Delete), this is basically the same thing.

### Insert (Create)

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./bbc6ae94e0cc9ed1f528850126775f134b3780d7-1243x811.png)

For **Insert** permissions, choose that the user can only set the `title` of a to-do when a new one is created. There others all have default values:

- `id` is autogenerated (set during table creation)
- `completed` starts as `false`
- `created_at` is autogenerated to `now()` (set during table creation)
- `user_id` is set to the requesting user's ID

Since the `user_id` is dependent on the particular request, it must be configured as a "Column preset."Set it to `X-Hasura-User-Id` from the "session variable."

When you use Clerk's Hasura integration, `X-Hasura-User-ID` is automatically set in the session variable that gets sent to Hasura. The code to retrieve the session variable and send it to Hasura is in `lib/apolloClient.js`.

### Select (Read)

For **Select** permissions, you want to configure Hasura so users can only read their own to-dos. You can verify this by "checking" if the to-do's `user_id` is the same as the `X-Hasura-User-Id` you receive from the session variable.

If the user ID's match, you can grant read permissions to every column. The exact configuration required is below:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./4835557a24bb810db6c74ac78a1e5107f0779e50-583x715.png)

### Update

For **Update** permissions, you want to include the same "check" as **Select**, to ensure that a user can only update their own to-dos.

However, if the check is valid, you don't want the user to have permission to update every column. Instead, only grant permission to update the `completed` column.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./16fc8d112053b024ddd9f20218aea292ddbd350f-560x758.png)

### Delete

For **Delete** permissions, you want to include the same "check" as **Select**, to ensure that a user can only delete their own to-dos.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./cdfcf7790ce2e6d07238c23aad1e6695da0c5139-538x425.png)

That's all of the permissions we need to set! Now, let's work on the frontend.

## Connect Hasura to the Frontend

Go to [http://localhost:3000](http://localhost:3000) and create an account on your app. Then, click **"Start saving your todos"** and you will see this:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./c18fe60869e319bd82bcb6e8f3e72e3125fcf3d7-485x316.png)

These is sample data and is still static. In the next steps of the tutorial, we will connect this list to Hasura and your database, so users can create and manage their own to-dos.

### Create a to-do

The first step is giving users the ability to create a to-do. We will do this from `components/AddTodo.js`.

If you look at the `onSubmit` function, you will see that nothing will currently happen when the user clicks add. You must create a GraphQL "mutation" to update the database when add is clicked.

Replace the top of your file (everything above the return statement) with this code:

```jsx {{ prettier: false }}
import { gql, useMutation } from '@apollo/client'
import { useState } from 'react'

const ADD_TODO = gql`
  mutation AddTodo($title: String!) {
    insert_todos_one(object: { title: $title }) {
      id
      title
    }
  }
`;

const AddTodo = () => {
  const [title, setTitle] = useState("");
  const [addTodo] = useMutation(ADD_TODO, {
    onCompleted: () => setTitle(""),
  });

  const onSubmit = (e) => {
    e.preventDefault();
    addTodo({
      variables: { title },
    });
  };

  return (...
```

This mutation accepts a title and passes it to the `insert_todos_one` method that Hasura has created for us.

Now, let's go back to our frontend and try adding a todo.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./5ca789e007ad71c91b663402b4dc6619f5ad81c6-430x108.png)

You'll see notice that nothing happens on the frontend, and that's expected because we're still reading static to-dos. But, let's check the database to see if the mutation succeeded. Go back to the Hasura Cloud Console, copy and paste the following query and click the play button:

```graphql
query GetTodos {
  todos {
    id
    title
    user_id
    created_at
    completed
  }
}
```

You should see your todo was created successfully:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./6a24c4a662abb8aca62498b14c6de56890aedfcf-953x384.png)

### Fetch to-dos

Now, we will update the frontend to read the user's to-dos from Hasura. You can do this from `components/TodoList.js`.

The file starts by showing static data. Update the component to instead run a GraphQL "query":

```jsx
import { gql, useQuery } from '@apollo/client'

import SingleTodo from '../components/SingleTodo'

export const GET_TODOS = gql`
  query GetTodos {
    todos(order_by: { created_at: desc }) {
      id
      title
      completed
    }
  }
`

const TodoList = () => {
  const { loading, error, data } = useQuery(GET_TODOS)

  if (loading) return 'Loading...'

  if (error) return <>{console.log(error)}</>

  return (
    <div className="overflow-hidden rounded-md bg-white shadow">
      <ul className="divide-y divide-gray-200">
        {data?.todos.map((todo) => (
          <SingleTodo key={todo.id} todo={todo} />
        ))}
      </ul>
    </div>
  )
}

export default TodoList
```

First, we created a query that gets all to-dos (remember, the user can only see the ones attached to their own `user_id`). We set the query to return `id`, `title`, and `completed`. We order the to-dos by `created_at` descending, so the newest are first in the list.

`useQuery` returns an object so you can render different things depending on if the data is loading, if there's an error, or if the data has been retrieved.

We've configured an early return while the data is loading or if there's an error, then render the list if that is available. After saving, you should see something like this:

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./5a80afdca9917e3ec1fb8fdcb49e8bd086b23ab1-268x178.png)

Let's try adding a new todo.

![Build A To Do App With Clerk And Hasura And No Backend Code guide illustration](./85f7ac27b6b73c4343812e4a42fae99dabdc79a2-304x189.png)

You should see that the form clears after clicking "Add", but the list below doesn't automatically update. However, if you manually refresh the page, you will see new to-do.

That's not the best experience and we will fix this later by implementing a cache, so your can keep your database and your frontend in sync.

Before that, let's implement toggle and delete mutations.

### Delete Todo

Open `components/SingleTodo.js`, which is the component the renders for each individual to-do.

Update the code to add a delete mutation when the delete button is clicked:

```jsx {{ prettier: false }}
import { gql, useMutation } from '@apollo/client'
import { GET_TODOS } from './TodoList'

const DELETE_TODO = gql`
  mutation DeleteTodo($id: uuid!) {
    delete_todos_by_pk(id: $id) {
      id
      title
    }
  }
`

const SingleTodo = ({ todo }) => {
  const [deleteTodoMutation] = useMutation(DELETE_TODO)

  const deleteTodo = () => {
    deleteTodoMutation({
      variables: { id: todo.id },
    })
  }

  // rest of the code
```

Now, try deleting a todo. It works, but you get same experience as inserting. You need to refresh the page to see it.

We will fix this shortly, but first let's add toggle functionality.

### Toggle Todo

Still inside `components/SingleTodo.js`, now you can add a new toggle mutation. Here is the updated component with both delete and toggle functionality:

```jsx
import { gql, useMutation } from '@apollo/client'
import { TrashIcon } from '@heroicons/react/solid'
import { GET_TODOS } from './TodoList'

const DELETE_TODO = gql`
  mutation DeleteTodo($id: uuid!) {
    delete_todos_by_pk(id: $id) {
      id
      title
    }
  }
`

const TOGGLE_TODO = gql`
  mutation ToggleTodo($id: uuid!, $completed: Boolean!) {
    update_todos_by_pk(pk_columns: { id: $id }, _set: { completed: $completed }) {
      id
      completed
    }
  }
`

const SingleTodo = ({ todo }) => {
  const [deleteTodoMutation] = useMutation(DELETE_TODO)
  const [toggleTodoMutation] = useMutation(TOGGLE_TODO)

  const deleteTodo = () => {
    deleteTodoMutation({
      variables: { id: todo.id },
    })
  }
  const toggleTodo = () => {
    toggleTodoMutation({
      variables: { id: todo.id, completed: !todo.completed },
    })
  }

  return (
    <li key={todo.id} className="flex justify-between px-6 py-4">
      <div>
        <input
          id={todo.id}
          name="completed"
          type="checkbox"
          checked={todo.completed}
          onChange={toggleTodo}
          className="mr-3 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
        />
        <label htmlFor={todo.id} className={todo.completed ? 'text-gray-400 line-through' : ''}>
          {todo.title}
        </label>
      </div>
      <TrashIcon className="h-5 w-5 cursor-pointer text-gray-500" onClick={deleteTodo} />
    </li>
  )
}

export default SingleTodo
```

Now, every CRUD operation works. But you need still need to refresh the page to see changes. Let's fix that.

Notice we are importing `GET_TODOS`, we'll need it for the next step.

### Using Apollo Cache

The GraphQL library this tutorial uses, Apollo, implements a dynamic, local cache. Instead of reloading the full list of updates after each mutation, you can run the mutations against your local cache. Then, the to-do list on your frontend will automatically be updated.

One great feature of this cache is called the `optimisticResponse`. With this, you can assume that your GraphQL mutations will succeed and reflect the change in your frontend right away, instead of waiting for the success message from Hasura. The `optimisticResponse` is preferred for your to-do app since you're not anticipating any errors, and it results in a faster-feeling user experience.

To use the cache, you need to add the `cache` and `optimisticResponse` parameters to your mutation functions.

In your `deleteTodo` function:

```jsx
const deleteTodo = () => {
  deleteTodoMutation({
    variables: { id: todo.id },
    optimisticResponse: true,
    update: (cache) => {
      const data = cache.readQuery({ query: GET_TODOS })
      const todos = data.todos.filter(({ id }) => id !== todo.id)
      cache.writeQuery({
        query: GET_TODOS,
        data: { todos },
      })
    },
  })
}
```

In your `toggleTodo` function:

```jsx
const toggleTodo = () => {
  toggleTodoMutation({
    variables: { id: todo.id, completed: !todo.completed },
    optimisticResponse: true,
    update: (cache) => {
      const data = cache.readQuery({ query: GET_TODOS })
      const todos = data.todos.map((t) => {
        if (t.id === todo.id) {
          return { ...t, completed: !todo.completed }
        }
        return t
      })

      cache.writeQuery({
        query: GET_TODOS,
        data: { todos },
      })
    },
  })
}
```

Finally, we must leverage the cache in `components/AddTodo.js`:

At the top of the file, add:

```jsx
import { GET_TODOS } from './TodoList'
```

And update your `onSubmit` as follows:

```jsx
const onSubmit = (e) => {
  e.preventDefault()
  addTodo({
    variables: { title },
    update: (cache, { data }) => {
      const existingTodos = cache.readQuery({
        query: GET_TODOS,
      })
      cache.writeQuery({
        query: GET_TODOS,
        data: { todos: [data.insert_todos_one, ...existingTodos.todos] },
      })
    },
  })
}
```

## Final thoughts

That's it! You now have a complete to-do list using Clerk, Hasura, and Next.js - and you didn't write any backend code. It's powerful, easy to configure, and easy to scale.

If you have enjoyed this tutorial or have questions or concerns, please check our [support page](https://clerk.com/contact/support). If you prefer Twitter, feel free to contact me ([@nachoiacovino](https://twitter.com/nachoiacovino)) or Clerk ([@clerk](https://x.com/clerk)) directly.

---

# How HttpOnly cookies help mitigate XSS attacks
URL: https://clerk.com/blog/how-httponly-cookies-help-mitigate-xss-attacks.md
Date: 2021-05-06
Category: Engineering
Description: HttpOnly cookies do not prevent cross-site scripting (XSS) attacks, but they do lessen the impact and prevent the need to sign out users after the XSS is patched. HttpOnly cookies are not a substitute for XSS prevention measures.

**In short: HttpOnly cookies do not prevent [cross-site scripting (XSS)](https://owasp.org/www-community/attacks/xss) attacks, but they do lessen the impact and prevent the need to sign out users after the XSS is patched. HttpOnly cookies are not a substitute for XSS prevention measures.**

Our very first architecture decision at Clerk was to use HttpOnly cookies for session management. It has long been understood that HttpOnly cookies help mitigate cross-site scripting (XSS) attacks, and we felt it was important to include this best-practice directly in our product.

But while there's strong consensus that using HttpOnly cookies is a best practice, we've found many developers are unsure of how they help with XSS. We think this stems from the guidance, which often just says what to do rather than explaining why:

> A cookie with the HttpOnly attribute is inaccessible to the JavaScript[ Document.cookie](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie) API; it is sent only to the server. For example, cookies that persist server-side sessions don't need to be available to JavaScript, and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting ([XSS](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_\(xss\))) attacks.\
> [Mozilla MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)

> Cookies \[...] SHOULD be tagged to be inaccessible via JavaScript (HttpOnly).\
> [NIST 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html#711-browser-cookies)

## The attack vector

After reading this guidance, you might be surprised to learn that HttpOnly cookies do not prevent XSS attacks.

Instead, HttpOnly cookies are helpful when you assume an XSS attack will happen and want to **lessen the impact**. Ultimately, they mitigate XSS attacks by making it easier for organizations to respond.

The specific threat HttpOnly cookies protect against is called **session token exfiltration**, which is a fancy way of saying that the attacker is able to steal a user's session token.

When a session token is stored in a cookie *without* the HttpOnly flag, the token can be stolen during an XSS attack with [document.cookie](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie). This is problematic because session tokens are the primary mechanism used by backends to authenticate a user.

**Once an attacker has access to a session token, they can often act on behalf of a user until that token expires or is revoked.** Actions can be taken remotely - even if the user is no longer visiting the page with the XSS vulnerability - which can serve to dramatically increase the surface area of the attack.

Conversely, when a session token is stored in a cookie *with* the HttpOnly flag, that token cannot be directly exfiltrated during an XSS attack. This minimizes the surface area of the XSS attack and makes it easier for organizations to respond.

## Responding to XSS attacks - without HttpOnly cookies

When an organization is responding to an XSS attack, the first step is always patching the XSS vulnerability.

If HttpOnly cookies were not used, organizations should then assume that session tokens were exfiltrated. This means that - even with the XSS vulnerability patched - the attacker may still have the ability to act on behalf of users.

The next step is revoking the session of any user who may have been subjected to the XSS vulnerability, since those are the users who may have had their session tokens exfiltrated. These users will need to sign in again next time they visit the website.

Lastly, the organization will need to reverse any actions the attacker took on behalf of their users, from the time the vulnerability began to the time session tokens were revoked.

## Responding to XSS attacks - with HttpOnly cookies

With HttpOnly cookies, organizations still need to patch the XSS vulnerability and reverse any actions taken on behalf of their users, but they do not need to revoke sessions and ask users to sign in again.

## What about localStorage and sessionStorage?

Although [window.localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [window.sessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) are newer client-side storage APIs, they function like cookies *without* the HttpOnly flag. HttpOnly cookies are still the only standard mechanism for persisting session tokens that cannot be exfiltrated during an XSS attack.

---

# How to deploy Clerk using Vercel with one click
URL: https://clerk.com/blog/clerk-vercel-one-click-deployment.md
Date: 2021-04-11
Category: Guides
Description: If you are looking to implement Auth in the easiest way possible, this is the tutorial for you. Deploy in 5 minutes.

Hi! My name is Nacho Iacovino ([@nachoiacovino](https://twitter.com/nachoiacovino)), and I'm the Developer Advocate at Clerk. In this guide, I will teach you how to deploy Clerk using Vercel. This is the fastest and easiest way to set up a new application, complete with all the auth features provided by Clerk. In just a few minutes, you’ll create an app, deploy it, and set up your local environment.

If you prefer to follow this tutorial on video, you can [watch it here](https://www.youtube.com/watch?v=6kXKaQVv-k4).

To get started, go to [clerk.com](https://clerk.com) and sign into your account. If you don’t have an account yet, click “Get started for free” and create an account.

## Quick start

Once signed in, you’ll land your Applications page. Let's click **Create application** to create a new application.

![Quick start screenshot](./f058f4cdb1fbbc2500a4e397c49b80ad3b96d101-1920x947.webp "Starting page, here you will find a \"Create application\" button.")

When prompted, choose **Quick start with Next.js and Vercel**.

![Quick start screenshot](./b2ea95ec5f659b9e0cf2813ea7693787821c962a-631x448.webp "Two options, a standard setup and a \"Quick start with Next.js and Vercel\", we'll use this one.")

You will be directed to Vercel. Sign into your Vercel account, or create an account if you don’t have one. Choose a project name then click **Continue** to import the clerk-nextjs-starter repository.

![Quick start screenshot](./3bb14296a04f19420a8301fa163246c74db0030c-653x476.webp "Choose a project name and hit \"Continue\".")

## Install Clerk

Next, let's install the Clerk integration to connect Clerk to Vercel. When prompted, click **Install**. A new window will pop up. Follow the instructions to create a new Clerk application to connect to this Vercel project. Choose an application name and a brand color for your app. By default, the Clerk application name is the same as the Vercel project.

![Install Clerk screenshot](./f9d3d6baf359059b0a30c0bc517c9760dcaf07e3-800x599.webp "Add a Clerk application name and a brand color, and hit \"Create application\".")

Next, you will create a Git repository for your project. You can choose to use GitHub, GitLab, or BitBucket. For this tutorial, we'll use GitHub. Choose a repository name and click **Continue**.

![Install Clerk screenshot](./cf9010cc557956e8b827179a346a82207c1c1dff-651x474.webp "Create the Git repository on GitHub.")

## Deploy the project

On the Import Project screen, Clerk sets the necessary environment variables for you, so you don't have to do anything. Simply click Deploy.

![Deploy the project screenshot](./f6579e44e558e59d912581c25291814aa3bb95da-652x765.webp "Import the project and Deploy, Clerk adds the necessary Environment Variables for you")

Vercel will create and deploy the new Git repository. This step may take a few minutes. After the process is complete, you should see a congratulatory screen.

![Deploy the project screenshot](./a92b293486a2145858faef16946ceb3c9dc1efa0-841x276.webp "\"Congratulations!\" screen with a link to Visit the project.")

You can visit your deployed application by clicking Visit. You can see mine [here](https://clerk-nextjs-vercel.vercel.app)!

## Clone the project locally

The next step is to clone the newly-deployed repository to your local machine. Go to your Git provider, open the repository, and find the URL to clone the repository.

![Clone the project locally screenshot](./9f4a6980f39b251c0658287a75c4881c89928feb-1243x450.webp "GitHub page of the project so you can clone it.")

Open your local machine’s command line interface and navigate to the folder where you want to install your project, then paste the following code snippet:

```sh
git clone <repo_url>
```

Next, open the project and run `yarn install`

After the installation process completes, run `yarn dev`. This is where the magic happens!

Note: If you haven’t previously used Vercel on your local machine, you’ll have to sign in using a [link sent to your email](/blog/magic-links) before running `yarn dev`.

You will be prompted to set up link this local project with the Vercel project. Respond **Y** to each prompt.

And voilà! Your app is now running on `localhost:3000` —you can open it and see everything already working. Create an account on your app and take a minute to explore its features.

## Modify settings

Now that your app is up and running, you can configure user management and theme settings provided by Clerk at [https://dashboard.clerk.com](https://dashboard.clerk.com). Navigate to your application and choose which instance you would like to work on. Each Clerk project has 3 instances: Development, Staging, and Production. Instances are independent and do not share users or settings between them, so you can try different configurations before pushing them live.

![Modify settings illustration](./6f42dfa0338c8366a451105dfb35f885bb5a9f35-1920x947.webp "Different instances of a Clerk app. Development / Staging / Production.")

Any changes made will be applied to your project without the need to modify code or re-deploy.

![Modify settings screenshot](./2e7f92556ec64ecd2a758f07619aee73fa98190a-1920x947.webp "Some of the settings you can modify on your Clerk app.")

Try different configurations and find what works best for your application!

You can also modify theme settings to match the look and feel of Clerk components with the rest of your app.

![Modify settings screenshot](./1500da74670907ae6de28f82ae0f329b3658f917-543x861.webp "Theming options you can modify on your Clerk app.")

And that's it! You just created a new application, deployed it with Vercel, and set up your local environment. No need to mess with complex backend configurations or worry about painful security customizations.

If you run into any problems, don't hesitate to contact me! I’m here to help. Check out my profile on Twitter and DM me at [@nachoiacovino](https://twitter.com/nachoiacovino).

Any feedback is appreciated!

---

# Introducing Clerk: All of user management, not just authentication
URL: https://clerk.com/blog/all-of-user-management-not-just-authentication.md
Date: 2021-01-07
Category: Company
Description: The task came with a sense of helplessness. We knew what "great" looked like, but it was impractical to build all of that functionality.

![--- screenshot](./3537bb21b9137a72b75315872c03b0a97678aef2-2000x914.webp "Google user management")

We thought authentication-as-a-service vendors might ease our pain, but over and over again, we were disappointed by how much extra work was necessary. We never understood why until one friend quipped, *"auth-as-a-service really just solves half of 2-factor auth."*

Then it clicked. **We needed all of user management, not *just* authentication.** And we realized if we could solve this problem, countless others could benefit, too.

Today, we're absolutely thrilled to launch Clerk: **user management as-a-service**. We're solving the whole problem, from frontend to backend, with beautiful UIs and elegant APIs. Our architecture pulls user management out of the way, so developers can focus on what makes their software truly special.

![Today, we're absolutely thrilled to launch Clerk: user management as-a-service. We're solving the whole problem, from frontend to backend, with beautiful UIs and elegant APIs. Our architecture pulls user management out of the way, so developers can focus on what makes their software truly special. screenshot](./a37e8ca7365432384210b4b61e7c11d631148c14-2000x1016.webp "Clerk user management")

Our launch today includes UI components for Sign Up, Sign In, User Profile, and what we're calling the "User Button." They can be mounted directly in your application, or you can redirect users to a Clerk-hosted page on accounts.yourdomain.com.

Best of all, the components will automatically update as our team optimizes their design, develops new features, and adds support for the latest in account security.

While today marks an exciting milestone for Clerk, this is truly just a "minimum viable product." The roadmap ahead will bring many features to better support developers and their end users:

- SDKs for additional languages and frameworks
- Additional OpenID Connect and OAuth providers
- Additional 2-step verification factors like TOTP and WebAuthN
- Session management and revocation in the User Profile
- Team management and enterprise authentication like SAML

Need help with something you don't see listed? [Make a request](https://clerk.com/contact/support).