# Clerk Blog — Guides

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

---

# 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*

---

# 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).

---

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

---

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

---

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

---

# 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).

---

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

---

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

---

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

---

# 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 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).

---

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

---

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

---

# 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!

---

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

---

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

---

# 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)!

---

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

---

# 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!

---

# 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!

---

# 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).

---

# 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) 👋

---

# 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 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).

---

# 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](/).

---

# 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).

---

# 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)

---

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

---

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

---

# 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).

---

# 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).

---

# 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 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!