# Clerk Blog — Page 7

# Comparing Authentication in React.js vs. Next.js
URL: https://clerk.com/blog/comparing-authentication-react-nextjs.md
Date: 2024-03-15
Category: Company
Description: We compare authentication in React.js and Next.js, emphasizing the ease of securing user data with Clerk.

Authentication in React and Next.js is a critical topic. This tutorial will compare authentication in [React.js](/react-authentication), known for dynamic interfaces, and [Next.js](/nextjs-authentication), a framework that optimizes React for production. We'll cover the importance of authentication for securing user data and access to protected components, and dive into the practical differences between using React and Next.js for this purpose.

Additionally, we'll discuss how Clerk can simplify the authentication process with framework-specific solutions for both [React](/react-authentication) and [Next.js](/nextjs-authentication). For practical experience, we've provided a [code repository on GitHub](https://github.com/theodesp/react-vs-nextjs-auth) with examples to deepen your understanding of authentication in React and Next.js.

Let's get started.

## Core Differences between React and Next.js

Let's delve into the real differences between React and Next.js when it comes to authentication and how they affect our decision-making process.

### Server-side rendering vs client-side rendering

React.js is a library for creating user interfaces on web and native platforms, as described in its official documentation. It's primarily a client-side tool for developing UI applications, where UI refers to the interface on the user's device or browser for interacting with websites or applications.

React.js doesn't have built-in server-side rendering capabilities, limiting its function to client-side rendering. However, future updates, such as React Server Component, aim to improve server-side support, though this involves complex nuances.

On the other hand, Next.js is a React framework that enhances React.js with additional features and capabilities. Next.js is a full-stack framework with features including:

- Server-side Rendering (SSR): Where pages are generated on the server and sent to the client.
- React Server Component support (RSC)
- Routing: A Built-in routing system.
- Static Export (SSG): Pre-rendering pages at build time to serve static HTML.
- Automatic Build Size Optimization: Optimizes build sizes automatically.
- Incremental Static Regeneration: Re-rendering static pages on-demand without rebuilding the entire site.

Even when utilizing Next.js, which facilitates server-side rendering with React, developers encounter restrictions on certain authentication operations. We'll expand deeper into these limitations as we examine the actual code examples.

### React Routing capabilities

React.js does not handle page routing for you. It’s up to the developer to choose how to navigate through pages or redirects in a concise manner. That's why developers opt-in to use a third party tool like [React-router](https://reactrouter.com/en/main) or [TanStack Router](https://tanstack.com/router/latest) to satisfy their needs.

Next.js on the other hand has routing and it’s fixed or built-in with no option to override it at least on the server side. As a matter of fact, Next.js currently supports two routing methods:

**Pages** creates routes within the pages folder.

**App Router** organizes routes within the app folder.

Although both routing options can be used together, they fundamentally work differently so they are not compatible with each other without certain modifications.

## Example: How to Implement Authentication in React

We now take a closer look on how to Implement Authentication in React from scratch. You can work your way through this section of the tutorial with the code examples as located in the react-auth folder.

### Overview of client-side authentication

Focusing on React as a client-side library, our overview will center on creating authentication components like login and logout forms and managing the visibility of sensitive information.

Client-side authentication primarily involves showing or hiding content based on a user's authentication status. Unauthenticated users are redirected to a [login page](/blog/building-a-react-login-page-template) to enter credentials for accessing protected routes. This common approach in applications is standard and typically trouble-free.

However, it involves intricate technicalities that are not apparent to the end user. Developers must handle authentication securely and efficiently on the client side, including in React applications. We will outline key considerations for client-side authentication before diving into coding specifics.

### Considerations for client-side authentication

When adopting authentication solutions for your client-side app, the first step is defining criteria for identifying users. Additionally, you need to consider the following:

**Security Concerns**

To ensure security, it's essential to manage the generation, storage, and retrieval of Personally Identifiable Information (PII), session identifiers, or tokens that could be misused for user impersonation. Given that sensitive information is handled client-side, it's critical to mitigate potential security risks effectively.

**State Management**

If you store session keys in the user's device or browser, you must establish how to manage them in the application's state. This involves determining the appropriate data structure for storing authentication tokens or session information, implementing mechanisms to synchronize state changes across components, and handling authentication-related events such as login and logout actions.

In many cases, React should be used as a thin client aiming to retain minimal data. The responsibility for managing session revalidation or token updates, in accordance with the security protocols of the organization, is typically delegated to the server, which instructs the client accordingly.

Considering the above parameters, let's explore in practice how to implement these considerations in React and how easy it is to miss important details. The code for the whole tutorial section is located in the `react-auth` folder of the repo.

### Example of setting up authentication in React

Let’s show now how to set up authentication in React. We first initiate a new React Project using [Vite](https://vitejs.dev) since `create-react-app` is deprecated:

```bash
$ npm init @vitejs/app react-auth --template react-ts
$ cd react-auth
```

#### Step 1: Setup the main App Router page

We'll use React Router to manage navigation within our application. If you haven't already installed React Router, you can do so using npm:

```bash
$ npm install react-router-dom
```

Next, let's define the main application router in our `App.tsx` file:

```tsx {{ title: 'App.tsx' }}
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import Login from './Login'
import Profile from './Profile'
import AuthProvider from './providers/AuthProvider'
import Protected from './components/Protected'

function App() {
  return (
    <div className="App">
      <Router>
        <AuthProvider>
          <Routes>
            <Route path="/login" element={<Login />} />
            <Route element={<Protected />}>
              <Route path="/profile" element={<Profile />} />
            </Route>
          </Routes>
        </AuthProvider>
      </Router>
    </div>
  )
}
export default App
```

Inside the `<AuthProvider />` component, we define routes for the login page (`/login`) and the user profile page (`/profile`). The `<Protected/>` component ensures that the `/profile` route is only accessible to authenticated users. If a user tries to access the profile page without authentication, they will be redirected to the login page.

Since we haven’t defined what the `AuthProvider`, `Protected` or `Login` components do yet let's proceed to explore the client side components next.

#### Step 2: Define the Login Component

Let's examine the structure and style of the login page component, which includes a form for users to input their credentials for authentication.:

```tsx {{ title: 'Login.tsx' }}
import React, { useState } from 'react'
import { useAuth } from './hooks/useAuth'
import './Login.css'
const Login: React.FC = () => {
  const [credentials, setCredentials] = useState({ username: '', password: '' })
  const auth = useAuth()
  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (formValid(credentials)) {
      await auth.loginAction(credentials)
    }
  }

  const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setCredentials((prev) => ({
      ...prev,
      [name]: value,
    }))
  }

  const formValid = (state: typeof credentials) => {
    return state.username && state.password
  }

  return (
    <div className="login-container">
      <form onSubmit={handleLogin} className="login-form">
        <input
          type="text"
          placeholder="Username"
          name="username"
          value={credentials.username}
          onChange={handleInput}
        />
        <input
          type="password"
          placeholder="Password"
          name="password"
          value={credentials.password}
          onChange={handleInput}
        />
        <button className="login-button" type="submit">
          Login
        </button>
      </form>
    </div>
  )
}
export default Login
```

In the Login Page, we use  the `useState` hook to manage the state of the input fields (credentials). The `handleLogin` function is called when the form is submitted, and it triggers the login action by calling the `loginAction` function from the `useAuth` hook. The `handleInput` function updates the state as the user types in the input fields.

#### Step 3: Define the AuthProvider and Custom Hooks

Now that we have our login page component ready, let's implement the authentication logic using the `AuthProvider` and custom hooks. The `AuthProvider` component will manage the authentication state and provide authentication-related functions to its child components using React context:

```tsx {{ title: 'AuthProvider.tsx' }}
import React, { createContext, PropsWithChildren, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import useToken from '../hooks/useToken'
import appConfig from '../app.config'

interface UserData {
  username: string
}

export interface Credentials {
  username: string
  password: string
}

interface LoginData {
  token: string
  user: UserData
}

interface AuthContextType {
  token: string | null
  user: UserData | null
  loginAction: (data: Credentials) => void
  logoutAction: () => void
}

export const AuthContext = createContext<AuthContextType | undefined>(undefined)

const AuthProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const [user, setUser] = useState<UserData | null>(null)
  const { token, setToken } = useToken()
  const navigate = useNavigate()

  const loginAction = async (credentials: Credentials) => {
    try {
      // Mocking authentication request
      const response = await fetch(`${appConfig.AUTH_API_URL}/login`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(credentials),
      })
      if (!response.ok) {
        throw new Error('Invalid credentials')
      }
      const data: LoginData = await response.json()
      setUser(data.user)
      setToken(data.token)
      navigate('/profile')
    } catch (error) {
      console.error('Login error:', error)
    }
  }

  const logoutAction = () => {
    setUser(null)
    setToken(null)
    navigate('/login')
  }

  const authContextValue: AuthContextType = {
    token,
    user,
    loginAction,
    logoutAction,
  }

  return <AuthContext.Provider value={authContextValue}>{children}</AuthContext.Provider>
}
export default AuthProvider
```

Here the `loginAction` function handles user authentication by sending a POST request to the server with the user's credentials. Upon successful authentication, it updates the user state with the authenticated user data and stores the authentication token in local storage. It then navigates the user to the profile page.

The `logoutAction` function clears the user state and removes the authentication token from local storage, effectively logging out the user and redirecting them to the login page.

The `useAuth` and `useToken` hooks are  shown next:

```tsx {{ title: 'useAuth.ts' }}
import { useContext } from 'react'
import { AuthContext } from '../providers/AuthProvider'

export const useAuth = () => {
  const authContext = useContext(AuthContext)
  if (!authContext) {
    throw new Error(
      'useAuth hook was called outside of context, make sure your app is wrapped with AuthProvider',
    )
  }
  return authContext
}
```

The `useAuth` hook allows components to access the authentication context provided by the AuthProvider.

Next, let's define the `useToken` hook:

```tsx {{ title: 'useToken.ts' }}
import { useState } from 'react'

const ACCESS_TOKEN_KEY = 'access_token'
export default function useToken() {
  const getToken = () => {
    const token = localStorage.getItem(ACCESS_TOKEN_KEY)
    return token
  }

  const [token, setToken] = useState<string | null>(getToken())

  const saveToken = (data: string | null) => {
    if (!data) {
      localStorage.removeItem(ACCESS_TOKEN_KEY)
    } else {
      localStorage.setItem(ACCESS_TOKEN_KEY, data)
      setToken(data)
    }
  }

  return {
    token,
    setToken: saveToken,
  }
}
```

The `useToken` hook provides a simple interface for managing token storage and retrieval using local storage.

Tokens facilitate authenticated server requests, but relying solely on token authentication has limitations and security risks. Simple token systems often lack secure methods to refresh or revoke tokens, exposing applications to unauthorized access if tokens are compromised. Moreover, storing tokens in local storage makes them vulnerable to cross-site scripting (XSS) attacks, where malicious scripts could steal tokens, risking security breaches.

Serving this API should be done over HTTPS to ensure there are no Man In The Middle attacks (MitM) that could compromise the token value.

Setting those arguments aside, the last component is the `Protected` component that ensures certain routes are only accessible to authenticated users. This component will act as a guard, preventing unauthorized access to protected routes by redirecting unauthenticated users to the login page:

```tsx {{ title: 'Protected.tsx' }}
import React from 'react'
import { Navigate, Outlet } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'

const Protected: React.FC = () => {
  const { token } = useAuth()

  // If the user is not authenticated, redirect to the login page
  if (!token) {
    return <Navigate to="/login" />
  }

  // If the user is authenticated, render the child routes
  return <Outlet />
}
export default Protected
```

The `Protected` component utilizes the `useAuth` hook to tap into the authentication context and obtain the authentication token. If authentication is confirmed (indicated by the presence of the token), the Protected component displays the child routes. This setup permits the rendering of nested routes within protected areas for authenticated users.

#### Step 4: Implementing authentication endpoints on the server

Nevertheless you still need to have a server to handle client-side authentication with React. In our case, since we are building things from scratch we will have to consider the simplest option.

Here is what the server code looks like:

```tsx {{ title: 'auth-server.js' }}
import express from 'express'
import bodyParser from 'body-parser'
const app = express()
const PORT = 5000
// Middleware to parse JSON request bodies
app.use(bodyParser.json())

// Mock user data
const users = [
  { username: 'theo', password: 'password1', user: 'Theo' },
  { username: 'alex123', password: 'password2', user: 'Alex' },
]
// Authentication endpoint: POST /auth/login
app.post('/auth/login', (req, res) => {
  const { username, password } = req.body
  const user = users.find((u) => u.username === username && u.password === password)
  if (!user) {
    return res.status(401).json({ message: 'Invalid username or password' })
  }
  // Generate authentication token
  const token = generateToken(user)
  res.json({ token, user: user.user })
})
function generateToken(user) {
  return `generated-token-for-${user.username}`
}

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`)
})
```

This server sets up a route : `/auth/login` for user authentication. It uses a simple array of user objects as a mock user database and a fake token generator for generating an access token.

Once you have our server set up, we can proceed in testing the whole authentication workflow.

#### Step 5: Running the Development Environment

To locally test our authentication setup, we must run the Vite development server for the React application alongside the mock auth server. This can be done by inserting a custom script into our `package.json` file.

First, let's install the required dependencies:

```bash
$ npm install --save-dev concurrently
```

Next, let's update our `package.json` file with a script that starts both the Vite development server and the mock authentication server:

```json {{ title: 'package.json', prettier: false }}
"scripts": {
  "start": "concurrently \"vite\" \"node auth-server.js\""
}
```

Running `npm start` will simultaneously initiate the Vite development server and the authentication server, enabling local testing of our authentication setup as if it were on a live server.

That required significant effort! We've merely begun with a basic setup that doesn't cover user sessions or storing user information in a database—both crucial for production-level authentication.

Next, we'll explore how to enhance our approach with a Next.js implementation.

## Example: How to Implement Authentication in Next.js

In this part, we'll delve into how to incorporate authentication in a Next.js application using NextAuth.js. This package simplifies adding authentication to Next.js projects by providing ready-made support for well-known providers such as Google, GitHub, and Facebook, reducing the manual configuration often seen in React authentication.

Next.js's API routes feature further streamlines this process, allowing us to manage authentication logic directly within our project without a separate server. This enhances our solution's maintainability. The complete code for this tutorial can be found in the `nextjs-auth` folder of the repository.

#### Step 1: Install NextAuth.js

First, let's install Next with NextAuth.js and its dependencies:

```bash
$ npx create-next-app@latest nextjs-auth
$ cd nextjs-auth
$ npm install next-auth@beta @auth/core
```

#### Step 2: Configure NextAuth.js

Next, create a new file named `auth.ts` somewhere in your application. This file will handle authentication requests and configurations. In this tutorial, we are using GitHub as the authentication provider so you need to make sure you register a [new GitHub App](https://github.com/settings/apps/new) to get the provider credentials. Also make sure to set up the correct callback URL. Here is mine for reference:

![NextAuth GitHub configuration](./github.png)

*Setting up a new GitHub App*

```tsx {{ title: 'lib/auth.ts' }}
import NextAuth from 'next-auth'
import Github from '@auth/core/providers/github'
export const {
  handlers: { GET, POST },
  auth,
  signIn,
  signOut,
  unstable_update,
} = NextAuth({
  debug: process.env.NODE_ENV === 'development',
  secret: process.env.NEXTAUTH_SECRET,
  session: {
    strategy: 'jwt',
  },
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID || '',
      clientSecret: process.env.GITHUB_SECRET || '',
    }),
  ],
})
```

Then export the auth handlers to the  `app/api/auth/[...nextauth]/route.ts` as well. This will handle all the API requests for the `next-auth` package and any configured OAuth callbacks.

```tsx
export { GET, POST } from '@/lib/auth'
```

#### Step 3: Use the provided auth functions to enforce authentication

The `auth` function effectively fetches the current user session. If the user is unauthenticated, it returns null, allowing us to check and redirect as necessary.

Below is an example of implementing protected routes using this approach:

```tsx {{ title: 'pages/api/protected.ts' }}
import { auth } from '@/lib/auth'
export default auth((req) => {
  if (req.auth) {
    return Response.json({ data: 'Success' })
  }
  return Response.json({ message: 'Not authenticated' }, { status: 401 })
})
```

And here is how to protect pages:

```tsx {{ title: 'app/profile/page.tsx' }}
import { redirect } from 'next/navigation'
import { authOptions } from '@/lib/auth'
export default async function Page() {
  const session = await auth()
  if (!session) {
    redirect('/login')
  }
  return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```

#### Step 4: Setup the Login page

To log in using GitHub or any other configured provider, utilize the `signIn` function exported from `auth.ts`.

```tsx {{ title: '(auth)/login/page.tsx' }}
import { Login } from '@/components/Login'
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'

export default async function LoginPage() {
  const session = await auth()
  if (!session?.user) return <Login provider="github" />
  redirect('/profile')
}
```

The `Login` component interacts with server actions as follows:

```tsx {{ title: 'components/Login.tsx' }}
import { signIn } from '@/lib/auth'
export function Login({ provider, ...props }: { provider?: string }) {
  return (
    <form
      action={async () => {
        'use server'
        await signIn(provider)
      }}
    >
      <button {...props}>Login</button>
    </form>
  )
}
```

#### Step 5: Testing the login with GitHub flow

The final step involves testing the entire authentication process with GitHub. Start by navigating to the `/login` page to view the main `Login` button. Upon clicking the `Login` Button, you'll be redirected to GitHub, where you can complete the authentication login process.

Integrating [authentication with Next.js](/nextjs-authentication) and the NextAuth package marked a substantial improvement over the React example. Yet, configuring it revealed certain complexities.

Primarily, the [NextAuth documentation](https://next-auth.js.org/getting-started/introduction) focuses on authentication setup for the pages router, necessitating a visit to the beta documentation for `app` folder insights, leading to discrepancies in examples.

Moreover, not configuring the `secret` and `session` strategy for the GitHub provider in the NextAuth configuration led to difficult-to-diagnose 500 errors. This indicates that developers must still dedicate significant effort to achieve a smooth authentication process with Next.js. The final part of this tutorial will explore whether a more streamlined approach exists.

## How do React and Next.js Authentication differ from each other?

This guide highlights that both React and Next.js offer flexibility in authentication options without enforcing a specific approach, leaving the decision to developers. However, implementing client-side authentication with React still requires a server.

Below is a table illustrating the key differences between authentication in React and Next.js:

| Aspect                   | React.js      | Next.js                                                                                 |
| ------------------------ | ------------- | --------------------------------------------------------------------------------------- |
| Authentication Libraries | External Only | External Only                                                                           |
| Session Management       | No            | [Yes](https://clerk.com/blog/complete-guide-session-management-nextjs)                  |
| Server Side Code         | No            | Yes                                                                                     |
| Middleware support       | No            | [Yes](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) |

While Next.js offers somewhat better support for authentication compared to React, leveraging external solutions for authentication and authorization remains essential. These solutions must be compatible with Next.js's newest features, like the App router, for smooth integration and to fully utilize the framework's capabilities.

Considering the effort involved, would you prefer a more straightforward approach? Explore how Clerk simplifies authentication challenges for both React and Next.js.

## Simplifying Authentication with Clerk

This part of the tutorial is brief, thanks to Clerk's streamlined solution for handling authentication in React and Next.js, app-router support included.

You just need to follow these four simple steps to set it up!

### Clerk with React

Clerk offers a dedicated library for this purpose, and you can easily follow the steps outlined in their [quickstart guide](https://clerk.com/docs/quickstarts/react) to get started.

### Clerk with Next.js

**Step 1: Install Clerk**

```bash
npm install @clerk/nextjs
```

**Step 2: Setup Clerk Secret Keys**

```env {{ title: '.env.local' }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=<CLERK_PUBLISHABLE_KEY>
CLERK_SECRET_KEY=<CLERK_SECRET_KEY>
```

**Step 3: Setup the ClerkProvider on the root component**

Add the `<ClerkProvider/>` on the root layout component:

```tsx {{ title: 'app/layout.tsx' }}
import { ClerkProvider } from '@clerk/nextjs'
import './globals.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

Then configure the `authMiddleware` to protect routes:

```tsx {{ title: 'middleware.ts' }}
import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware({
  publicRoutes: ['/'],
})
export const config = {
  matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api)(.*)'],
}
```

**Step 4: Utilize the relevant Authentication components where you see fit**

Here is a full example of using `<UserButton />` which allows users to manage their account information and log out:

```tsx {{ title: 'app/page.tsx' }}
import { SignedIn, SignedOut, SignInButton, UserButton } from '@clerk/nextjs'
function Header() {
  return (
    <header
      style={{
        display: 'flex',
        justifyContent: 'space-between',
        padding: '10px',
      }}
    >
      <h1>My App</h1>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
      </SignedIn>
      <SignedOut>
        <SignInButton />
      </SignedOut>
    </header>
  )
}
export default function Home() {
  return (
    <main>
      <Header />
    </main>
  )
}
```

We didn't encounter any difficulties implementing essential user authentication features like login and logout with Clerk. Clerk simplifies the management of authentication state and sessions, making it straightforward and efficient.

This concise tutorial demonstrated Clerk's ability to effortlessly manage authentication in both React.js and Next.js, positioning it as the ideal solution for authentication requirements.

If Clerk's features have caught your attention, we recommend trying it as your primary authentication solution. For additional tutorials and resources, check out our [docs](https://clerk.com/docs). Sign up for the [free plan](https://clerk.com/pricing) to explore everything Clerk provides.

---

# How to Add an Onboarding Flow for your Application with Clerk
URL: https://clerk.com/blog/add-onboarding-flow-for-your-application-with-clerk.md
Date: 2024-01-30
Category: Engineering
Description: Leverage Clerk’s customizable session tokens, publicMetadata and Next’s Middleware to create a robust onboarding experience within a few lines of code.

As part of your onboarding flow, you may want to collect extra information from your user and use it to drive your application state. Let’s walk through a quick example using Next.js and TypeScript to show you how simple implementing an onboarding flow can be.

In this guide, you will learn how to:

1. Add custom claims to your session token
2. Configure your middleware to read session data
3. Update with the user’s onboarding state

To see a working example, check out our [sample demonstration app here](https://sample-onboarding-app.clerkpreview.com).

The examples below have been pared down to the bare minimum to enable you to easily customize them to your needs, you
can build them with the [Clerk + Next Quickstart](/docs/quickstarts/nextjs) using @clerk/nextjs 6.0.2 and Next
15.0.2.

Let’s get started!

![Add Onboarding Flow For Your Application With Clerk guide illustration](./3e1f0959942a439402674187ee45ef6c592e5e38-2400x1392.png)

## Add Custom Claims to Your Session Token

Session tokens are JWTs that are generated by Clerk on behalf of your instance, and contain claims that allow you to store data about a user’s session. With Clerk, when a session token exists for a user, it indicates that the user is authenticated, and the associated claims can be retrieved at any time. \[[Learn More](/docs/backend-requests/making/custom-session-token)]

First, navigate to [Sessions](https://dashboard.clerk.com/last-active?path=sessions) in your Clerk Dashboard and click the ‘Edit’ button. In the modal that opens, there will be a window where you can augment your session token with custom claims.

In there, add the following and hit save:

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

If you haven’t already, we can make the public metadata type information accessible to our application by adding the following to `src/types/globals.d.ts`:

```typescript {{ filename: 'src/types/globals.d.ts' }}
export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      onboardingComplete?: boolean
      applicationName?: string
      applicationType?: string
    }
    firstName?: string
  }
}
```

We have just added custom data to our session token in the Clerk Dashboard and made those claims accessible to our app. Next, we’ll use `clerkMiddleware` to redirect the user based on `onboardingComplete` status.

## Configure your Next.js middleware to read session data

Clerk's `clerkMiddleware()` allows you to configure access to your routes with fine grained control. You can also retrieve claims directly from the session and redirect your user accordingly. \[[Learn More](/docs/references/nextjs/clerk-middleware#usage)]

Add the code sample below to your `src/middleware.ts` file:

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

const isPublicRoute = createRouteMatcher(['/', '/onboarding'])

export default clerkMiddleware(async (auth, req) => {
  const { userId, sessionClaims, redirectToSignIn } = await auth()

  // If the user isn't signed in and the route is private, redirect to sign-in
  if (!userId && !isPublicRoute(req)) {
    return redirectToSignIn({ returnBackUrl: req.url })
  }

  // Catch users who do not have `onboardingComplete: true` in their publicMetadata
  // Redirect them to the /onboading route to complete onboarding
  if (
    userId &&
    !sessionClaims?.metadata?.onboardingComplete &&
    req.nextUrl.pathname !== '/onboarding'
  ) {
    const onboardingUrl = new URL('/onboarding', req.url)
    return NextResponse.redirect(onboardingUrl)
  }

  // If the user is logged in and the route is protected, let them view.
  if (userId && !isPublicRoute(req)) {
    return NextResponse.next()
  }
})

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

Next, create a `layout.tsx` file in `src/app/onboarding` and add the following code to the file. This logic could go in the Middleware, but by adding to the `layout.tsx` to the route the logic remains in one place. This file can also be expanded to handle multiple steps, if multiple steps are required for an onboarding flow.

```tsx {{ filename: 'src/app/onboarding/layout.tsx' }}
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  // Check if a user has completed onboarding
  // If yes, redirect them to /dashboard
  if ((await auth()).sessionClaims?.metadata?.onboardingComplete === true) {
    redirect('/dashboard')
  }

  return <>{children}</>
}
```

Now that we have the logic for where to direct the user, we’ll need a way to track their onboarding status and note it on their session, let’s dig into that now!

## Update publicMetadata based on onboarding state

Updating a user's `publicMetadata` as they progress through the flow will allow us to recognize when they have successfully completed their onboarding and, per the logic above, are now able to access the application. \[[Learn More](/docs/users/metadata)]

To do this you need:

- A method in your backend to securely update the user `publicMetadata`
- A process in your frontend with logic to collect and submit all the information for onboarding. In this guide you’ll use an example form.

### Add userUpdate method to your backend

First, add a method in your backend, that will be called on form submission and update the user’s `publicMetadata` accordingly. The example below uses the `clerkClient` wrapper to interact with the Backend API.

Under `src/app/onboarding/_actions.ts` add the following code snippet:

```typescript {{ filename: 'src/app/onboarding/_actions.ts' }}
'use server'

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

export const completeOnboarding = async (formData: FormData) => {
  const client = await clerkClient()
  const { userId } = await auth()

  if (!userId) {
    return { message: 'No Logged In User' }
  }

  try {
    await client.users.updateUser(userId, {
      publicMetadata: {
        onboardingComplete: true,
        applicationName: formData.get('applicationName'),
        applicationType: formData.get('applicationType'),
      },
    })
    return { message: 'User metadata Updated' }
  } catch (e) {
    console.log('error', e)
    return { message: 'Error Updating User Metadata' }
  }
}
```

Now that we have a method to securely update our user’s `publicMetadata` we can call this server action from a client side form.

### Add a form to your frontend

With the backend updateUser method in place, we’ll add a basic page that contains a form to complete the onboarding process.

This example form that will capture an application name (applicationName) and application type of either B2C or B2B (applicationType). This is a very loose example — you can use this step to capture information from the user, sync user data to your database, have the user sign up to a course or subscription, or more.

To implement this logic, insert the following into your `src/app/onboarding/page.tsx`:

```tsx {{ filename: 'src/app/onboarding/page.tsx' }}
'use client'

import * as React from 'react'
import { useUser } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import { completeOnboarding } from './_actions'

export default function OnboardingComponent() {
  const { user } = useUser()
  const router = useRouter()

  const handleSubmit = async (formData: FormData) => {
    await completeOnboarding(formData)
    await user?.reload()
    router.push('/dashboard')
  }
  return (
    <div className="px-8 py-12 sm:py-16 md:px-20">
      <div className="mx-auto max-w-sm overflow-hidden rounded-lg bg-white shadow-lg">
        <div className="p-8">
          <h3 className="text-xl font-semibold text-gray-900">Welcome!</h3>
        </div>
        <form action={handleSubmit}>
          <div className="space-y-4 px-8 pb-8">
            <div>
              <label className="block text-sm font-semibold text-gray-700">
                {' '}
                Application Name{' '}
              </label>
              <p className="text-xs text-gray-500">Enter the name of your application.</p>
              <input
                type="text"
                name="applicationName"
                className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
              />
            </div>

            <div>
              <label className="block text-sm font-semibold text-gray-700">Application Type</label>
              <p className="text-xs text-gray-500">Describe the type of your application.</p>
              <input
                type="text"
                name="applicationType"
                className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
              />
            </div>
          </div>
          <div className="bg-gray-50 px-8 py-4">
            <button
              type="submit"
              className="w-full rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
            >
              Submit
            </button>
          </div>
        </form>
      </div>
    </div>
  )
}
```

## Wrap Up

Your onboarding flow is now complete! 🎉 New users who haven’t yet onboarded will now land on your `/onboarding` page and, once they have completed onboarding, will be sent through to the dashboard. By using Clerk, which already handles user authentication, we were able to simplify the process of creating a custom user onboarding flow as well.

---

# Create Your Own Custom User Menu with Radix - Part 2
URL: https://clerk.com/blog/create-custom-user-menu-radix-pt-2.md
Date: 2024-01-29
Category: Guides
Description: Extend your Radix powered custom User Menu to turn it into a Sign In or User Profile component

Code samples are from @clerk/nextjs 4.29.5, @radix-ui/react-dropdown-menu 2.0.6, class-variance-authority 0.7.0 and
@heroicons/react 2.1.1

Welcome to the second part of building a custom user menu! [In the first part,](/blog/create-custom-user-menu-radix) we built a replacement for Clerk’s `<UserButton /> `using the Radix Dropdown primitive and some of Clerk’s hooks. Now we’ll be upgrading our user button with sign-in functionality when the user is not logged in as well as improve the behavior of the component in several ways.

## Refactoring the component

The first step is to refactor the component so it's ready to build upon. Take the contents of the `return` and create a new component in the file above the exported component. Call the new component `<UserButtonAndMenu />` and paste the copied code. We will need to add in the destructures for the user method from the `useUser()` hook, `signOut()`, and `openUserProfile()` from the `userClerk()` hook, and the router method from the `userRouter()` hook.

The first step is to refactor the component so it's ready to build upon.

- Create a new component in the file called `<UserButtonAndMenu />`
- Move the JSX from the original `<UserButton />` into the `<UserButtonAndMenu />` component
- Move `user` method from `useUser()` hook to the new component
- Move `signOut()`, and `openUserProfile()` from `userClerk()` hook to the new component
- Move `router` method from `userRouter()` hook to the new component\\

In the `<UserButton />` component, we’re going to leave the check for `isLoaded` and add a `user.id` check using `if ( !user.id ) {}` and return the `<SignInButton />` component from Clerk if there is no user.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useUser, useClerk, SignInButton } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import Link from 'next/link'

// Create a new UserButtonandMenu component and move the old return into this
const UserButtonAndMenu = () => {
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()
  const { user } = useUser()

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Render a button using the image and email from `user` */}
        <button className="flex flex-row rounded-xl border border-gray-200 bg-white px-4 py-3 text-black drop-shadow-md">
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
            className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-6 py-4 text-black drop-shadow-2xl">
          <DropdownMenu.Label />
          <DropdownMenu.Group className="py-3">
            <DropdownMenu.Item asChild>
              {/* Create a button with an onClick to open the User Profile modal */}
              <button onClick={() => openUserProfile()} className="pb-3">
                Profile
              </button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild>
              {/* Create a fictional link to /subscriptions */}
              <Link href="/subscriptions" passHref className="py-3">
                Subscription
              </Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator className="my-1 h-px bg-gray-500" />
          <DropdownMenu.Item asChild>
            {/* Create a Sign Out button -- signOut() takes a call back where the user is redirected */}
            <button onClick={() => signOut(() => router.push('/'))} className="py-3">
              Sign Out{' '}
            </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}

// Refactor to show the default <SignInButton /> if the user is logged out
// Show the UserButtonAndMenu if the user is logged in
export const UserButton = () => {
  const { isLoaded, user } = useUser()

  if (!isLoaded) return null

  if (!user?.id) return <SignInButton />

  return <UserButtonAndMenu />
}
```

## Improving the component

If you saved your work and tested it, you will see that the refactoring has already accomplished the base goal — it is now both a Sign-In button and a User Button/User Menu. We can still improve the component and provide a better user experience, so let’s do a few more refactors.

The first step is moving the button to its component. We’ll also add in a `forwardRef` to plan for the later improvements.

Tip: You might have your own custom button already created or available from a library you are using. If so then you
can use that in place of this one.

```tsx
// Add import
import * as React from 'react'

// Create a new <Button /> component using the same classes
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ children, className, ...props }, ref) => {
    return (
      <button ref={ref} className={className} {...props}>
        {children}
      </button>
    )
  },
)
```

The second step is to refactor part of the `<UserButtonAndMenu /`> component. We want to take advantage of the new `<Button />`

```tsx
const UserButtonAndMenu = () => {
  const { user } = useUser()
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Swap <button /> to the new <Button /> */}
        <Button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl!}
            width={30}
            height={30}
            className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </Button>
      </DropdownMenu.Trigger>
    </DropdownMenu.Root>

    // rest of the component remains the same
  )
}
```

The third step is to refactor the top-level `<UserButton /> `component to also use the new `<Button />` component. This button will use `openSignIn() `from `useClerk()` to programmatically open the sign-in modal. This results in a custom button and removal of the Clerk `<SignInButton />`

```tsx
// Update @clerk/nextjs imports
import { useUser, useClerk } from '@clerk/nextjs'

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  // Bring in openSignIn
  const { openSignIn } = useClerk()

  if (!isLoaded || !user?.id) {
    /* Use the new <Button /> component for the sign-in button */
    return <Button onClick={() => openSignIn()}>Sign In</Button>
  }

  return <UserButtonAndMenu />
}
```

You can see that we left the user profile image inside of `<UserMenuAndButton />` and passed it to the `<Button />` as a child. Depending on your need you could hoist the image handling into the `<Button />` — that’s totally up to the needs of what you’re building.

## Refining the component

Everything is working nicely at this point, and the structure is in a great place to build on. That said, we can add a few refinements to elevate the user experience. Let’s start by installing an icon package and the class-variance-authority package.

This is using `@heroicons/react` as it's a simple, one-stop icon package. Use other icons that might suit your design
or needs better.

```bash
pnpm install @heroicons/react class-variance-authority
```

With that installed, let’s do another refactor of the Button. We’ll use `class-variance-authority` to expand on what the button can do. This is a very structured approach and provides TypeScript support. We will set up a `variant` and a `size`, use the resulting `primary` and `regular` for the main button, and `menu` and `small` for the buttons in the dropdown menu.

```tsx
// Add imports
import { VariantProps, cva } from 'class-variance-authority'

// Configure the styles for the Button and its variants and sizes
const button = cva(['flex', 'flex-row', 'items-center', 'rounded-xl'], {
  variants: {
    variant: {
      primary: [
        'border',
        'border-gray-200',
        'bg-white',
        'text-black',
        'drop-shadow-md',
        'hover:bg-stone-100',
        'hover:text-stone-800',
      ],
      menu: ['bg-transparent', 'text-gray-800/70', 'hover:text-gray-900'],
    },
    size: {
      regular: ['px-4', 'py-3'],
      small: ['py-3', 'py-2'],
    },
  },
  defaultVariants: {
    variant: 'primary',
    size: 'regular',
  },
})

// Extend the default Button types with props created by create-variance-authority
interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof button> {}

// Create a new <Button /> component using the same classes
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant, size, children, className, ...props }, ref) => {
    return (
      <button
        ref={ref}
        className="flex flex-row rounded-xl border border-gray-200 bg-white px-4 py-3 text-black drop-shadow-md"
        {...props}
      >
        {children}
      </button>
    )
  },
)
```

This `<Button />` component can be used anywhere in your application — if you don’t have a `<Button />` you’ve built
already or one from a library then you can move this out of the `<UserButton />` and instead use it application-wide.

Now that our `<Button />` has reached its final form, let’s import some icons.

```typescript
// Add import
import {
  ArrowRightCircleIcon,
  ArrowRightEndOnRectangleIcon,
  CurrencyDollarIcon,
  UserIcon,
} from '@heroicons/react/24/solid'
```

Let’s modify the button that serves as the trigger for the User Menu. We’ll use Clerk’s `hasImage` value from the `user` return of `useUser()`. Using them will let us display the `UserIcon` we just imported when the user hasn’t set a profile image, but use their image when they have. We will also will move the logic we have for the label for the button up.

```tsx
const UserButtonAndMenu = () => {
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()
  const { user } = useUser()
  // Use the firstname if there is on, otherwise provide a label */
  const label = user?.firstName ? user.firstName : 'Profile'

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <Button>
          {/* Render a button using the image and email from `user` */}
          {user?.hasImage ? (
            <Image
              alt={label}
              src={user?.imageUrl}
              width={30}
              height={30}
              className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
            />
          ) : (
            <>
              {/* Display the icon is there is no profile image */}
              <UserIcon className="mr-2 h-6 w-auto" />
            </>
          )}
          {label}
        </Button>
      </DropdownMenu.Trigger>
    </DropdownMenu.Root>

    // Rest of the component here
  )
}
```

We can use the `ArrowRightCircleIcon` to add a little flare to the Sign-In button.

```tsx
export const UserButton = () => {
  const { isLoaded, user } = useUser()
  const { openSignIn } = useClerk()

  if (!isLoaded) return null

  if (!user?.id) {
    return (
      <Button onClick={() => openSignIn()}>
        Sign In
        {/* Add an icon to the Sign-in button */}
        <ArrowRightCircleIcon className="ml-2 h-6 w-auto" />
      </Button>
    )
  }

  return <UserButtonAndMenu />
}
```

Lastly, we will use `UserIcon`, `ArrowRightEndOnTectangleIcon`, and `CurrencyDollarIcon` to add icons to the drop-down menu. At the same time we will add the variant and size to the buttons so they are using the new configuration.

```tsx
<DropdownMenu.Portal>
  <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-2 py-2 text-black drop-shadow-2xl">
    <DropdownMenu.Label />
    <DropdownMenu.Group className="py-1">
      <DropdownMenu.Item asChild>
        <Button onClick={() => openUserProfile()} className="pb-3" variant="menu" size="small">
          <UserIcon className="mr-2 h-6 w-auto" />
          Profile
        </Button>
      </DropdownMenu.Item>
      <DropdownMenu.Item asChild>
        <Link href="/subscriptions" passHref>
          <Button className="py-2" variant="menu" size="small">
            <CurrencyDollarIcon className="mr-2 h-6 w-auto" />
            Subscription
          </Button>
        </Link>
      </DropdownMenu.Item>
    </DropdownMenu.Group>
    <DropdownMenu.Separator className="my-1 h-px bg-gray-200" />
    <DropdownMenu.Item asChild>
      <Button
        onClick={() => signOut(() => router.push('/'))}
        className="py-3"
        variant="menu"
        size="small"
      >
        <ArrowRightEndOnRectangleIcon className="mr-2 h-5 w-auto" /> Sign Out
      </Button>
    </DropdownMenu.Item>
  </DropdownMenu.Content>
</DropdownMenu.Portal>
```

## Finishing Touches

We can add a few finishing touches to the component to flesh it out some more with a few smaller tweaks and improvements:

- add an `accent` variant, and then use that for the user button.
- give `menu` variant buttons unique styling.
- add `className="min-w-[192px]"` to the sign-in and user button to help give them a more consistent width.
- add `ArrowPathIcon` to the icon import create a button for `!isLoaded`, and give it a loading/spanning state.
- use the `outline-none` class to remove the focus ring from the menu items

```tsx
'use client'

import * as React from 'react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useUser, useClerk } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
import Link from 'next/link'
import {
  ArrowPathIcon,
  ArrowRightCircleIcon,
  ArrowRightEndOnRectangleIcon,
  CurrencyDollarIcon,
  UserIcon,
} from '@heroicons/react/24/solid'
import { VariantProps, cva } from 'class-variance-authority'

const button = cva(['flex', 'flex-row', 'items-center', 'rounded-xl'], {
  variants: {
    variant: {
      primary: [
        'border',
        'border-gray-200',
        'bg-white',
        'text-black',
        'drop-shadow-md',
        'hover:bg-stone-100',
        'hover:text-stone-800',
        'justify-center',
      ],
      accent: [
        'border',
        'border-stone-950',
        'bg-stone-800/70',
        'hover:bg-stone-950',
        'text-stone-200',
        'justify-center',
      ],
      menu: [
        'w-full',
        'justify-start',
        'bg-transparent',
        'hover:bg-stone-800/70',
        'text-gray-800/70',
        'hover:text-stone-100',
        'px-4',
        'rounded-sm',
      ],
    },
    size: {
      regular: ['px-4', 'py-3'],
      small: ['py-3', 'py-2'],
    },
  },
  defaultVariants: {
    variant: 'primary',
    size: 'regular',
  },
})

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof button> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ variant, size, children, className, ...props }, ref) => {
    return (
      <button ref={ref} className={button({ variant, size, className })} {...props}>
        {children}
      </button>
    )
  },
)

// Create a new UserButtonandMenu component and move the old return into this
const UserButtonAndMenu = () => {
  const { user } = useUser()
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()
  const label = user?.firstName ? user.firstName : 'Profile'

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild className="outline-none">
        <Button variant="accent" className="min-w-[192px]">
          {user?.hasImage ? (
            <Image
              alt={label ? label : 'Profile image'}
              src={user?.imageUrl}
              width={30}
              height={30}
              className="mr-2 rounded-full border border-stone-950 drop-shadow-sm"
            />
          ) : (
            <UserIcon className="mr-2 h-6 w-auto" />
          )}
          {label}
        </Button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-2 py-2 text-black drop-shadow-2xl">
          <DropdownMenu.Label />
          <DropdownMenu.Group className="py-1">
            <DropdownMenu.Item asChild className="outline-none">
              <Button
                onClick={() => openUserProfile()}
                className="pb-3"
                variant="menu"
                size="small"
              >
                <UserIcon className="mr-2 h-6 w-auto" />
                Profile
              </Button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild className="outline-none">
              <Link href="/subscriptions" passHref>
                <Button className="py-2" variant="menu" size="small">
                  <CurrencyDollarIcon className="mr-2 h-6 w-auto" />
                  Subscription
                </Button>
              </Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator className="my-1 h-px bg-gray-200" />
          <DropdownMenu.Item asChild className="outline-none">
            <Button
              onClick={() => signOut(() => router.push('/'))}
              className="py-3"
              variant="menu"
              size="small"
            >
              <ArrowRightEndOnRectangleIcon className="mr-2 h-5 w-auto" /> Sign Out
            </Button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  const { openSignIn } = useClerk()

  if (!isLoaded)
    return (
      <Button onClick={() => openSignIn()} className="w-48">
        <ArrowPathIcon className="ml-2 h-6 w-auto animate-spin" />
      </Button>
    )

  if (!user?.id)
    return (
      <Button onClick={() => openSignIn()} className="w-48">
        Sign In
        <ArrowRightCircleIcon className="ml-2 h-6 w-auto" />
      </Button>
    )

  return <UserButtonAndMenu />
}
```

Example repository: [https://github.com/royanger/clerk-custom-user-menu](https://github.com/royanger/clerk-custom-user-menu)

## Explore the Powerful Customization Options Clerk Offers!

With this component you have the building blocks to build out your own user button and menu. Add the new entries to the dropdown that you need for your application, and use the tools provided by Radix and `cva` to design and style your component so it matches your application's design language!

Take a look at our [Custom Flows](/docs/custom-flows/overview) documentation to explore more ways to customize your application using the many hooks and methods Clerks provides. The ability to add the pieces you need from Clerk to fully custom and unique UI provides flexibility to projects.

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless user management starts here!

---

# Introducing Webhook Workflows with Inngest & Svix
URL: https://clerk.com/blog/clerk-inngest-svix-webhooks.md
Date: 2024-01-24
Category: Company
Description: We are excited to announce that Clerk has teamed up with Inngest and Svix to integrate with external systems reliably.

In this post we will dive into how the collaboration between Clerk, [Inngest](https://www.inngest.com), and [Svix](https://www.svix.com) enhances your authentication workflows using Clerk’s new Inngest webhook transformation template.

## Building with Clerk Webhooks

Clerk webhooks (powered by Svix) allow you to receive event notifications from Clerk. When a `user.created` event is triggered, you may want to:

- Sync user data to your database.
- Kick off an account provisioning workflow.
- Start a trial in Stripe.
- Send a welcome email or start a drip campaign.
- Add the user to your product newsletter in Mailchimp.

Today, with Clerk’s new Inngest webhook transformation template, you can easily use Clerk webhook events to trigger Inngest functions.

Inngest is a reliability layer for your application. Using it comes with a few key benefits such as [managing concurrency](https://www.inngest.com/docs/guides/concurrency), [automatic retries](https://www.inngest.com/docs/functions/retries), [parallel execution](https://www.inngest.com/docs/guides/fan-out-jobs), [complex workflows](https://www.inngest.com/docs/reference/functions/step-run), or [task scheduling](https://www.inngest.com/docs/reference/functions/step-sleep). This approach eliminates concerns about operating and scaling webhooks or error handling.

## See it in Action

The code below features the `inngest.createFunction` method, which inserts a new user into the database. It will be triggered whenever a `clerk/user.created` event occurs.

```javascript
const syncUser = inngest.createFunction(
  { id: 'sync-user-from-clerk' },
  { event: 'clerk/user.created' }, // ← This is the function's triggering event
  async ({ event }) => {
    const user = event.data // The event payload's data will be the Clerk User json object
    const { id, first_name, last_name } = user
    const email = user.email_addresses.find((e) => e.id === user.primary_email_address_id).email
    await database.users.insert({ id, email, first_name, last_name })
  },
)
```

You can also have multiple functions react to the same event. The code below uses `step.sleep` to send a welcome email, then wait for three days, then follow up with a message offering a free trial:

```javascript
const sendOnboardingEmails = inngest.createFunction(
  { id: 'onboarding-emails' },
  { event: 'clerk/user.created' },
  async ({ event, step }) => {
    // ← step is available in the handler's arguments
    const { user } = event.data
    const { first_name } = user
    const email = user.email_addresses.find((e) => e.id === user.primary_email_address_id).email

    await step.run('welcome-email', async () => {
      await emails.sendWelcomeEmail({ email, first_name })
    })

    // wait 3 days before second email
    await step.sleep('wait-3-days', '3 days')

    await step.run('trial-offer-email', async () => {
      await emails.sendTrialOfferEmail({ email, first_name })
    })
  },
)
```

To learn how to integrate Inngest into your workflows, check out [Clerk's official integration guide](/docs/integrations/webhooks/inngest) or refer to [Inngest documentation](https://www.inngest.com/docs).

## **Onward and Upward**

As we continue to relentlessly improve the Developer Experience of our products, we are excited to see what developers build using the Inngest integration.

---

# Clerk raises $30M Series B from CRV and Stripe
URL: https://clerk.com/blog/series-b.md
Date: 2024-01-18
Category: Company
Description: New funding will accelerate expansion beyond authentication, into authorization

We’re excited to share that Clerk has raised $30M in Series B funding led by [CRV](https://www.crv.com), with participation from [Stripe](https://stripe.com), and existing investors [Andreessen Horowitz](https://a16z.com) and [Madrona](https://www.madrona.com). We will use this capital to expand our service beyond *authentication*, which identifies who a user is, and into *authorization*, which determines the permissions a user has.

To build our authorization service, we are partnering with Stripe to tightly integrate Stripe Billing. Together, we will create the easiest way to subscribe users to plans and grant access to features based on the plan that was purchased — all with no webhooks required.

> “The `<SignIn/>` and `<UserProfile/>` components from Clerk have already set a new standard in authentication, and proven that components are the new APIs. We’re thrilled to support Clerk as they apply their solutions to the authorization market.” - Reid Christian, General Partner at CRV

Our Series B was led by Reid Christian at CRV, who we’ve been trading notes with for years. Reid and CRV are no strangers to modern developer tools, having invested in Vercel in 2015, during the earliest days of Next.js. We’re thrilled to be working with him for this next phase of Clerk and beyond.

Of course, none of this would be possible without our foundations in authentication, which we’ve been bolstering since 2019. Thanks to the dedication of our team, Clerk has experienced tremendous growth since our [Series A](/blog/series-a) last year, and is now managing over 16 million end-users! And, we’re facilitating over one new sign-up per second across verticals like SaaS, fintech, e-commerce, and more.

Our mission is to simplify the way you keep your users and their data safe, without forcing you to sacrifice high-quality user experience. We’ve been stalwart in our commitment to that mission for authentication, and you can be confident we will maintain our principles as we expand into authorization.

If you want to get in touch, find us on X at [@clerk](https://x.com/clerk). On behalf of the entire Clerk team, thank you so much for your continued support. We can’t wait to continue this journey with all of you, and to show you what we’re building next!

---

# Clerk in 2023: A Year in Review
URL: https://clerk.com/blog/clerk-2023-year-review.md
Date: 2024-01-04
Category: Company
Description: Wow! We had an incredible year in 2023, here is a list of everything we shipped...

This was a exciting year for Clerk, packed with new features and a growing community around Clerk’s user authentication platform. Let's rewind and celebrate some of the milestones that marked our journey!

## January

![Clerk 2023 Year Review tutorial illustration](./95c27737706e26d7bb2a24cd81b1dee3046269d8-2400x1260.png)

### Enhanced DX & Security

As part of Clerk’s mission to revolutionize DX, the focus was on removing roadblocks and streamlining workflows. To enhance security, Clerk implemented several key improvements, including eliminating third-party cookies, which can be used for malicious acts such as cross site scripting. Additionally, Clerk improved API key management for stronger access control; all of this means developers can build more secure applications with ease, using Clerk.

### Upgraded Dashboard & Documentation

Clerk made it easier for developers to find answers and manage their Clerk integrations with a more intuitive interface and comprehensive documentation.

### Gatsby SDK & Community RedwoodJS SDK

Expanded Clerk's compatibility with popular web development frameworks, making it even easier for developers to integrate user authentication into their projects. The major thing to highlight here is Clerk’s growing community and passion for enabling Clerk to work with the tools they love! Learn more in this [changelog post](/changelog/2023-01-27#gatsby-v-5).

### The Popular Starter Template t3-turbo-and-clerk Powered by Clerk

[This open-source template](https://github.com/clerk/t3-turbo-and-clerk) showcases the power of Clerk for building web and mobile apps with robust authentication.

## February

### Improved Next.js Middleware Integration

Clerk’s [new Middleware](/changelog/2023-02-10#next-js-middleware-protection-strategy) allowed for seamless integration with the popular Next.js framework, allowing developers to easily [add user authentication to your Next.js applications](/nextjs-authentication).

### Fastify & RedwoodJS v4 Integrations

Further broadened Clerk's reach by offering official integrations with [Fastify](/docs/quickstarts/fastify#use-clerk-with-fastify) and [RedwoodJS](/docs/quickstarts/redwood#use-clerk-with-redwood-js) frameworks, empowering developers to choose the tools they love.

### Documentation Overhaul

Revamped Clerk's documentation with better organization, search functionality, performance, and authoring experience, making it easier for developers of all levels to learn and use Clerk.

## March

![Clerk 2023 Year Review tutorial illustration](./2d1af91c70bf05d829a3d890a0db78bc4c95b8fd-2400x1260.png)

### $15M Series A Led by Madrona & Clerk.com Domain!

This significant investment and domain move solidified Clerk's position as a leading authentication provider and fueled Clerk’s continued growth. Read more about our Series A in this [announcement post](/blog/series-a).

### SDK Performance Boost through Lazy Loading

Optimized performance by ensuring Clerk's pre-built components only load when needed, preventing initial render time delays and ensuring a smooth user experience.

### Dedicated Chrome Extensions SDK

Opened up possibilities for secure user authentication in the booming Chrome extension market. Check out the [starter repo](https://github.com/clerk/clerk-chrome-extension-starter) on GitHub.

### T3 Stack Tutorial by Theo Browne Showcases Clerk, Vercel & PlanetScale

The [T3 Stack Tutorial](https://www.youtube.com/watch?v=YkOSUVzOAA4\&t) highlighted the seamless integration of Clerk with other popular tools like Vercel and PlanetScale, demonstrating how developers can build powerful and scalable applications with a unified tech stack.

## April

### Clerk Becomes an Identity Provider

Clerk IdP enables large enterprise companies juggling Authentication between all their vendors to create a nexus through Clerk for Single Sign-On functionality.

### Improved Phone & Email Input, Customizable Hosted Pages through Dashboard

Enhanced the user experience with more intuitive input fields and the ability to personalize the look and feel of login and signup pages, boosting brand consistency.

### Full Expo 48 Support

Made Clerk a perfect fit for mobile developers using Expo, a popular framework for building cross-platform apps, and streamlined the creation of custom login flows within Expo apps. Learn more in [this changelog entry](/changelog/2023-04-07#expo-48-support) or explore our [Expo authentication](/expo-authentication) solution.

## May

![Clerk 2023 Year Review tutorial illustration](./50919943d3ffac47193796b588967026f710ae57-2400x1260.png)

### Next.js 13.4 & App Router Support

Clerk swiftly followed the release of Next.js 13.4 and App Router stable by [offering full support](/blog/nextjs-13-4) on May 5th. This made Clerk one of the first development tools and the first authentication provider to fully leverage the power of Next.js, React Server Components, and Edge middleware.

### Enhanced Password Security & UX

May saw a [significant upgrade to password functionalities](/blog/a-new-password-experience) in Clerk applications. Setting and resetting passwords became smooth and user-friendly, while breach detection and complexity tests ensured stronger security.

Developers gained complete control over password security through the Dashboard, allowing them to tailor password policies and requirements to their specific needs.

### Stepping into the Public Spotlight

Clerk's presence in the community exploded in May. We transitioned from sporadic YouTube mentions to sponsoring and presenting at major conferences.

CEO Colin Sidoti's [keynote at Reactathon](https://www.youtube.com/watch?v=enUuBY3HXh4), advocating for the power of well-designed components, and VP of Engineering Sokratis Vidros's talk at CityJS on [building for JavaScript edge runtimes](https://www.youtube.com/watch?v=_ypxvJ0oU-Y), solidified Clerk's position as a thought leader in the industry.

### "How We Roll" Launch

May also marked the launch of the ["How We Roll" blog series](/blog/how-we-roll-passwords), offering a behind-the-scenes look at Clerk's technology and how it delivers a seamless user experience.

### UX Tweaks & Optimization

Recognizing the power of well-crafted UI components, Clerk focused on subtle but impactful UX improvements. Highly customizable user avatars with hover effects and subsequent image size optimizations are prime examples of this dedication to user experience.

## June

### SAML

In June, [SAML support](/docs/authentication/enterprise-connections/overview) was released to public beta, allowing companies to leverage Clerk to seamlessly integrate their internal tools and frontends with their SAML services. Just a few projects leveraging Clerk’s SAML offering include, [Airflip](https://www.airflip.com) a modern tool for procurement teams, [Lawhive](https://lawhive.co.uk) a powerful platform for finding litigators in the U.K., and [Casa](https://clibrain.com) a LLM specialized in usage in Spanish.

### Bot Detection

Clerk’s ownership of the authentication flows allows [enhanced bot detection](/changelog/2023-07-07#enhanced-bot-detection-for-ui-components). Clerk’s hosted pages were retrofitted with layers of bot detection capabilities in June. Clerk also enabled self-service user delete, an important user privacy feature that every app collecting user data should implement.

## July

### Bot Detection in the JavaScript SDK

Clerk’s bot detection capabilities were quickly brought to the JavaScript SDK, including the pre-built components and hooks, ensuring that you can be much more confident about your users not being bots. This capability is a must-have for applications providing free trial or usage credits, user generated content, or social interactions, and comes with Clerk out-of-the-box with no additional configuration required.

### API Improvements for Sorting Users & Organizations

The ownership of user management also comes with a responsibility - user data being stored with Clerk should not become a bottleneck when developing applications, and developers should be able to query the data however they want. July saw improvements to the APIs for querying users and organizations with [advanced filtering and sorting capabilities](/changelog/2023-07-21#filtering-and-sorting-for-users-organizations-members), and better customization of the session token with custom user data.

## August

### Detection for Disposable Emails & Subaddressing

The defenses put in place to fight the bots got even better in August, when Clerk added [detection for disposable emails and subaddressing](/docs/authentication/configuration/restrictions#block-email-subaddresses). These capabilities make Clerk especially useful to AI products for which bots can be very costly.

### Reverse Proxy Support

Another key infrastructure upgrade quickly followed - support for [proxying the requests to Clerk’s Frontend API](/docs/advanced-usage/using-proxies#proxying-the-clerk-frontend-api) through a reverse proxy server!

The reverse proxy support unlocked an entire realm of capabilities, since your application can now be deployed on any domain (preview, staging, tenant subdomains etc) and have full access to authentication, as well as synchronization of signed in sessions.

### “How We Roll” Conclusion

The blog series “How We Roll” closed out with its [10th chapter](/blog/how-we-roll-roundup), rounding up prior chapters and summarizing how Clerk maximizes developer experience, application security, and user experience. The community made some amazing contributions to Clerk’s integrations ecosystem through a Vue, Elysia, and Rust integration, fully open source like all of Clerk’s integrations.

##

## September

![Clerk 2023 Year Review tutorial illustration](./df028ebcab1340139c60bf84ae8aa24b3125b9f7-2400x1260.png)

### Account Portal

September saw the release of Clerk’s [Account Portal](/docs/account-portal/getting-started#getting-started-with-the-account-portal), which replaced hosted pages as the fastest way to authenticate any application, by eliminating the need for the developer to build any authentication related UI at all. The Account Portal was a fresh redesign of the entire user experience, along with better control over the look and feel.

### Custom Domain Registration

Clerk’s B2B offerings continue to get crucial upgrades, like custom domain registration for organizations, allowing users to automatically join organization based on their work email instead of requiring an invite from the admins.

### Form Pre-Fill Ability

The pre-built components for sign in and sign up also received the [ability to pre-fill](/docs/account-portal/direct-links#prefill-sign-in-and-sign-up-fields) the form, making them a lot more flexible and elevating the user experience.

## October

![Clerk 2023 Year Review tutorial illustration](./34c514b138fe70e4ac08798abd3e0f84f691e262-2400x1379.png)

### Changelog & Roadmap

This is getting to be a lot to keep track of, right? We agree, and in October we addressed this through the new changelog and roadmap.

The new [changelog page](/changelog) on [clerk.com](https://clerk.com) provides a one stop hub for all the important releases and announcements, while the [roadmap](https://feedback.clerk.com/roadmap) provides a lot of insight into the ongoing efforts of our product teams and provide any feedback for a new feature or improvements.

### Custom Pages

We also want to make sure as developers scale their products and require user management capabilities that Clerk doesn’t provide, they don’t have to opt out of the pre built components and build completely new UI. [Custom pages](/changelog/2023-10-26) in user and organization profile take their extensibility to the next level.

## November

![Clerk 2023 Year Review tutorial illustration](./913027a390d70b194c9f1bce6fde26240efb031e-2400x1260.png)

### Pricing Overhaul

We want the experience of Clerk to be available to everyone, including developers who don’t have paying users. We solidified our dedication towards the affordability of Clerk with a massive pricing overhaul, which came with 10,000 free MAUs for everyone, with the user’s first day free, and paid add-ons for the advanced authentication, administration, and B2B features. Read our [announcement post](/blog/new-pricing-plans) for further details.

### Hono Adapter

Applications built using Hono, a powerful and lightweight web server known for its compatibility with edge runtimes, also got access to Clerk’s authentication features with an [official adapter](https://github.com/honojs/middleware/tree/main/packages/clerk-auth)!

## December

### Account Lockout

[Account Lockout](/changelog/2023-12-01) is a Clerk feature that enables you to protect your users from brute-force attacks on static credentials such as passwords or backup codes. When enabled it tracks all the attempts and locks down the account after 100, the default attempts, it’s locked for an hour!

### Custom Roles & Permissions

One of our most sought after features of the year, custom roles and permissions, dropped in December, just in time for the holidays. Companies were able to simplify and improve their UX around roles, permissions and access provided to their end users.

As a part of this addition, we've added helper functions and components – `has()`, `protect()` and `<Protect />`. To learn more, read our [announcement post](/blog/introducing-authorization).

### FAPI Reference Docs

Our [Frontend API documentation](/docs/reference/frontend-api) is back and better than ever!

### Fetch User By Activity

Another drop was a new endpoint that allows insight into when a user was last active, this is great for all kinds of UX or admin dashboard implementations! Check out the [changelog entry](/changelog/2023-12-07) to learn more.

## That's A Wrap!

Let's reach new heights in 2024 with Clerk, don't stop the auth conversation! Dive into the nitty-gritty with our technical wizards on [Discord](https://clerk.com/discord). Share your ideas, get expert advice, and join the Clerk community building the future of User Management. Plus, stay in the know with [@clerk](https://x.com/clerk) on X for all the latest releases and sneak peeks. Your seamless journey starts here!

---

# Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk
URL: https://clerk.com/blog/build-movie-emoji-quiz-with-remix-fauna-and-clerk.md
Date: 2024-01-02
Category: Guides
Description: Test the emoji game of all the movie buffs you know by building a Movie Emoji Quiz app with Remix, Fauna, and Clerk.

> \[!WARNING]
> Fauna’s service [ended on May 30, 2025](https://web.archive.org/web/20250319171743/https://fauna.com/blog/the-future-of-fauna), but they’ve [open-sourced their codebase](https://faunadb.org).

## Project setup

A fun challenge coming out of the modern smartphone world is to identify a movie based only on a sequence of emoji. Sometimes the emoji “spell out” the words in the title, while other times they identify key plot themes. Test the emoji game of all the movie buffs you know by building a Movie Emoji Quiz app.

In this tutorial, we will build the app using the Remix full-stack web framework, a Fauna database, and Clerk for authentication and user management. Remix is a relatively new, open-source framework for React that has been gaining traction. Fauna is a developer-friendly, cloud-based database platform that offers modern features, such as real-time streaming and GraphQL. Clerk, an authentication provider built for the modern web, has a first-party [Remix authentication package](/solutions/remix-authentication) and integrates with Fauna through its JWT templates feature.

A brief web search didn't come up with any existing Remix and Fauna tutorials. And then I found this tweet from Ryan Florence ([@ryanflorence](https://twitter.com/ryanflorence)), the co-founder of Remix:

> I really want to build some demos with FaunaDB (I love the direction they're going) and AWS DynamoDB (I mean, come on, it's solid) as well. — Ryan Florence (@ryanflorence) [April 6, 2021](https://twitter.com/ryanflorence/status/1379289891226316801?ref_src=twsrc%5Etfw)

That sold me on moving forward in building with this stack.

If you would like to skip ahead, you can see the [completed
codebase](https://github.com/clerkinc/remix-fauna-tutorial) here.

### Assumptions

This tutorial makes the following assumptions...

- Basic command line usage
- Node.js (≥ v14) installed with `npm` (≥ v7)
- Experience with React components and hooks
- [Clerk](https://dashboard.clerk.com/sign-in) and [Fauna](https://dashboard.fauna.com/accounts/login) accounts already set up (if you haven’t done so, do it now\... we’ll wait)

### Set up a Clerk application

The first step is to create a new application from the Clerk dashboard. We’ll name this application **Movie Emoji Quiz** and leave the default **Password** authentication strategy selected. We’re also going to choose **Google** and **Twitter** as the social login providers, but feel free to select whichever ones you and your friends use.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./0a352efb7eacb52a6221f618c1fcd705126aae31-567x882.png)

Click the **Add application** button and your Clerk development instance will be created.

### Create Remix project

The next step is to create the Remix project. Run the following command in your terminal:

```bash
npx create-remix movie-emoji-quiz
```

It may prompt you to install the create-remix package. Then respond with the following:

```text
? What type of app do you want to create? Just the basics
? Where do you want to deploy? Remix App Server
? Do you want me to run `npm install`? Yes
```

It will then ask “TypeScript or JavaScript?”, we’re going with **JavaScript** on this one, but that’s up to your personal preference.

As the instructions state, change into the app directory with `cd movie-emoji-quiz/`

Now install the two dependencies we need for this application:

```bash
npm install @clerk/remix faunadb
```

Next, touch `.env` to create an environment variables file and replace `<YOUR_FRONTEND_API>` and `<YOUR_CLERK_API_KEY> `with their respective values from your Clerk instance, which you can get from the [API keys](https://dashboard.clerk.com/last-active?path=api-keys) page.

```yaml {{ title: '.env' }}
CLERK_FRONTEND_API=<YOUR_FRONTEND_API>
CLERK_API_KEY=<YOUR_CLERK_API_KEY>
```

Once those environment variables are set, spin up the Remix dev server:

```bash
npm run dev
```

### Set up Clerk authentication

To share authentication state with Remix routes, we need to make three modifications to the `app/root.jsx` file:

1. Export `rootAuthLoader` as `loader`
2. Export `ClerkCatchBoundary` as `CatchBoundary`
3. Wrap the default export with `ClerkApp`

```jsx {{ title: 'app/root.jsx' }}
import { rootAuthLoader } from '@clerk/remix/ssr.server'
import { ClerkApp, ClerkCatchBoundary } from '@clerk/remix'

export const loader = (args) => rootAuthLoader(args) /* 1 */
export const CatchBoundary = ClerkCatchBoundary() /* 2 */

function App() {
  return <html lang="en">{/*...*/}</html>
}

export default ClerkApp(App) /* 3 */
```

Clerk supports Remix SSR out-of-the-box.

### Set up Fauna database

From the Fauna dashboard, create a new database. We named ours **emoji-movie-quiz** and chose the **Classic** Region Group.

We only need to create one Collection for this application. You can do so either in the Dashboard UI or in the interactive query shell.

To do it in the shell, type the following:

```javascript
CreateCollection({ name: 'challenge' })
```

Then press the **Run query** button. If it was successful, you should see similar output.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./e653aadad75fe1c67e048c30db7c942f332dd09f-972x932.png)

We’re going to seed the database with a few examples to get started. If you’ve never used the Fauna Query Language (FQL) before, the syntax may look a little funny.

```javascript
Map(
  [
    { emoji: '🔕🐑🐑🐑', title: 'Silence of the Lambs' },
    { emoji: '💍💍💍💍⚰️', title: 'Four Weddings and a Funeral' },
    { emoji: '🏝🏐', title: 'Castaway' },
    { emoji: '👽📞🏡', title: 'E.T.' },
    { emoji: '👂👀👃👅✋6️⃣', title: 'The Sixth Sense' },
  ],
  Lambda('data', Create(Collection('challenge'), { data: Var('data') })),
)
```

This will map over the examples array and create a new document with challenge data for each item in the Challenge collection.

To learn more about FQL, check out the [Fauna docs](https://docs.fauna.com/fauna/current) and this handy [cheat
sheet](https://docs.fauna.com/fauna/current/api/fql/cheat_sheet).

You can navigate to the **Collections** tab to validate that the data is all set. You can even make edits directly from the user interface if you prefer.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./3ff4a1aba4c5fe26181d70d9257ded3cdccfc59e-2674x1620.png)

While we’re here, let’s navigate over to the **Functions** tab and write a couple FQL functions we will need later.

Click the **New Function** button, name it `getChallenges`, and then paste the following function body:

```javascript
Query(
  Lambda(
    [],
    Map(
      Paginate(Documents(Collection('challenge'))),
      Lambda(
        'challenge',
        Let(
          {
            challengeRef: Get(Var('challenge')),
            data: Select('data', Var('challengeRef')),
            refId: Select(['ref', 'id'], Var('challengeRef')),
          },
          Merge(Var('data'), { id: Var('refId') }),
        ),
      ),
    ),
  ),
)
```

This function will get all the challenges and include the unique document ID inside each data object.

You can test out the functionality in the Shell by running:

```javascript
Call('getChallenges')
```

The second function we’re going to define is called `getChallengeById` and will take the unique document ID parameter and return the respective challenge data or `null` if it doesn’t exist.

```javascript
Query(
  Lambda(
    'id',
    Let(
      {
        challengeRef: Ref(Collection('challenge'), Var('id')),
        exists: Exists(Var('challengeRef')),
        challenge: If(Var('exists'), Get(Var('challengeRef')), null),
      },
      Select('data', Var('challenge'), null),
    ),
  ),
)
```

That’s all of the custom functions we’ll need here.

### Authentication integration

Although Fauna offers built-in identity and basic password authentication, it requires that you manage the user data yourself and does not provide features like prebuilt UI components, `<UserProfile />` access, and other auth strategies such as OAuth social login and [magic links](/blog/magic-links). Clerk provides these features and more without the hassle of managing your own user and identity service.

The Clerk integration with Fauna enables you to authenticate queries to your Fauna database using a JSON Web Token (JWT) created with a JWT template.

From your Clerk dashboard, navigate to the [JWT Templates](https://dashboard.clerk.com/last-active?path=jwt-templates) screen. Click the **New template** button and choose the **Fauna** template.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./46108295ebfce65e7c5446b2abe9e80b8959d5df-1386x1246.png)

Take note of the default template name of fauna (as this will come up later). You can leave the default settings and optionally add your own custom claims using convenient shortcodes.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./7e8a7f7f9821b7cb54e49094faf4e62c84a34e41-1519x1096.png)

While keeping this tab open, go back to the Fauna dashboard and navigate to the **Security** page.

Click on the **Roles** tab and then create a **New Custom Role**. Name this role `user` and give it **Read** and **Create** access to the `challenge` collection as well as **Call** permission for both `getChallenges` and `getChallengeById` functions.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./1c3605a3b7bb4cbf0b12803f1a9004a3db0834f5-1519x1039.png)

If everything looks correct, click **Save** to create the user role.

Next, click on the **Providers** tab and click on the **New Access Provider** button.

Enter `Clerk` as the name to identify this access provider.

We’re going to play a little pattycake back-and-forth between Fauna and Clerk, but bear with me and we’ll get through it together.

1. **Fauna**: Copy the **Audience** URL and go back to the Clerk JWT template tab.
2. **Clerk**: Paste the Audience URL as the value for the `aud` claim.
3. **Clerk**: Copy the **Issuer** URL.
4. **Fauna**: Paste into Issuer field.
5. **Clerk**: Copy the **JWKS Endpoint** URL.
6. **Fauna**: Paste into the JWKS endpoint field.
7. **Fauna**: Select the **user** role for access.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./7cafa54652ac1e960b91ec6faac5cf40746b526b-786x695.png)

After those fields have been set, you can save both the Clerk JWT template and Fauna access provider. Whew! That was fun.

### Add Clerk auth to Remix

Now we can get back into building our application. Open the Remix project in your code editor of choice.

In Remix, `app/root.jsx` wraps your entire application in both server and browser contexts.

Clerk requires a few modifications to this file so the authentication state can be shared with your Remix routes. First, add these imports to `app/root.jsx`:

```jsx
import { rootAuthLoader } from '@clerk/remix/ssr.server'
import { ClerkApp, ClerkCatchBoundary } from '@clerk/remix'
```

Second, export `rootAuthLoader` as `loader` taking in the args parameter.

```javascript
export const loader = (args) => rootAuthLoader(args)
```

Next, we need to export the `ClerkCatchBoundary` as `CatchBoundary` to handle expired authentication tokens. If you want your own custom boundary, you can pass it in as the first argument.

```javascript
export const CatchBoundary = ClerkCatchBoundary()
```

And finally, we need to wrap the default export with the `ClerkApp` higher-order component.

```jsx
export default ClerkApp(function App() {
  return <html lang="en">{/*[...]*/}</html>
})
```

That’s all that’s needed to install and configure Clerk for authentication. The next step will include adding sign in functionality.

### Add SignIn to index route

We’re going to use the Clerk hosted `<SignIn />` component to render the sign in form.

Update `app/routes/index.jsx` with the following:

```jsx {{ title: 'app/routes/index.jsx' }}
import { SignIn } from '@clerk/remix'
import styles from '~/styles/index.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

export default function Index() {
  return (
    <div>
      <main>
        <div className="content">
          <SignIn />
        </div>
      </main>
    </div>
  )
}
```

We’re using Remix’s [route styles](https://remix.run/docs/en/v1/guides/styling#route-styles) functionality to dynamically add a stylesheet to this route.

Create a `styles` directory inside of the `app` folder and save the following as `index.css`:

app/styles/index.css

```css
@font-face {
  font-family: 'color-emoji';
  src:
    local('Apple Color Emoji'), local('Segoe UI Emoji'), local('Segoe UI Symbol'),
    local('Noto Color Emoji');
}

:root {
  --font-body:
    'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif,
    color-emoji;
  --color-error: #c10500;
  --color-success: #15750b;
}

body {
  margin: 0;
  font-family: var(--font-body);
}

header {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  background-color: #fd890f;
  box-shadow: 0 2px 2px 1px rgba(0, 0, 0, 0.2);
}

.actions {
  display: flex;
  align-items: center;
}

.actions > a {
  margin: 0 20px;
  color: #fff;
  font-size: 14px;
  text-decoration: none;
}

.actions > a:hover,
.actions > a:focus {
  color: rgba(255, 255, 255, 0.8);
}

.logo {
  color: #fff;
  font-size: 22px;
  font-weight: 600;
}

main {
  display: flex;
  height: calc(100vh - 56px);
}

aside {
  width: 280px;
  height: 100%;
  overflow-y: scroll;
  flex-shrink: 0;
  background-color: #f5f5f4;
  border-right: 1px solid #d8d8d4;
}

aside h2 {
  margin: 20px 20px 10px;
  font-size: 18px;
}

aside ul {
  list-style: none;
  padding: 0;
}

aside li {
  font-size: 28px;
}

aside li:not(:last-child) {
  border-bottom: 1px solid #d8d8d4;
}

aside li > a {
  display: block;
  padding: 8px 20px;
  text-decoration: none;
}

aside li > a:hover,
aside li > a:focus,
aside li > a.active {
  background-color: #ececea;
}

.content {
  width: 100%;
  height: calc(100vh - 40px);
  padding: 40px 20px 0;
  background-color: #e5e6e4;
  text-align: center;
}

.content h1 {
  margin-bottom: 8px;
}

@font-face {
  font-family: 'color-emoji';
  src:
    local('Apple Color Emoji'), local('Segoe UI Emoji'), local('Segoe UI Symbol'),
    local('Noto Color Emoji');
}

:root {
  --font-body:
    'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif,
    color-emoji;
  --color-error: #c10500;
  --color-success: #15750b;
}

body {
  margin: 0;
  font-family: var(--font-body);
}

header {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 20px;
  background-color: #fd890f;
  box-shadow: 0 2px 2px 1px rgba(0, 0, 0, 0.2);
}

.actions {
  display: flex;
  align-items: center;
}

.actions > a {
  margin: 0 20px;
  color: #fff;
  font-size: 14px;
  text-decoration: none;
}

.actions > a:hover,
.actions > a:focus {
  color: rgba(255, 255, 255, 0.8);
}

.logo {
  color: #fff;
  font-size: 22px;
  font-weight: 600;
}

main {
  display: flex;
  height: calc(100vh - 56px);
}

aside {
  width: 280px;
  height: 100%;
  overflow-y: scroll;
  flex-shrink: 0;
  background-color: #f5f5f4;
  border-right: 1px solid #d8d8d4;
}

aside h2 {
  margin: 20px 20px 10px;
  font-size: 18px;
}

aside ul {
  list-style: none;
  padding: 0;
}

aside li {
  font-size: 28px;
}

aside li:not(:last-child) {
  border-bottom: 1px solid #d8d8d4;
}

aside li > a {
  display: block;
  padding: 8px 20px;
  text-decoration: none;
}

aside li > a:hover,
aside li > a:focus,
aside li > a.active {
  background-color: #ececea;
}

.content {
  width: 100%;
  padding: 40px 20px 0;
  background-color: #e5e6e4;
  text-align: center;
}

.content h1 {
  margin-bottom: 8px;
}

.emoji {
  display: block;
  font-size: 80px;
  line-height: 1.2;
  margin: 20px auto 10px;
}

.error > p {
  color: var(--color-error);
  font-size: 18px;
}
```

Your app should now look something like:

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./dc28c45c9b45ff09ce76b659a41a7841ddf9e1a2-2258x1618.png)

If you see a blank screen, you may be already signed in. We will handle that case momentarily.

Inside of the `app` directory, create a folder called `components` and a file called `header.jsx`.

Drop in the following code:

```jsx {{ title: 'app/components/header.jsx' }}
import { SignedIn, UserButton } from '@clerk/remix'

export default function Header() {
  return (
    <header>
      <span className="logo">Movie Emoji Quiz</span>
      <SignedIn>
        <UserButton />
      </SignedIn>
    </header>
  )
}
```

Here we’re making use of the `<SignedIn>` control flow component and the `<UserButton />` which will allow us to edit our profile and sign out.

Go back to `app/routes/index.jsx` and import the `<Header />` component and add it just above the `<main>` element:

```jsx {{ title: 'app/routes/index.jsx' }}
import Header from '../components/header'

export default function Index() {
  return (
    <div>
      <Header />
      <main>
        <div className="content">
          <SignIn />
        </div>
      </main>
    </div>
  )
}
```

If you sign in, you should now see your avatar. Click on it to see the user profile menu.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./4b528fa3e84cf5949c2047dfa5bfba9e13a0186f-1988x1488.png)

You can now sign in, sign out, and manage your account. Clerk makes it super easy with their hosted components.

### Authenticate with the Fauna client

In order to start fetching data from our Fauna database, we need to set up the Fauna client.

Create a `utils` folder inside of `app` and a file named `db.server.js`.

The `.server` naming convention is a hint to the compiler to ignore this file in the browser bundle.

Add the following code:

```javascript {{ title: 'app/utils/db.server.js' }}
import { getAuth } from '@clerk/remix/ssr.server'
import faunadb from 'faunadb'

export const getClient = async (request) => {
  const { userId, getToken } = await getAuth(request)

  if (!userId) {
    return null
  }

  const secret = await getToken({ template: 'fauna' })

  return new faunadb.Client({ secret })
}

export const q = faunadb.query
```

Here we are using the `getAuth` function from Clerk to check if we have a `userId` (e.g. if the user is signed in) and to get access to the `getToken` function, which when called with our Fauna JWT template name (it was simply “fauna” if you remember), will be passed to the Fauna client as the authentication secret.

If the user is signed in and we get access to the Fauna JWT template, we should then be able to make queries against our Fauna database.

We are also exporting `q` here, which is a convention when using `faunadb.query`. This way all our database helper functions are kept in the same place.

## Display movie challenges

Now it’s time to display some of these challenges.

First let’s create a new route at `/challenges`. We do this by creating a file `app/routes/challenges.jsx` and populating it with the following:

```jsx {{ title: 'app/routes/challenges.jsx' }}
import { Outlet } from '@remix-run/react'
import Header from '../components/header'
import styles from '~/styles/index.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

export default function Challenges() {
  return (
    <div>
      <Header />
      <main>
        <div className="content">
          <Outlet />
        </div>
      </main>
    </div>
  )
}
```

This should look the same as the index route, with the exception being `<SignIn />` is replaced with the Remix `<Outlet />` component.

Next, import the database utils we previous created and export a `loader` function with the below code:

```javascript
import { getClient, q } from '../utils/db.server'

export const loader = async ({ request }) => {
  const client = await getClient(request)

  if (!client) {
    return null
  }

  const response = await client.query(q.Call('getChallenges'))

  // Check your terminal for response data
  console.log(response)

  return json(response)
}
```

You can see we’re using the Fauna client to perform a FQL query to call the `getChallenges` function we created. If all went well, check your terminal window (not the browser console) and you should see the response data.

Create a new file `app/components/sidebar.jsx` that will loop over this data and create links to each challenge based on its ID.

```jsx {{ title: 'app/components/sidebar.jsx' }}
import { NavLink } from '@remix-run/react'

export default function Sidebar({ data }) {
  return (
    <aside>
      <h2>Guess these movies...</h2>
      <ul>
        {data?.map((movie) => (
          <li key={movie.id}>
            <NavLink to={`/challenges/${movie.id}`}>{movie.emoji}</NavLink>
          </li>
        ))}
      </ul>
    </aside>
  )
}
```

Import the `<Sidebar />` component in the index route directly under the `main` element.

To access the data from the loader, we will use the aptly named `useLoaderData` hook from Remix and pass the `data` property to the `Sidebar` component:

```jsx
export default function Challenges() {
  const { data } = useLoaderData()

  return (
    <div>
      <Header />
      <main>
        <Sidebar data={data} />
        <div className="content">
          <Outlet />
        </div>
      </main>
    </div>
  )
}
```

If you visit [http://localhost:3000/challenges](http://localhost:3000/challenges), you should now see the emoji challenges rendered as links in the sidebar:

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./22b986b5e553c075663f766a040df434cfde6167-2192x1488.png)

If you click on the links, they will cause an error because we haven’t created those individual challenge routes. Let’s do that now.

### Create challenge route

Let’s create a new stylesheet that will hold the challenge styles at `app/styles/challenge.css`.

app/styles/index.css

```css
.emoji {
  display: block;
  font-size: 80px;
  line-height: 1.2;
  margin: 20px auto 10px;
}

.author {
  padding-bottom: 10px;
  color: #8a8a8a;
  font-size: 14px;
}

form {
  display: flex;
  flex-flow: column wrap;
  align-items: center;
  justify-content: center;
  margin-top: 20px;
}

label {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 20px;
}

input[type='text'] {
  min-width: 280px;
  padding: 8px;
  border: 1px solid #ccc;
  font-family: var(--font-body);
  font-size: 16px;
}

.submit-btn {
  appearance: none;
  margin-top: 40px;
  padding: 12px 24px;
  background-color: #7180ac;
  border: 1px solid #6575a4;
  border-radius: 4px;
  cursor: pointer;
  color: #fff;
  font-family: var(--font-body);
  font-size: 14px;
}

.submit-btn:hover,
.submit-btn:focus {
  background-color: #6575a4;
}

.form-field {
  display: flex;
  align-items: baseline;
  margin-top: 20px;
  text-align: left;
}

.form-field label {
  display: block;
  min-width: 50px;
  margin: 0 16px 0 0;
}

.form-validation-error {
  color: var(--color-error);
  font-size: 14px;
  margin-top: 4px;
  margin-bottom: 0;
}

.message {
  margin: 8px 0 0;
  font-size: 16px;
}

.message--correct {
  color: var(--color-success);
}

.message--incorrect {
  color: var(--color-error);
}

.reveal {
  position: relative;
}

.reveal-btn {
  position: relative;
  z-index: 2;
  appearance: none;
  width: 300px;
  margin: 24px auto;
  padding: 16px 24px;
  background: #f5f5f4;
  border: 1px solid #d8d8d4;
  transition: opacity 1.5s ease;
  cursor: help;
  font-family: var(--font-body);
  font-size: 14px;
}

.reveal-btn:hover {
  opacity: 0;
}

.reveal-text {
  position: absolute;
  top: 40px;
  left: 0;
  width: 100%;
  font-size: 14px;
}
```

Next, create a file inside a folder at the path `app/routes/challenges/$id.jsx`

The `$` prefix is important here as it creates a [dynamic segment](https://remix.run/docs/en/v1/guides/routing#dynamic-segments).

Add the following code:

```jsx {{ title: 'app/routes/challenges/$id.jsx' }}
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { getClient, q } from '../../utils/db.server'
import styles from '~/styles/challenge.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

export const loader = async ({ params, request }) => {
  const client = await getClient(request)

  if (isNaN(params.id)) {
    throw new Response('Challenge not found', {
      status: 404,
    })
  }

  const challenge = await client.query(q.Call('getChallengeById', params.id))

  if (!challenge) {
    throw new Response('Challenge not found', {
      status: 404,
    })
  }

  return json(challenge)
}

export default function Challenge() {
  const { emoji } = useLoaderData()

  return (
    <div>
      <span className="emoji">{emoji}</span>
    </div>
  )
}
```

This code follows a similar pattern to what we did in the sidebar with the `loader` and the `useLoaderData` hook. The difference here is we’re calling the `getChallengeById` FQL function and passing it the `id` parameter, which comes from the `$id` dynamic route.

We are also using a Remix convention of throwing `Response` objects for error scenarios (e.g. invalid ID, challenge not found).

If you click on the links in the sidebar, you should now see each emoji challenge rendered in its full glory (a large type size).

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./91eb7782b4592ebde7e96f1c0023f4fea6e576d5-2090x1572.png)

### Challenge form

Now it’s time to add the form to submit guesses for the emoji challenges. Form handling is one area where Remix really shines.

Add the following form markup below the emoji:

```jsx
<form method="post" autoComplete="off">
  <label htmlFor="guess">What movie is this?</label>
  <input id="guess" type="text" name="guess" placeholder="Enter movie title..." required />
  <button className="submit-btn">Submit guess</button>
</form>
```

This is pretty standard JSX form markup. Nothing too fancy going on here.

Let’s import our DB utils as well as the `json` Response helper from Remix.

```javascript
import { json } from '@remix-run/node'
import { getClient, q } from '~/utils/db.server'
```

Then export the following action function:

```javascript
export const action = async ({ params, request }) => {
  const form = await request.formData()
  const guess = form.get('guess')
  const client = await getClient(request)
  const challenge = await client.query(q.Call('getChallengeById', params.id))
  const isCorrect = guess.toLowerCase() === challenge.title.toLowerCase()

  return json({
    guessed: isCorrect ? 'correct' : 'incorrect',
    message: isCorrect ? 'Correct! ✅' : 'Incorrect! ❌',
    answer: challenge.title,
  })
}
```

Here we’re using native [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) methods to read the guess input and comparing it against the challenge title we get by calling our FQL `getChallengeById` function with the challenge ID from the route params.

Based on whether the guess matches the title (case insensitive), we return an appropriate JSON response. We can access the action data using the `useActionData` hook (also aptly named).

```javascript
import { useActionData, useLoaderData } from '@remix-run/react'
```

Now we can update the UI to display the correct message based on the guess submitted.

```jsx
export default function Challenge() {
  const { emoji } = useLoaderData()
  const data = useActionData()

  return (
    <div>
      <span className="emoji">{emoji}</span>
      <form method="post" autoComplete="off">
        <label htmlFor="guess">What movie is this?</label>
        <input id="guess" type="text" name="guess" placeholder="Enter movie title..." required />
        {data?.guessed ? (
          <p className={`message message--${data.guessed}`}>{data.message}</p>
        ) : null}
        <button className="submit-btn">Submit guess</button>
        {data?.guessed === 'incorrect' ? (
          <div className="reveal">
            <button className="reveal-btn" type="button">
              Reveal answer
            </button>
            <span className="reveal-text">{data?.answer}</span>
          </div>
        ) : null}
      </form>
    </div>
  )
}
```

If an incorrect guess is submit, we provide the user a way to reveal the answer.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./125715f68cfbb3512476f88b2e28ef96d0154475-2090x1572.png)

The form should now work to submit correct and incorrect guesses.

### Handling transitions

Because the form is shared between different challenge routes, if you enter text in one input and go to another challenge, you will see the input value is not being cleared.

We can fix this by using the `useTransition` hook, putting a ref on the form element, and then resetting the form when the transition is a normal page load.

```jsx
export default function Challenge() {
  const { emoji } = useLoaderData()
  const data = useActionData()
  const transition = useTransition()
  const ref = useRef()

  useEffect(() => {
    if (transition.type == 'normalLoad') {
      // Reset form on route change
      ref.current && ref.current.reset()
    }
  }, [transition])

  return (
    <div>
      <span className="emoji">{emoji}</span>
      <form ref={ref} method="post" autoComplete="off">
        <label htmlFor="guess">What movie is this?</label>
        <input id="guess" type="text" name="guess" placeholder="Enter movie title..." required />
        {data?.guessed ? (
          <p className={`message message--${data.guessed}`}>{data.message}</p>
        ) : null}
        <button className="submit-btn">Submit guess</button>
        {data?.guessed === 'incorrect' ? (
          <div className="reveal">
            <button className="reveal-btn" type="button">
              Reveal answer
            </button>
            <span className="reveal-text">{data?.answer}</span>
          </div>
        ) : null}
      </form>
    </div>
  )
}
```

You will need to add the necessary hook imports from React and Remix.

```javascript
import { useEffect, useRef } from 'react'
import { useActionData, useLoaderData, useTransition } from '@remix-run/react'
```

After that is added, the form should clear when choosing a different challenge.

## Submit new challenges

This app is not much so much fun if users can’t submit their own movie emoji challenges. So that’s the functionality we’re going to add now.

As with most things in Remix, the first thing we need to do is create a new route.

Create `app/routes/challenges/new.jsx` with the following:

```jsx {{ title: 'app/routes/challenges/new.jsx' }}
import { getAuth } from '@clerk/remix/ssr.server'
import { json, redirect } from '@remix-run/node'
import { Form, useActionData } from '@remix-run/react'
import { getClient, q } from '~/utils/db.server'
import styles from '~/styles/challenge.css'

export const links = () => {
  return [{ rel: 'stylesheet', href: styles }]
}

const badRequest = (data) => json(data, { status: 400 })

const validateEmoji = (emoji) =>
  !emoji.trim() || /\p{L}|\p{N}(?!\uFE0F)|\p{Z}/gu.test(emoji)
    ? 'Please enter only emoji'
    : undefined

const validateTitle = (title) =>
  title && title.length > 1 ? undefined : 'Please enter a movie title'

export const action = async ({ request }) => {
  const form = await request.formData()
  const emoji = form.get('emoji')
  const title = form.get('title')

  if (typeof emoji !== 'string' || typeof title !== 'string') {
    return badRequest({
      formError: 'Form not submitted correctly.',
    })
  }

  const fieldErrors = {
    emoji: validateEmoji(emoji),
    title: validateTitle(title),
  }

  if (Object.values(fieldErrors).some(Boolean)) {
    return badRequest({
      fieldErrors,
      fieldValues: {
        emoji,
        title,
      },
    })
  }

  const { userId } = await getAuth(request)
  const client = await getClient(request)
  const data = {
    emoji,
    title,
    userId,
  }

  const response = await client.query(q.Create('challenge', { data }))

  return redirect(`/challenges/${response.ref.value.id}`)
}

export default function NewRoute() {
  const actionData = useActionData()

  return (
    <div>
      <h1>Create new challenge</h1>
      <Form method="post" autoComplete="off">
        <div className="form-field">
          <label htmlFor="emoji">Emoji</label>
          <input id="emoji" type="text" name="emoji" />
        </div>
        {actionData?.fieldErrors?.emoji ? (
          <p className="form-validation-error" role="alert" id="name-error">
            {actionData.fieldErrors.emoji}
          </p>
        ) : null}
        <div className="form-field">
          <label htmlFor="title">Movie</label>
          <input id="title" type="text" name="title" />
        </div>
        {actionData?.fieldErrors?.title ? (
          <p className="form-validation-error" role="alert" id="name-error">
            {actionData.fieldErrors.title}
          </p>
        ) : null}
        {actionData?.formError ? (
          <p className="form-validation-error" role="alert">
            {actionData.formError}
          </p>
        ) : null}
        <button className="submit-btn">Submit challenge</button>
      </Form>
    </div>
  )
}
```

This time we’re using the `Form` component provided by Remix, which helps with automatically serializing the values. There’s validation logic to check for valid emoji and titles. If the validation passes, we create a new challenge. We use the `getAuth` function from Clerk to send in the user ID along with the emoji and title. These values form the data for a new challenge document in Fauna. On a successful document creation, the user is redirected to the new challenge page.

Let’s add a link to the header so we can get to this page. It’s wrapped with `<div className="actions">` to provide the necessary styling.

```jsx {{ title: 'app/components/header.jsx' }}
import { SignedIn, UserButton } from '@clerk/remix'
import { Link } from '@remix-run/react'

export default function Header() {
  return (
    <header>
      <span className="logo">Movie Emoji Quiz</span>
      <SignedIn>
        <div className="actions">
          <Link to="/challenges/new">Submit challenge</Link>
          <UserButton />
        </div>
      </SignedIn>
    </header>
  )
}
```

Clicking the link should take you to the page where you can create a new challenge. You should see validation errors if you don’t fill out the form properly.

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./96ba5c89aa2784720ee94206c12557645c742168-2482x1550.png)

Once submitted, you will be redirected to the new challenge page. Because the action created a new mutation, Remix will automatically update the data in the sidebar. Neat!

![Build Movie Emoji Quiz With Remix Fauna And Clerk guide illustration](./0a87785674c2ec965c2c6b54bcf92e8a8e98f967-2482x1550.png)

You now have a working Movie Emoji Quiz app. If you’re ready to share it with your family and friends, Remix makes deployment very easy and has support for various [deployment targets](https://remix.run/docs/en/v1/guides/deployment). Using the [Vercel CLI,](https://vercel.com/cli) all you need to deploy your Remix app is run:

```bash
npm i -g vercel
vercel
```

And your app will be live within minutes!

## Next steps

To take this app even further, you can do the following:

- Add [catch boundaries](https://remix.run/docs/en/v1/guides/not-found) and redirects to handle error cases and invalid challenge routes
- Create Remix routes for `/sign-in` and `/sign-out` to use [mounted Clerk components](/docs/references/javascript/clerk/sign-in#sign-in-component)
- Use the [Clerk User API](/docs/reference/backend-api) to query the username for each user user-submitted challenge so you can give them credit for their cleverness
- Keep track of correct guesses and display an indicator in the UI so the user knows which challenges they have yet to tackle

If you enjoyed this tutorial or have any questions, feel free to reach out to me ([**@devchampian**](https://x.com/devchampian)) on X, follow [**@clerk**](https://x.com/clerk), or join our [**Discord community**](https://clerk.com/discord) to connect with other developers. Happy coding!

---

# Ultimate Guide to Magic Link Authentication
URL: https://clerk.com/blog/magic-links.md
Date: 2023-12-20
Category: Guides
Description: In this post, we discuss the benefits of email magic links, show examples of how they work, and explain why they meet the requirements for secure, passwordless authentication.

Data breaches and password overload have made companies and their users wary of using a traditional username/password authentication system. Companies know that handling user passwords is both technically challenging ***and*** costly, as it requires stringent security measures, robust infrastructure for storage, and continuous monitoring to prevent unauthorized access. Users find it cumbersome to manage multiple complex passwords for various accounts, especially with the prevalence of methods like SSO, and often end up reusing passwords across different platforms, as a matter of convenience; this not only leaves the individual vulnerable, but also makes companies jobs of securing data that much harder, highlighting the critical requirements for improved security measures.

Luckily, email magic links have emerged as an elegant solution to secure user sessions in a [passwordless](/docs/authentication/configuration/sign-up-sign-in-options#authentication-strategy) context. Their rising popularity is underpinned by their dual advantages:

- An augmented security posture through the minimization of phishing opportunities and password theft.
- An optimized user experience devoid of the need to memorize and manage many login credentials.

## What are Magic Links?

Magic links are a token-based authentication (TBA) strategy that uses a unique, time-sensitive URL, which leverages a securely-generated token to serve as a credential for user authentication.

The links are sent directly to the user's registered email or phone number, providing a straightforward, secure authentication method. When a user clicks on a magic link, the embedded token is validated against the server to authenticate the user's identity. This process, by design, eliminates the traditional risks associated with password-based systems and simplifies the login experience for the user.

In this guide, we will show you why you should consider authentication with magic links and how they work at a high level, before going through [a Next.js App Router implementation](/blog/secure-authentication-nextjs-email-magic-links) to show how you can add magic links to your application.

## The Benefits of Magic Links

Magic links bolster the security architecture of authentication systems by adopting a token-based, stateless interaction model. Each link is cryptographically unique and typically accompanied by an expiration timestamp, making it resilient against replay attacks. Given their ephemeral nature, even if a magic link were to be intercepted or exposed, its short-lived validity constrains the window of opportunity for malicious exploitation.

But there are a few other benefits to magic links for companies and users:

- **Streamlined user experience**. Authentication with magic links eliminate the cognitive burden of remembering and managing many complex passwords, by providing a single-click authentication pathway. This frictionless access modality can enhance user engagement and satisfaction, reducing abandonment rates during the sign-in or sign-up processes.
- **Compliance and data protection**. By using magic links to minimize passwords, organizations can reduce the risk of breaches involving personal data and avoid the consequences of compromised credentials, which can include heavy fines and reputational damage.
- **Reduction in credential exposure**. With magic links, the threat of credential stuffing attacks—where compromised credentials are used to gain unauthorized access to multiple user accounts—is mitigated. Since magic links do not rely on reusable passwords, the standard vector of credential exposure is eliminated, enhancing overall system security.
- **Improved accessibility**. For users with disabilities or those less familiar with technology, magic links offer a more accessible authentication mode. The simplification of the login process—replacing typing and memory demands with a single action—can be particularly advantageous for individuals facing physical or cognitive challenges.

## How Magic Links Work

A magic link is structurally composed of two critical elements: the URL, which provides the link for the user's web interaction, and the embedded token, a cryptographically-generated string serving as the temporary credential.

In essence, a typical magic link may resemble the following structure:

`https://example.com/authenticate?token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6`

In this example, the `token` query parameter carries the weight of authentication, substantiating the user's claim without revealing identity until verified by the server.

Here’s an example of how the process works:

![Example Magic Link Authentication Process](./387a0a12d7a188ea6c83f3b15c54bf37e63e6462-2400x1392.png)

### Token Validity

Magic links are designed to become invalid under certain conditions for security purposes:

- **Expiration**: The token embedded in the link has a short lifespan and is often configurable based on application security requirements.
- **Usage Limitation**: Once a magic link is used, it becomes invalid, preventing multiple uses, which could lead to unauthorized access.
- **Revocation**: The system can programmatically revoke a token, thus invalidating the magic link in response to specific triggers or anomalies.

### Token Generation

The lifecycle of a magic link commences with the generation of a unique token. This process employs cryptographic algorithms to ensure each token is a random, high-entropy string, making it virtually impossible to predict or reproduce through brute force or other cryptographic attacks. Typically, the token generation utilizes [HMAC](https://en.wikipedia.org/wiki/HMAC) or AES combined with a [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) to guarantee the robustness of the token against collision and preimage attacks.

Once generated, the token is stored on the server along with metadata that includes the user's identifier, the token's expiration time, and any other relevant session data. The magic link is then composed by appending the token to a predetermined URL structure, forming a complete, ready-to-use hyperlink.

This link is dispatched to the user's email address or phone number via SMTP or SMS protocols. The communication channel must be secure, leveraging TLS for email and similarly secure protocols for SMS to safeguard the link during transit.

### User Interaction and Verification

A user typically interacts with the magic link by clicking on it, which initiates a secure request to the service's endpoint. The service extracts the token from the URL and verifies it against the stored data. This verification process involves several checks:

- **Authenticity**: Validates that the token matches a recently generated and stored token.
- **Integrity**: Ensures the token has not been tampered with during transit.
- **Timeliness**: Confirms the token has not expired based on the predefined validity period.
- **Non-reuse**: Ensures the token has not been used before, adhering to the principle of one-time use.

The server considers the authentication request legitimate if the token passes these checks.

### Session Initialization

Post-verification, the server establishes a session for the user. This session is typically stateless, with a new session token or cookie generated to maintain the user's authenticated state in the application. This session token is separate from the magic link token. It has its own security considerations, such as being HttpOnly and Secure, to prevent access via client-side scripts and ensure transmission over HTTPS only.

## Building a Magic Link System

Let’s create our own email with magic link authentication. In this example, we’ll use [Next.js](https://nextjs.org) 13 with the App Router. We’ll also use [Supabase](https://supabase.com) for our backend database to store users and tokens.

First, let’s create a new Next.js project:

```sh
npx create-next-app@latest
```

Follow the prompts to select how you want to configure your app, but be sure to select “Yes” for “Would you like to
use App Router? (recommended)”.

Once you have created and configured your project, `cd` into the created directory and run `npm run dev` to start it. You’ll see just the default Next.js homepage when you load `localhost:3000`.

Before we start building out our project, we need to install a few dependencies that we’ll use. Install them using:

```sh
npm install nodemailer jsonwebtoken @supabase/supabase-js
```

What do these do?

- [nodemailer](https://www.npmjs.com/package/nodemailer): A library that allows for easy email sending.
- [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken): A library to implement JSON Web Tokens for secure data transmission.
- [@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js): The Supabase client library for Javascript.

With those installed, let’s open up the project in an IDE. In total, we’re going to have two pages, two API routes, and two helper libraries:

- `page.js` will be our homepage. It will have a simple email field, and will call our `requestMagicLink` API reroute.
- `requestMagicLink.js` will be the API route that will create our magic link, send it to the email address passed from `page.js`, and save the token on our Supabase database.
- `verify.js` will be the API route called when the user clicks on the magic link in their email. It will verify the token and redirect the user to the protected dashboard page.
- `dashboard/page.js` will be a simple mock “protected page” (that would require the user to be logged in to view it).
- `lib/database.js` will be a number of database helper functions to save, load, and delete Supabase data.
- `lib/supabaseClient.js` will set up our Supabase client.

### page.js

Let’s start with what the user will see, `page.js`:

```jsx
// app/page.js
'use client'
import { useState } from 'react'

export default function RequestMagicLink() {
  const [email, setEmail] = useState('')
  const [message, setMessage] = useState('')
  const [isLoading, setIsLoading] = useState(false)

  const handleSubmit = async (event) => {
    event.preventDefault()
    setIsLoading(true)
    setMessage('')

    try {
      const response = await fetch('/api/auth/requestMagicLink', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
      })

      const data = await response.json()

      if (response.ok) {
        setMessage('Magic link sent! Check your email to log in.')
      } else {
        setMessage(data.error || 'An error occurred. Please try again.')
      }
    } catch (error) {
      setMessage('An error occurred. Please try again.')
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
        required
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Sending...' : 'Send Magic Link'}
      </button>
      {message && <p>{message}</p>}
    </form>
  )
}
```

In the code example above, we are requesting the `RequestMagicLink` component, which is responsible for handling the functionality of requesting a magic link via an email address. This component provides a UI for users to request a magic link. Users enter their email and submit the form, triggering a request to the server. Feedback is given to the user through messages and button state changes during the process.

First, we set up the state variables for the page. In the Next.js App Router, you can only use `useState` in client-side components. As all components default to server-side, we have to use the `“use client”` directive at the top of the file to show this page has to be rendered on the client. We have three state variables:

- `email`: Stores the user's email address. It's updated every time the user types into the email input field.
- `message`: Used to display messages to the user, like confirmation or error messages.
- `isLoading`: Indicates whether the request is being processed. It's used to disable the submit button and change the button text while the request is in progress.

After that, we have the `handleSubmit` function. This is triggered when the form is submitted, and initially prevents the default form submission action with `event.preventDefault()`. It then sets `isLoading` to `true` to indicate the start of an asynchronous operation and clears any previous messages stored in `message`.

Then, comes the core part of the component. We make an async `POST` request to the `/api/auth/requestMagicLink` endpoint with the user's email in the request body. We then update the `message` state based on the success or failure of the request:

- If the request is successful (`response.ok` is true), it sets a success message.
- If the request fails, it displays an error message, either from the response or a generic error message.

We have some basic error catching, and then set `isLoading` to `false`.

The actual form presented to the user is basic, with just an email input field that updates the `email` state variable on change and a submit button that is disabled and changes its text based on the `isLoading` state. We have an `onSubmit` event handler linked to `handleSubmit` to send the form details and a paragraph that displays any messages stored in the `message` state.

This `page.js` file calls `requestMagicLink.js`; let’s dig into that, next.

### requestMagicLink.js

```javascript
// app/api/auth/requestMagicLink.js
import jwt from 'jsonwebtoken'
import nodemailer from 'nodemailer'
import { headers } from 'next/headers'

import { saveToken, getUserByEmail } from '../../../../lib/database'

export async function POST(req, res) {
  const { email } = await req.json()

  const user = await getUserByEmail(email)

  if (!user) {
    return Response.json({ message: 'User note found' })
  }

  // Create a magic link token
  const token = jwt.sign({ email }, process.env.JWT_SECRET, {
    expiresIn: '1h',
  })
  const headersList = headers()
  const host = headersList.get('host')
  const magicLink = `http://${host}/api/auth/verify?token=${token}`

  // Save token in your database with an expiration time
  await saveToken(user.id, token)

  // Set up email transporter and send the magic link
  const transporter = nodemailer.createTransport({
    host: '<email-host>',
    port: 587,
    secure: false, // upgrade later with STARTTLS
    auth: {
      user: '<email-username>',
      pass: '<email-password>',
    },
  })

  await transporter.sendMail({
    from: '<from-address>',
    to: email,
    subject: 'Your Magic Link',
    text: `Click here to log in: ${magicLink}`,
  })

  return Response.json({ message: 'Magic link sent!' })
}
```

This example code handles the API requests related to generating and sending the magic link for user authentication. Let's go through the major components and functions of the code.

The function takes `req` (request) and `res` (response) objects as parameters, and then extracts the `email` from the request's JSON body. We then use `getUserByEmail` from `lib/database.js` to search for a user in the database using the provided email. If the user doesn’t exist we send a JSON response indicating the user was not found.

The function then creates the token using `jsonwebtoken` to create a JWT with the user's email, signing it with a secret from environment variables and setting an expiration of 1 hour. With that token we can create our magic link. We retrieve the host from the request headers and construct a URL with the generated token as a query parameter.

The generated token is then saved in the database with an associated user ID using `saveToken`.

After that, we configure a `nodemailer` transporter with SMTP settings (host, port, security, and authentication credentials) and send an email to the user with the magic link authentication.

Finally, we send back a JSON response indicating that the magic link was sent.

Here, we using one of our helper libraries, `database.js`. Let’s go through that next.

### database.js

```javascript
// lib/database.js

import { supabase } from './supabaseClient'

export const saveToken = async (userId, token) => {
  const { data, error } = await supabase.from('magic_tokens').insert([
    {
      user_id: userId,
      token: token,
      expires_at: new Date(Date.now() + 3600000),
    }, // expires in 1 hour
  ])

  if (error) throw new Error(error.message)
  return data
}

export const getUserByEmail = async (email) => {
  const { data, error } = await supabase.from('users').select('*').eq('email', email).single()

  console.log(data)
  if (error) throw new Error(error.message)
  return data
}

export const getTokenData = async (token) => {
  const { data, error } = await supabase
    .from('magic_tokens')
    .select('*')
    .eq('token', token)
    .single()

  if (error) throw new Error(error.message)
  if (new Date(data.expires_at) < new Date()) {
    throw new Error('Token expired')
  }
  return data
}

export const deleteUserToken = async (token) => {
  const { data, error } = await supabase.from('magic_tokens').delete().match({ token: token })

  if (error) throw new Error(error.message)
  return data
}
```

This is a collection of utility functions designed to interact with Supabase. Each function is designed to interact with specific tables in a Supabase database, which each handle different aspects like token generation, user lookup, token validation, and cleanup. Let's break down each function:

### saveToken

- **Purpose**: To save a newly generated token in the database, fulfilling the requirements for secure token management.
- **Parameters**: Accepts `userId` (the user's ID) and `token` (the magic link token).
- **Functionality**:
  - Inserts a new record into the `magic_tokens` table in Supabase with the user's ID, the token, and an expiration time (set to 1 hour ahead of the current time).
  - If an error occurs during the database operation, it throws an error with the message received from Supabase.
  - Returns the data received from Supabase if the operation is successful.

### getUserByEmail

- **Purpose**: To fetch a user's data from the database using their email address.
- **Parameters**: Accepts `email`, which is the email address of the user.
- **Functionality**:
  - Queries the `users` table in Supabase for a record matching the provided email address.
  - Logs the email and the data received for debugging purposes.
  - If an error occurs, it throws an error with the message from Supabase.
  - Returns the user data if a matching record is found.

### getTokenData

- **Purpose**: To retrieve token data from the database.
- **Parameters**: Accepts `token`, the magic link token.
- **Functionality**:
  - Queries the `magic_tokens` table for a record with a token matching the provided one.
  - Checks if the token has expired by comparing the `expires_at` field with the current time. If the token is expired, it throws an error.
  - If an error occurs during the query, it throws an error with the message from Supabase.
  - Returns the token data if a matching and non-expired token is found.

### deleteUserToken

- **Purpose**: To delete a token from the database, complying with the requirements for token lifecycle management.
- **Parameters**: Accepts `token`, the magic link token to be deleted.
- **Functionality**:
  - Deletes the record from the `magic_tokens` table that matches the provided token.
  - If an error occurs during this operation, it throws an error with the message from Supabase.
  - Returns the data received from Supabase upon successful deletion.

This file, in turn, is calling on the other helper library `supabaseClient.js`.

### supabaseClient.js

```javascript
// lib/supabaseClient.js

import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.SUPABASE_URL
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```

This is a straightforward setup for initializing a client instance of Supabase in a Javascript application.

`export const supabase = createClient(supabaseUrl, supabaseAnonKey);`creates and exports an instance of the Supabase client, which is used to interact with your Supabase project. The `createClient` function takes the Supabase URL and the anonymous key as arguments and returns the initialized client.

`SUPABASE_URL` and `SUPABASE_ANON_KEY` (along with `JWT_SECRET`) are pulled from our environment variables file, `.env.local`.

### .env.local

```sh
SUPABASE_URL=<supabase-url>
SUPABASE_ANON_KEY=<supabase-anon-key>
JWT_SECRET=<jwt-secret>
```

You get `SUPABASE_URL` and `SUPABASE_ANON_KEY` from your Supabase dashboard.

You’ll see from above, we also need to set up two tables in Supabase. We need a `users` table with an `email` field, and a `magic_tokens` table with these fields:

- `user_id`, which comes from the users table.
- `token`, which is the generated token.
- `expires_at`, to add an expiry time to the token.

Let’s get back to the main code. If we fill out the email field on the homepage and click “Send Magic Link,” `requestMagicLink` will be called and an email sent to the entered email address. Clicking on that link calls the `verify.js` endpoint.

### verify.js

```javascript
// app/api/auth/verify.js
import jwt from 'jsonwebtoken'

import { getTokenData, deleteUserToken } from '../../../../lib/database'

export async function GET(req) {
  const token = req.url.split('=')[1]

  console.log(token) // Logs the token value
  const tokenData = await getTokenData(token)

  if (!tokenData) {
    return Response.json({ error: 'Invalid or expired token' })
  }

  const { email } = jwt.verify(token, process.env.JWT_SECRET)

  // Delete or invalidate the token
  await deleteUserToken(token)

  return Response.redirect('/dashboard') // Or wherever you want to redirect the user after login
}
```

This defines the API route for verifying the magic link token as part of an authentication process.

The code is designed to:

- Extract a token from the request URL.
- Verify the token's validity.
- Perform actions based on the token's validity (such as user redirection or error response).

The code retrieves the token from the URL query string by splitting the URL at the `=` character and taking the second part (`req.url.split("=")[1]`).

The `getTokenData` function is called to fetch the token data from the database. If no data is found (implying the token is invalid or expired), it returns a JSON response with an error message. Using `jwt.verify`, we validate the token against the secret key stored in `process.env.JWT_SECRET`. This also extracts the payload (`email`) from the token.

We then call `deleteUserToken` to remove the token from the database, ensuring it cannot be reused. Finally, it redirects the user to the `/dashboard` route upon successful token verification.

### dashboard/page.js

There isn’t much to `dashboard/page.js`:

```javascript
// app/dashboard/page.js
'use client'

export default function Dashboard() {
  return <h1>A verified page</h1>
}
```

In a production application, this is the page you would build out in your application.

## Final Thoughts

There is a lot to think about with email magic links. The above example doesn’t go into robust authentication checking with the user, nor does it add rate-limiting or have significant error handling. An unfortunate truth of authentication is that there is no silver bullet. While magic links take away the headache of managing user credentials, you still have to manage token generation, storage and expiry. Plus, you are still managing and storing user data.

Any good developer is going to take the time to understand, at least, the basic strategies leveraged by the tools they use to speed up their processes (good work understanding magic links!). With that being said, a simpler and more secure alternative to all the code above is to use [Clerk](/). We built Clerk to make it quick and easy to add advanced authentication techniques into your application. To learn more about magic links, visit our [documentation](/docs/custom-flows/magic-links) or this article on [implementing magic links with Next.js and Clerk](/blog/secure-authentication-nextjs-email-magic-links).

---

# Create Your Own Custom User Menu with Radix
URL: https://clerk.com/blog/create-custom-user-menu-radix.md
Date: 2023-12-14
Category: Guides
Description: Quickly and easily build a custom user menu for your application leveraging Clerk's hooks and methods and building on Radix primitives for a custom UI.

> \[!IMPORTANT]
> `<UserButton />` might be more flexible than you think! As of August 2024, you can now customize the component with [custom menu items](/docs/customization/user-button). Should you need even more customization, this post and [part 2](/blog/create-custom-user-menu-radix-pt-2) are still here to help you build the experience you desire from scratch.

Clerk’s components were created with you in mind. Components do most of the functional work for you, allowing you to get your auth flows working in minutes, and support customization to fit your app’s style. That said, sometimes a component like the [`<UserButton />`](/docs/components/user/user-button) doesn’t suit the needs of your application. The good news? Clerk provides hooks and functions that make building your custom UI components easy. Let’s take a quick look at how to create your custom user menu.

Code samples are from @clerk/nextjs 4.27.2 and @radix-ui/react-dropdown-menu 2.0.6

## Getting Started

The hardest part of building a custom user menu is often the dropdown menu itself. You need the button to trigger the menu opening, a way to close it, a way to track open/closed states, a way to handle ‘click off’ to close, logic to close if the user hits the `Esc` button, a way to handle keyboard input, and the list goes on. We’re going to save ourselves some time and use a great library while doing so. Radix provides world-class, accessible, unstyled [primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) that you can use to quickly and efficiently build your UI. Let’s start by installing the primitive we need — the dropdown menu.

```bash
pnpm install @radix-ui/react-dropdown-menu
```

Once the installation finishes, let’s create the scaffolding for our new component. The following will be the foundation of the menu. The trigger will hold the User button that will open the menu, and each item will hold one of the menu entries. Remember that Radix Primitives are unstyled and there is no content so this will be blank.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'

export const UserButton = () => {
  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger></DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content>
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item></DropdownMenu.Item>
            <DropdownMenu.Item></DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item></DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## Create the User Button

The first content we will add is the User button. This will show that the user is logged in, and will be the trigger to open the menu. For the sake of this post we will assume that you have marked email as required in the Clerk Dashboard, in the [User & Authentication → Email, Password, Username → Email](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username) section to ensure every user has an email. You could change this to a first name or username easily enough. Just make sure that whatever option you choose is something that will exist for all users, for the sake of rendering. You could also conditionally render different information depending on what the user has provided — show the first name if available; if not, show the email.

To build the button we will leverage the [`useUser()`](/docs/references/react/use-user#use-user) hook. This gives us access to information about the user, such as profile image, email, name, and more. We will also make sure that Clerk and the user have loaded, and that there is valid user data, before rendering the User button.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
// Import useUser()
import { useUser } from '@clerk/nextjs'
// Import the Image element
import Image from 'next/image'

export const UserButton = () => {
  // Grab the `isLoaded` and `user` from useUser()
  const { isLoaded, user } = useUser()

  // Make sure that the useUser() hook has loaded
  if (!isLoaded) return null
  // Make sure there is valid user data
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Render a button using the image and email from `user` */}
        <button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content>
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item></DropdownMenu.Item>
            <DropdownMenu.Item></DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item></DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## Add the Sign-Out and Manage Account Buttons

With the User button in place, we can now add Sign Out and Manage Account buttons. The [`useClerk()`](/docs/references/react/use-clerk) hook provides the two methods we will need for this — the [`signOut()`](/docs/custom-flows/sign-out#custom-sign-out) method and the [`openUserProfile()`](/docs/references/javascript/clerk/user-profile#open-user-profile) method. The User Profile will open as a modal. You could, instead, mount the [`<UserProfile />`](/docs/components/user/user-profile) component to its own route, and then link it to the route. For the Sign Out button, we will also need to use the Next.js router to handle the redirect.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
// Import useClerk()
import { useUser, useClerk } from '@clerk/nextjs'
// Import the Next.js router
import { useRouter } from 'next/navigation'
import Image from 'next/image'

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  // Grab the signOut and openUserProfile methods
  const { signOut, openUserProfile } = useClerk()
  // Get access to the Next.js router
  const router = useRouter()

  if (!isLoaded) return null
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content>
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item asChild>
              {/* Create a button with an onClick to open the User Profile modal */}
              <button onClick={() => openUserProfile()}>Profile</button>
            </DropdownMenu.Item>
            <DropdownMenu.Item></DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item asChild>
            {/* Create a Sign Out button -- signOut() takes a call back where the user is redirected */}
            <button onClick={() => signOut(() => router.push('/'))}>Sign Out </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## Extending the Custom User Menu

The custom button has now replicated the behavior of the `<UserButton />`, though it is very much still unstyled. We’re going to do one more thing — add one more menu entry to mimic expanding the menu. This will use the [`<Link />`](https://nextjs.org/docs/app/api-reference/components/link) component from Next.js to link to a fictional `/subscriptions` route.

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { useUser, useClerk } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
// Import Link to add more buttons to the menu
import Link from 'next/link'

export const UserButton = () => {
  const { isLoaded, user } = useUser()
  const { signOut, openUserProfile } = useClerk()
  const router = useRouter()

  if (!isLoaded) return null
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <button>
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="border border-gray-200 bg-white text-black drop-shadow-md">
          <DropdownMenu.Label />
          <DropdownMenu.Group>
            <DropdownMenu.Item asChild>
              <button onClick={() => openUserProfile()}>Profile</button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild>
              {/* Create a fictional link to /subscriptions */}
              <Link href="/subscriptions">Subscription</Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator />
          <DropdownMenu.Item asChild>
            <button onClick={() => signOut(() => router.push('/'))}>Sign Out </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

## The Last Step!

Your new component is almost ready — it just needs some styling. Let’s add a little bit to get started. The code below is ready to drop right into your app, and then you can import the new `<UserButton />` into your header.

### The Custom User Menu

![Create Custom User Menu Radix tutorial illustration](./7d5be406f59f3f30aaecd91a4f4dab6393a7a0ff-366x385.png)

### Final code

```tsx
'use client'

import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
// Import useUser() and useClerk()
import { useUser, useClerk } from '@clerk/nextjs'
// Import Next's router
import { useRouter } from 'next/navigation'
// Import the Image element
import Image from 'next/image'
// Import Link to add more buttons to the menu
import Link from 'next/link'

export const UserButton = () => {
  // Grab the `isLoaded` and `user` from useUser()
  const { isLoaded, user } = useUser()
  // Grab the signOut and openUserProfile methods
  const { signOut, openUserProfile } = useClerk()
  // Get access to Next's router
  const router = useRouter()

  // Make sure that the useUser() hook has loaded
  if (!isLoaded) return null
  // Make sure there is valid user data
  if (!user?.id) return null

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        {/* Render a button using the image and email from `user` */}
        <button className="flex flex-row rounded-xl border border-gray-200 bg-white px-4 py-3 text-black drop-shadow-md">
          <Image
            alt={user?.primaryEmailAddress?.emailAddress!}
            src={user?.imageUrl}
            width={30}
            height={30}
            className="mr-2 rounded-full border border-gray-200 drop-shadow-sm"
          />
          {user?.username ? user.username : user?.primaryEmailAddress?.emailAddress!}
        </button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Portal>
        <DropdownMenu.Content className="mt-4 w-52 rounded-xl border border-gray-200 bg-white px-6 py-4 text-black drop-shadow-2xl">
          <DropdownMenu.Label />
          <DropdownMenu.Group className="py-3">
            <DropdownMenu.Item asChild>
              {/* Create a button with an onClick to open the User Profile modal */}
              <button onClick={() => openUserProfile()} className="pb-3">
                Profile
              </button>
            </DropdownMenu.Item>
            <DropdownMenu.Item asChild>
              {/* Create a fictional link to /subscriptions */}
              <Link href="/subscriptions" passHref className="py-3">
                Subscription
              </Link>
            </DropdownMenu.Item>
          </DropdownMenu.Group>
          <DropdownMenu.Separator className="my-1 h-px bg-gray-500" />
          <DropdownMenu.Item asChild>
            {/* Create a Sign Out button -- signOut() takes a call back where the user is redirected */}
            <button onClick={() => signOut(() => router.push('/'))} className="py-3">
              Sign Out{' '}
            </button>
          </DropdownMenu.Item>
        </DropdownMenu.Content>
      </DropdownMenu.Portal>
    </DropdownMenu.Root>
  )
}
```

Example repository:
[https://github.com/royanger/clerk-custom-user-menu](https://github.com/royanger/clerk-custom-user-menu)

## Explore the Powerful Customization Options Clerk Offers!

Take a look at our [Custom Flows](/docs/custom-flows/overview) documentation to explore more ways to customize your application using the many hooks and methods Clerks provides.

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless user management starts here!

---

# Introducing has(), protect(), and <Protect>
URL: https://clerk.com/blog/introducing-authorization.md
Date: 2023-12-13
Category: Engineering
Description: Authorization is now a core feature of Clerk – learn more about our new authorization helpers for B2B SaaS

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

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

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

## Custom roles and permissions

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

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

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

![Custom roles and permissions screenshot](./d69547abef435115556125370cab753a5e7d08f3-2062x1298.png "The new role manager in Clerk's dashboard")

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

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

### 1. has()

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

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

- `auth()` in Next.js App Router
- `useAuth()` in client components, including during SSR
- `getAuth(request)` in server contexts outside of the App Router (Remix Loaders, Node, Express, Fastify, etc)

```typescript
// Within a server component, server action, or route handler
const CreateWidget = () => {
  const { has } = auth()
  if (!has({ permission: 'org:widgets:create' })) {
    return null
  }
  // ...
}

// Within a client component
export const AdminsOnly = () => {
  const { has } = useAuth()
  if (!has({ role: 'org:admin' })) {
    return null
  }
  // ...
}

// Within an Express API route
app.post('/api/widgets', (req, res) => {
  const { has } = getAuth(req)
  if (!has({ permission: 'org:widgets:create' })) {
    return null
  }
  // ...
})
```

### 2. protect()

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

```typescript
export const POST = () => {
  const { userId, orgId } = auth().protect({ permission: 'org:widgets:create' })

  // Protect would throw an error if the user is unauthorized
  // If this point is reached, it is guaranteed that userId and ordId exist,
  // and that userId has the "org:widgets:create" permission in orgId

  // Bonus: TypeScript knows that userId and orgId exist!
  // ...
}
```

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

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

### 3. \<Protect>

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

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

```tsx
export const Navigation = () => {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/widgets">Widgets</Link>
      <Protect role="org:admin">
        <Link href="/admin">Admin Panel</Link>
      </Protect>
    </nav>
  )
}
```

## Documentation

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

- [Roles and Permissions overview](/docs/organizations/roles-permissions)
- [Create Roles and assign Permissions](/docs/organizations/create-roles-permissions)
- [Verify the active user’s permissions](/docs/organizations/verify-user-permissions)

## Authorization is now a core feature of Clerk

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

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

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

---

# Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan”
URL: https://clerk.com/blog/new-pricing-plans.md
Date: 2023-11-30
Category: Company
Description: Introducing NEW pricing for Clerk – a Pro Plan with additional features, Pro Add-Ons to fit your application's use case, and 10,000 MAUs free on all plans

We’re thrilled to introduce a simplified pricing structure that’s not only easier to understand, but also significantly cheaper for most applications currently using Clerk. **Every application gets 10,000 free monthly active users (MAUs)!**

Brand new users will also now receive their **“First Day Free,”** which means their activity is not counted until 24 hours after signing up. This ensures you are not charged for users who sign up, but churn within their first day and do not return.

Since Clerk’s inception, our primary goal has been to lower the barrier to building production-grade applications. Every application deserves secure and seamless user management that takes minutes to setup, not days — freeing you to focus on what’s important: *your application*.

Existing customers will be grandfathered into their current plans, however, it may be cheaper to switch, so please
evaluate accordingly and reach out to us if you have any questions.

Our new simplified pricing structure consists of “Free”, “Pro” and “Enterprise” plans. Where each plan includes your first 10,000 MAUs free and you will never pay for an inactive user. Additional features can be added to the Pro plan via “Pro add-ons” tailor made for specific use cases.

## Free Plan – Up to 10,000 MAUs

Our free plan remains generous, with no features removed. You still get beautifully designed, performant, and customizable sign-in pages on the domain of your choosing. However, we’ve now doubled the included number of MAUs! Get started, and iterate longer — completely for free!

[Visit the pricing page to see all features in the Free plan](/pricing)

## Going Pro – A complete feature set for most use cases

The new Pro plan includes the first 10,000 MAUs, and starts at $25/mo. Additional MAUs are only $0.02 each. This is a dramatic simplification, and includes 10X more MAUs compared to the previous Hobby plan. The Pro plan also now includes the ability to set a “Custom Session Duration,” which has been a key sticking point in the past.

**Additional Pro plan features include:**

- Removing Clerk Branding
- Custom Session Duration *(previously in the Business plan)*
- Password Complexity Requirements
- User Ban/Unban
- Allowlist/Blocklist
- Multiple Domains
- SMS Authentication
- SOC2 Report on Request

[Visit the pricing page to see all features in the Pro plan](/pricing)

## Pro Add-Ons – Customize to fit your needs

While the Pro plan is designed for the majority of applications, add-ons are tailored to specific business needs and are priced according to their added value. Today we’re launching with 3 Pro add-ons that will become more feature-rich over time:

- **Enhanced Authentication** — includes MFA
- **Enhanced Organizations** — includes domain restrictions, and custom roles and permissions *(Stay tuned for this new feature* 😉)
- **Enhanced Administration** — includes user impersonation

[Visit the pricing page to see all features in each Pro add-on](/pricing)

## The Future

We know pricing changes lead to uncertainty and we’re committed to minimizing them. Our end-goal here is to continue to drop the price of authentication as we add value in adjacent areas. Building applications should be getting easier and cheaper over time, and this pricing change is just one step in the right direction. Clerk will always be committed to the developer community, and hopefully this pricing overhaul reflects that.

[We’re here if you have any questions](https://clerk.com/contact/support) — Happy building!

---

# Next.js Authentication with Clerk: Streamlined SSR Handling
URL: https://clerk.com/blog/nextjs-auth-clerk-streamlined-ssr-efficiency.md
Date: 2023-11-18
Category: Engineering
Description: Discover streamlined Next.js authentication with Clerk, simplifying SSR for efficient user data handling in web development.

In the ever-evolving world of web development, streamlining tasks is paramount. Clerk, the versatile User Management platform, presents a contemporary way to manage user data in Next.js applications, leaving behind the complexity of old patterns for server-side rendering (SSR). Let's explore the key differences and the value of this new approach with basic code snippets.

## Navigating Next.js SSR: The Previous Approach

Previously, incorporating Clerk in a Next.js app involved intricate setups and token management. Here's a reminder of what the deprecated approach looks like

```typescript {{ title: 'Deprecated approach' }}
import { withServerSideAuth } from '@clerk/nextjs/ssr'

export const getServerSideProps = withServerSideAuth(({ req, resolvedUrl }) => {
  const { sessionId } = req.auth

  if (!sessionId) {
    return { redirect: { destination: '/sign-in?redirect_url=' + resolvedUrl } }
  }

  return { props: {} }
})
```

## **The New Way: Streamlined Handling**

These are just a few examples of the new streamlined approaches. If you are looking for a more comprehensive breakdown of the best way to read session data in your Next.js app using the [Pages Router](https://nextjs.org/docs/pages) or [App Router](https://nextjs.org/docs/app), please [reference the docs](/docs/references/nextjs/read-session-data?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=mirco-blog).

### SSR in App Router – Server Component

The modern Clerk approach in App Router simplifies user data handling with straightforward helper functions. Here's a snippet of the new approach:

```tsx {{ title: 'NextJS new approach - App Router ' }}
import { currentUser } from '@clerk/nextjs'

export default async function Example() {
  const user = await currentUser()

  return (
    <div>
      Hello {user.firstName}, your ID is {user.id}
    </div>
  )
}
```

### SSR in Pages Router

If you are using the Pages Router, you can find more detailed examples beyond this basic snippet in the [docs.](/docs/references/nextjs/build-clerk-props?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=micro-blog)

```typescript {{ title: 'NextJS new approach - Pages Router ' }}
import { getAuth, buildClerkProps } from '@clerk/nextjs/server'
import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const { userId } = getAuth(ctx.req)

  if (!userId) {
    // handle user is not logged in.
  }

  // Load any data your application needs for the page using the userId
  return { props: { ...buildClerkProps(ctx.req) } }
}
```

### **The Value of the New Approach**

1. **Simplicity**: The new approaches offer a cleaner, more straightforward codebase for effortless data access.
2. **Efficiency**: Data access becomes a breeze.
3. **Improved Workflow**: Focus on building features, not grappling with complex User Management setups.
4. **Maintainability**: A tidy codebase equals easier maintenance and fewer debugging hassles.

Dive headfirst into Clerk's innovative approach to supercharge your development process, ensuring your apps are always at the cutting edge. This newfound simplicity isn't just for show; it's here to make your work smoother, your code cleaner, and your applications more maintainable. And what's even more exciting? This flexibility isn't confined to one corner of your project; it stretches its arms to [Route Handlers](/docs/reference/nextjs/app-router/route-handlers) as well, making it the perfect fit for the demands of modern web development.

## **Ready to Implement Authentication in Your App?**

Don't hesitate to explore our [Next.js Clerk Docs](/docs/references/nextjs/overview?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=micro-blog) for a quick and comprehensive guide on integrating Clerk [Next.js authentication](/nextjs-authentication) into your application within minutes, not days.

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless User Management starts here!

---

# Clerk Webhooks: Data Sync with Convex
URL: https://clerk.com/blog/webhooks-data-sync-convex.md
Date: 2023-11-14
Category: Guides
Description: This post covers how to synchronize user data from Clerk into a Convex database using Webhooks.

Composing an application out of multiple data sources can be challenging, and while Clerk’s Backend APIs work great for most use cases, Webhooks offer the next level of integration that enables you to take full advantage of your existing stack. This post will cover how to synchronize user data from Clerk into your own backend using Webhooks.

## Data Sync with Convex

[Convex](https://convex.dev) is a real time [Backend-as-a-Service](https://en.wikipedia.org/wiki/Backend_as_a_service) Provider that makes building highly dynamic and interactive experiences with React easier than ever.

Clerk offers a first-class integration with Convex. The queries and mutations living in your Convex instance can be authenticated with a token that was created by Clerk. This integration is covered in more detail [in the documentation](/docs/integrations/databases/convex?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=webhooks-blog\&utm_term=convex-post).

Convex is also a great target for synchronizing user data from Clerk using webhooks, since you can take full advantage of Convex’s realtime capabilities for user data.

If you are not sure what webhooks are and how to use webhooks with Clerk, it’s recommended to read [this getting started post](/blog/webhooks-getting-started?utm_source=DevRel\&utm_medium=blog\&utm_campaign=blog-to-blog\&utm_content=webhooks-blog\&utm_term=convex-post) first.

To demonstrate data synchronization with Convex, we can use [Convex’s Clerk starter repo](https://github.com/thomasballinger/convex-clerk-users-table). You can clone and run this repo locally by following the instructions in the repo.

This post will:

1. Explain how the demo uses data sync with reactive queries together
2. Show code examples of the webhooks handler implementation in the demo

## The Demo

The demo application is a chat room where users can send and receive messages. To access the room, a user needs to sign up for an account using the Clerk integration. Once signed in, the user is redirected to a chat room, which establishes a reactive query to the Convex database. This reactive query looks for the user in the database, and returns a “No Clerk User” if the user is not found, and a “Logged In” if the user is found.

Initially after signing up, the user will not be found in the database. Since the signup process happened with Clerk, Convex has no record of this user.

![Webhooks Data Sync Convex setup guide](./1e2c045dab59119f46ab6a0767ce42e6e766e9c8-3600x1431.png)

This is where webhooks come into play. After the user signs up with Clerk, Clerk will send an http request to a pre-defined URL with data about the sign up. This URL will point to a Convex HTTP Action, which will parse the data and create a record in the Convex database for the user. Now the client can query for this user again and receive the expected response.

![Webhooks Data Sync Convex setup guide](./878c4fda4bc4fdfc93ba7e78ba644c1543666e6a-3600x1023.png)

But since Convex queries are reactive, the client doesn’t need to retry the query, instead it will automatically receive an update from the server with the user data. This will trigger React to re-render the UI, providing the user access to the chat room.

![Webhooks Data Sync Convex setup guide](./41c813e18fefb8873b6df2952569d7417acca319-3600x1023.png)

## The Code

To look at the webhook implementation, we can open the `convex/http.ts` file in this repo.

```typescript {{ title: 'convex/http.ts', prettier: false }}
// define the webhook handler
const handleClerkWebhook = httpAction(async (ctx, request) => {
  const event = await validateRequest(request)
  if (!event) {
    return new Response('Error occured', { status: 400 })
  }
  switch (event.type) { /* ... */ }
  return new Response(null, { status: 200 })
});

// define the http router
const http = httpRouter()

// define the webhook route
http.route({
  path: '/clerk-users-webhook',
  method: 'POST',
  handler: handleClerkWebhook,
})
```

The `handleClerkWebhook` function is responsible for… you guessed it… handling the Clerk webhook. It’s a function decorated with `httpAction` and receives the HTTP request and a context object to access other Convex resources.

This function runs security validation on the request to ensure that it came from a trusted source, and runs some database mutations based on the type of event received. The mutations simply mirror the event - creating, updating, or deleting a user in Clerk simply results in creating, updating, or deleting the same user in Convex. It forms a copy of the Clerk user table within Convex.

```typescript {{ title: 'convex/http.ts' }}
switch (event.type) {
  case 'user.created':
  case 'user.updated': {
    await ctx.runMutation(internal.users.updateOrCreateUser, {
      clerkUser: event.data,
    })
    break
  }
  case 'user.deleted': {
    const id = event.data.id!
    await ctx.runMutation(internal.users.deleteUser, { id })
    break
  }
  default: {
    console.log('ignored Clerk webhook event', event.type)
  }
}
```

This allows the most powerful feature of Convex - Queries, to access the user data directly, which means the application doesn’t need to make additional requests to Clerk to fetch the user data (also called the n+1 problem), and the user interface can get notified if the data changes (e.g. if the user changes their name or profile picture).

```typescript {{ title: 'convex/users.ts' }}
export const getUser = internalQuery({
  args: { subject: v.string() },
  async handler(ctx, args) {
    return ctx.db
      .query('users')
      .withIndex('by_clerk_id', (q) => q.eq('clerkUser.id', args.subject))
      .unique()
  },
})
```

## Full Potential

Webhooks are a powerful feature that enable integrations with existing backend tools, allowing you to use their technical potential to the fullest, while also using Clerk's user management capabilities to the fullest. They help glue disjointed parts of the backend together so that you can pick whatever technology suits your use case for storing and processing data.

## Ready to Integrate Authentication in Your App?

You can explore the [Clerk Webhooks docs](/docs/integrations/webhooks/overview?utm_source=DevRel\&utm_medium=blog\&utm_campaign=docslanding\&utm_content=webhooks-blog\&utm_term=convex-post) to learn more about the webhook events and data that is exposed by Clerk and build your own integrations today!

For more in-depth technical inquiries or to engage with our community, feel free to [join our Discord](https://clerk.com/discord). Stay in the loop with the latest Clerk features, enhancements, and sneak peeks by following our X account, [@clerk](https://x.com/clerk). Your journey to seamless User Management starts here!