# Clerk Blog — Page 5

# How to implement Google authentication in Next.js 15
URL: https://clerk.com/blog/nextjs-google-authentication.md
Date: 2025-01-24
Category: Guides
Description: Learn how to add Google authentication to your Next.js app, implement a user button for profile management, and enable Google One Tap using Clerk.

This guide walks you through adding Google authentication to your Next.js 15 application in record time.

> For a comprehensive overview of all authentication methods in Next.js, see our [Ultimate Guide to Next.js Authentication](/blog/nextjs-authentication).

By the end, you'll implement essential authentication features including:

- Google authentication for sign-up and sign-in
- Route protection from unauthenticated access
- Google One Tap integration (optional)
- Access to Google services like Calendar on behalf of the authenticated user using [OAuth](/glossary#oauth) (optional)

You'll also learn how to add a polished user button dropdown that gives your users control over their [session](/glossary#session) and profile.

![Google sign-in, user button dropdown, and One Tap UI](./1.png)

To implement Google authentication, you can choose between building it yourself with an open-source library - giving you complete control over the implementation - or using Clerk, which offers the quickest path to integration in Next.js. Both approaches have their place, but in this guide, we'll use Clerk.

## An introduction to Clerk

![Clerk homepage showing user management platform and components](./2.png)

[Clerk](/nextjs-authentication) is a user management and authentication platform that makes it quick to add secure authentication to your Next.js application. We provide pre-built components like ⁠`<SignIn/>` and `<SignUp />` that you can configure to support various authentication methods, including Google.

Using familiar Next.js patterns like components and [middleware](/blog/what-is-middleware-in-nextjs), Clerk handles all the complex backend logic — from session management to route protection.

## How to implement Google authentication using Clerk

Before you implement Google authentication, you'll need to [**create a Clerk account**](/sign-up) to manage your users and authentication settings. Creating an account is free for your first 10,000 monthly users, and no credit card is required.

Sign in to the Clerk dashboard and create your first application. Give it a name and enable Google authentication (you can enable additional authentication methods at any time), then click "Create application".

To proceed with this guide, follow the quickstart steps in the Clerk dashboard. Once you've completed the setup, return here to continue with the next steps.

---

Welcome back!

Start your development server and visit [http://localhost:3000](http://localhost:3000) to sign up and create your first Clerk user.

The quickstart code demonstrates a basic Clerk implementation in your root layout. It wraps your application in ⁠`<ClerkProvider />` and adds authentication UI components — showing a `<SignInButton />` button for unauthenticated users and a `<UserButton />` for those signed in.

The authentication components work as expected, but positioning them in the main navigation bar would create a more predictable user experience. Let's quickly explore how to do that next before diving into route protection.

> \[!NOTE]
> If you've set up [Single Sign-On (SSO)](/glossary/single-sign-on-sso) with Google before, you know it usually starts with configuring Google Cloud credentials. With Clerk in development mode, you can skip this setup and start building immediately using our shared development keys. For production, you'll still need [custom Google credentials](/docs/authentication/social-connections/google#configure-for-your-production-instance).

## Adding authentication to your navigation bar

While some authentication solutions focus solely on authentication, Clerk also handles user management. This means you get access to components like `⁠<UserButton />` - a dropdown that shows which account is signed in and lets users manage their session and profile data.

Since `⁠<UserButton />` and ⁠`<SignInButton />` are standard React components, you can style and position them anywhere in your application.

Here's a quick example using [Tailwind CSS](/glossary/tailwind-css) to illustrate how it’s done:

```tsx {{ filename: 'src/app/layout.tsx', ins: [19, 20, 21, 22, 30, 31, 32, 28], del: [27], prettier: false }}
import { 
  ClerkProvider,
  SignInButton,
  SignedIn,
  SignedOut,
  UserButton 
} from '@clerk/nextjs'
import './globals.css'

export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>
          <nav className="bg-white p-4 shadow-md">
            <div className="container mx-auto flex items-center justify-between">
              <h1 className="text-xl">My App</h1>
              <div className="flex items-center">
                <SignedOut>
                  <SignInButton />
                </SignedOut>
                <SignedIn>
                  <UserButton />
                  <UserButton showName />
                </SignedIn>
              </div>
            </div>
          </nav>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

## Protecting routes from unauthenticated access

Now that users can sign in, let's explore how to restrict page access to authenticated users only. While there are several approaches to [route protection in Next.js](/docs/reference/nextjs/app-router/route-handlers), we will focus on using middleware here.

During the quickstart, you added Clerk middleware to your application. By default, all routes are public. Let's update the middleware to protect specific routes or patterns of routes from unauthenticated access:

```tsx {{ filename: 'src/middlware.ts', ins: [2, 3, 4, 7, 8, 9], del: [1, 6] }}
import { clerkMiddleware } from '@clerk/nextjs/server'
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/forum(.*)'])

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

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

Above, we create a route matcher for paths starting with "⁠/dashboard" or ⁠"/forum" and then check each request against these patterns. If the route matches, use `⁠auth.protect()` to automatically redirect users to sign in. In effect, only authenticated users can access these pages.

> \[!NOTE]
> **Authentication vs authorization**
>
> [Authentication](/glossary#authentication) only verifies that a user is signed in. While this guide shows you how to protect routes from unauthenticated access, you might also need [authorization](/glossary#authorization) — checking if an authenticated user has *permission* to access specific resources based on ownership or roles.

## Adding Google One Tap support

[Google One Tap](https://developers.google.com/identity/gsi/web/guides/features) proactively prompts users to sign in with their Google account in a single click when they visit your site. This convenient approach can help increase your sign-in conversion rate compared to traditional authentication flows.

To implement Google One Tap, first, [update your Clerk application to use custom Google credentials](/docs/authentication/social-connections/google#configure-for-your-production-instance) instead of shared credentials.

Then, add ⁠`<GoogleOneTap />` to your layout:

```tsx {{ filename: 'src/app/layout.tsx', ins: [7, 25], prettier: false }}
import {
  ClerkProvider,
  SignInButton,
  SignedIn,
  SignedOut,
  UserButton,
  GoogleOneTap,
} from '@clerk/nextjs'
import './globals.css'

export default function RootLayout({ 
  children 
}: { 
  children: React.ReactNode 
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>
          <nav className="bg-white p-4 shadow-md">
            <div className="container mx-auto flex items-center justify-between">
              <h1 className="text-xl">My App</h1>
              <div className="flex items-center">
                <SignedOut>
                  <GoogleOneTap />
                  <SignInButton />
                </SignedOut>
                <SignedIn>
                  <UserButton showName />
                </SignedIn>
              </div>
            </div>
          </nav>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

When new users visit your application, Google One Tap will conveniently prompt them to sign in directly from the corner of the screen.

## Accessing Google services on behalf of the authenticated user using OAuth

![Accessing Google services via OAuth after Google sign-in](./3.png)
When users sign in with Google using Clerk, you're not just authenticating them — you're establishing a secure connection through OAuth that can do much more. While the authentication token proves who the user is, OAuth also enables your application to access Google services on their behalf.

Clerk simplifies this process. Beyond providing SSO with various providers, Clerk makes it straightforward to access user data from connected services. For example, you can retrieve an authenticated user's Google Calendar availability with just a few lines of code. For a detailed walkthrough, check out our [guide to accessing Google Calendar data](/blog/using-clerk-sso-access-google-calendar) with a complete demo application.

## Conclusion

In this guide, you've learned how to add Google authentication to your Next.js application using Clerk. Rather than building complex authentication logic yourself, Clerk provided pre-built components and middleware that enabled you to implement secure Google authentication in minutes. With Clerk's foundation in place, adding additional features like Google One Tap and OAuth access to Google services might have been easier to implement than you expected!

While we used Clerk's [Account Portal](/docs/customization/account-portal/overview) (a hosted authentication page) for the fastest implementation, you can also build your own sign-in and sign-up pages then render `<SignIn />` and `<SignUp />` directly in your application without the need for an external redirect. Learn how in this [guide](/docs/references/nextjs/custom-sign-in-or-up-page) or by following along with this video:

For more information about how to add Google as a social connection and an important note on switching to production, please refer to the [Clerk documentation](/docs/authentication/social-connections/google).

---

# What is middleware in Next.js?
URL: https://clerk.com/blog/what-is-middleware-in-nextjs.md
Date: 2025-01-16
Category: Guides
Description: Learn all about middleware in Next.js and how it works, as well as some of its common use cases, in this comprehensive guide.

Next.js middleware provides you with an incredible opportunity to customize the way your [Next.js application](https://nextjs.org) handles requests.

Middleware enables developers to intercept requests and perform operations like session validation, logging, and caching. While it may be tempting to use middleware to process and apply logic to every request to the application, doing so improperly might lead to massive performance depredations for your application. Once you understand how middleware works, you'll be better equipped to use middleware and understand when it shouldn't be used.

In this comprehensive guide, you'll learn what middleware is as it pertains to Next.js, how it works, and some of it's common use cases.

## What is Next.js middleware?

[Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) in Next.js refers to functions that run automatically for every incoming request, allowing you to inspect or modify the request data before it reaches your application's routing system.

Middleware can be used for a variety of purposes, such as [authentication](/nextjs-authentication), logging, and error handling. For example, you could use [middleware to authenticate](/docs/references/nextjs/clerk-middleware) incoming requests by checking tokens or credentials before allowing the request to proceed to your application's routing system.

Another benefit of using middleware in Next.js is its flexibility and customizability.

You can write your own middleware functions to fit the specific needs of your application to set application-wide settings or policies. This prevents you from having to worry about the complexity of having multiple layers of routing configuration.

By leveraging middleware, you can create a more robust, scalable, and maintainable application that meets the demands of complex web applications.

## When does Next.js process the middleware?

Next.js performs a series of operations when a request is received, so it helps to understand where middleware is handled in the order of operations:

### 1. `headers`

The `headers` configuration from `next.config.js` is applied first, setting the initial headers for every incoming request. This stage can be used to set security-related headers, such as content security policy or cross-origin resource sharing (CORS) headers.

### 2. `redirects`

The `redirects` configuration from `next.config.js` follows, determining how requests are redirected to other URLs. This stage handles URL rewriting and redirects, allowing you to manage routing rules that affect multiple pages or entire applications.

### 3. Middleware evaluation

Once `headers` and `redirects` are processed from the Next.js config file, the middleware is evaluated, and any logic within is executed. As you might expect, we’re going to dive deeper into this step throughout the guide.

### 4. `beforeFiles`

Next, the `beforeFiles` (`rewrites`) from `next.config.js` is applied. This stage allows you to perform additional rewriting or file-specific logic before routing takes place.

### 5. File system routes

The application's file system routes come into play next, including directories like `public/` and `_next/static/`, as well as individual pages and apps. This stage is where your application's static files are served.

### 6. `afterFiles`

Next up the `afterFiles` (`rewrites`) from `next.config.js` apply, providing a final chance to modify request data before dynamic routing takes place.

### 7. Dynamic Routes

Dynamic routes, like `/blog/[slug]`, execute next in the sequence. These routes require specific handling and rewriting logic to accommodate variables or parameters.

### 8. `fallback`

Finally, the `fallback` from `next.config.js` is applied, determining what happens when a request can't be routed using other configurations. This stage provides an opportunity to implement error handlers or fallback routes.

## What are some common use cases for Next.js middleware?

### Authentication

[Authentication](/nextjs-authentication) can be used with a login system where a user's credentials are validated before accessing sensitive routes or data. For instance, you might use Next.js middleware to validate a user's session on every request, redirecting them to the login page if their token is invalid.

[Clerk](https://clerk.com) uses Next.js middleware to intercept the request and determine the user's authentication state, something we'll explore in more detail later in this article.

### Logging

Logging logic can be added to middleware to track important events in your application, such as user actions or errors. You might implement logging using Next.js middleware to log every request to a centralized server, allowing you to analyze and debug issues more efficiently.

### Data fetching

While there are certain limitations to what kind of fetching can be performed, middleware can technically be used to load data from an API or database on every request, providing the most up-to-date information to users.

We'll explore the limitations of Next.js middleware in a later section.

### Request routing

Middleware can be used to customize the routing behavior of your application, such as catching all requests to a certain path and redirecting them to another route. This might be useful for implementing a catch-all error handler or for rewriting URLs to use a different domain.

### Cacheing

Cacheing can be used to improve performance by storing frequently used resources in memory and controlling the number of requests from individual users. The following example would check a cache object for the content of the request. If found, it is returned, otherwise, the response is intercepted and added to the cache for the next request.

```tsx
import { NextResponse } from 'next/server'

const cache = new Map()

export function middleware(request) {
  const { pathname } = request.nextUrl

  // Check if the response is cached
  if (cache.has(pathname)) {
    return new NextResponse(cache.get(pathname), {
      headers: { 'X-Cache': 'HIT' },
    })
  }

  // If not cached, proceed with the request
  const response = NextResponse.next()

  // Cache the response for future requests
  response.then((res) => {
    const clonedRes = res.clone()
    clonedRes.text().then((body) => {
      cache.set(pathname, body)
    })
  })

  response.headers.set('X-Cache', 'MISS')
  return response
}

export const config = {
  matcher: '/api/:path*',
}
```

### Rate limiting

Similarly, you could use middleware to keep track of requests coming from a single user or IP address and block the request if that user is making too many requests too frequently. This can help prevent upstream resources (ie: database) from being impacted for other uses.

### Page transforms

HTML rewrites and data transforms can be used to customize the behavior of your application when serving HTML files or transforming data in real-time. For instance, you might use Next.js middleware to rewrite URLs for images and other static assets, allowing you to host them on a different domain or with a custom subdomain.

### Analytics/reporting

Analytics and reporting can be used to track user behavior and monitor application performance, providing insights for improving the overall experience. You could use Next.js middleware to modify cookies on the fly, allowing you to integrate tracking scripts from third-party analytics providers without affecting the application's functionality.

### Internationalization

Internationalization can be used to deliver content in multiple languages and adapt the UI based on the user's locale. For example, you might determine a user's location by their IP or an HTTP header using middleware, redirecting users to a different language version of your application when they access it with a specific query parameter or cookie.

## How can I use middleware in a Next.js project?

To use middleware in a Next.js project, you'd create a single file at the root of the project called `middleware.ts` and add the necessary components.

Creating middleware involves defining a `middleware` function and (optionally) a matcher.

### The `middleware` function

The `middleware` function is where the logic of the middleware is stored. It uses a request as the single parameter and returns a response like so:

```tsx
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Your middleware logic here
  return NextResponse.next()
}
```

Using the `NextRequest` and `NextResponse` objects, you could write a basic middleware to redirect requests to `/dashboard`, while allowing requests to other routes to proceed:

```tsx
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api')) {
    return NextResponse.next()
  }

  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/sign-in', request.url))
  }
}
```

It's important to note that the `middleware` function must return one of the following responses:

- `NextResponse.next()` - Allows the request to proceed to its destination.
- `NextResponse.redirect()` - Redirects the response to another route.
- `NextResponse.rewrite()` - Transparently renders an alternate route internally without forcing the browser to redirect.
- `NextResponse.json()` - Returns raw JSON to the caller.
- `Response`/`NextResponse` - You can craft a custom response to the caller.

### The matcher

The matcher is how Next.js decides if a request should be processed by middleware. The matcher is defined in and exported via the `config` object like so:

```tsx
export const config = {
  matcher: '/hello',
}
```

You can also define a matcher in a number of ways. The above example matches a single route, but you can also include an array of routes:

```tsx
export const config = {
  matcher: ['/hello', '/world'],
}
```

And for more complex scenarios, you can use regex:

```tsx
export const config = {
  matcher: ['/hello', '/world', '//[a-zA-Z]+/'],
}
```

If a matcher is not specified, Next.js will use the middleware for ALL routes. This can cause the middleware to run when it really doesn't need to, which can lead to degraded performance and potentially increased hosting costs.

## How to combine multiple Next.js middleware

Next.js only supports one middleware file and function per project. If you need to use multiple functions, you'd have to create separate functions that return the appropriate response and call them in sequence, conditionally returning a response if a middleware generates one.

For example, let's say you want to use both a logging middleware and an authentication middleware.

First, create two separate middleware functions:

```tsx
export function logRequest(req) {
  console.log(`Request made to: ${req.nextUrl.pathname}`)
}
```

```tsx
import { NextResponse } from 'next/server'

// Assuming an in-memory cache for sessions
const sessions = new Map()

export function checkAuth(req) {
  const token = req.cookies.get('auth-token')
  if (!token) {
    return NextResponse.redirect(new URL('/login', req.url))
  }

  // Check if the session exists and is not expired
  if (sessions.has(token)) {
    const session = sessions.get(token)
    if (session.expiration < Date.now()) {
      // Session has expired, clear it from cache and redirect to login
      sessions.delete(token)
      return NextResponse.redirect(new URL('/login', req.url))
    }
    // Valid session, proceed with the request
    return
  } else {
    // Session not found or invalid, redirect to login
    return NextResponse.redirect(new URL('/login', req.url))
  }
}
```

Then, in your `middleware.ts` file, use both of these functions in sequence, returning the response from `checkAuth` if the authentication checks fail:

```tsx
import { NextResponse } from 'next/server'
import { checkAuth } from './middleware/checkAuth'
import { logRequest } from './middleware/logRequest'

// Main Middleware File
export function middleware(req) {
  logRequest(req)

  const authResponse = checkAuth(req)
  if (authResponse) return authResponse

  return NextResponse.next()
}

export const config = {
  matcher: ['/hello', '/world', '//[a-zA-Z]+/'],
}
```

## How does Clerk use Next.js middleware?

Clerk uses middleware to protect routes as they come into your Next.js application. The `clerkMiddleware` function actually wraps the typical middleware logic and internally will parse the cookies coming into the request and verify them with your userbase in Clerk.

```tsx
import { clerkMiddleware } from '@clerk/nextjs/server'

export default clerkMiddleware()

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

Since we wrap the middleware logic, we can extend it and provide helper functions like `auth` which makes it easier to protect routes:

```tsx
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/forum(.*)'])

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

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

The body of the callback in `clerkMiddlware` works just like a standard middleware so you can also apply custom routing rules. For example, the following snippet shows you how to reroute the incoming request to `/onboarding` only if the user is logging in for the first time:

```tsx
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

const isPublicRoute = createRouteMatcher(['/'])

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

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

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

  // All other routes are protected and the user is authenticated, let them view the requested page
})

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

> To learn more about how this onboarding flow works, check out [How to Add an Onboarding Flow for your Application with Clerk on our blog](/blog/add-onboarding-flow-for-your-application-with-clerk).

## Limitations to consider with Next.js middleware

Next.js middleware has several limitations that developers should be aware of:

### Edge Runtime Constraints

Middleware runs on the Edge Runtime, limiting the APIs and libraries that can be used. The Edge Runtime provides a subset of Node.js APIs, which means that middleware must rely on these limited resources. While this limitation may seem restrictive, it helps ensure that middleware functions are fast and efficient.

Because of the Edge Runtime, they also cannot use native Node.js APIs or perform operations like reading and writing to the file system.

### Size Restriction

Middleware functions are limited to 1MB in size, including all bundled code. This restriction is in place to ensure that middleware does not consume too much memory and can handle a large number of requests efficiently.

### ES Modules Only

Only ES Modules can be used in middleware. CommonJS modules are not supported. This is because ES Modules provide a more secure and efficient way of managing dependencies, which is essential for middleware functions.

### No String Evaluation

JavaScript's `eval` and `new Function(evalString)` are not allowed within the middleware runtime. This restriction helps prevent potential security vulnerabilities by blocking access to arbitrary code execution.

### Performance Considerations

Since middleware runs before every request with a matched route, complex or time-consuming operations in middleware can block users from receiving responses quickly. To mitigate this issue, developers should focus on writing lightweight and efficient middleware code that does not hinder the performance of their application.

It's also the reason that accessing a database within the middleware is generally not a good idea. It not only adds latency to the request but could also impact the performance of your database.

### Limited Access to Request/Response

Middleware does not have full access to complete request and response objects, which can limit certain dynamic operations. Specifically, the middleware cannot access the full URL path name, the request/response body, and some of the headers.

To work around this limitation, developers can use techniques like callbacks or promises to interact with the request and response objects.

## Conclusion

You are now better equipped to use Next.js middleware in the real world.

In this article, we have explored Next.js middleware from an introductory level but also discussed how it works, when it runs in relation to other operations Next performs with every request, and some of the best use cases for middleware.

---

# How to customize Next.js metadata
URL: https://clerk.com/blog/how-to-customize-nextjs-metadata.md
Date: 2025-01-09
Category: Guides
Description: Learn all about metadata and how to set it in your Next.js application

Improperly configured website metadata can cause drastic issues in user experience and website discoverability.

It’s important to not only understand what metadata is, but how it’s used by the greater internet, and how to configure it in your [Next.js](https://nextjs.org/) application. Next.js offers a number of different options when it comes to setting metadata, and the best option depends on the version of the framework you are using, and the way you are using it to generate pages for your visitors.

In this article, you'll learn the various ways you can customize and optimize your Next.js website's metadata to improve its SEO and user experience, as well as suggestions on when to use each approach.

## How is metadata used?

Wikipedia defines [metadata](https://en.wikipedia.org/wiki/Metadata) as "data that provides information about other data".

In the context of websites, metadata refers to the invisible data that describes the content of a website. This metadata is used by search engines, social media platforms, and other web services to understand the structure, content, and meaning of your website.

Depending on the configuration, missing or incorrect metadata can actively hurt the performance of your website. Here are some ways metadata is used:

- **Search engine optimization (SEO)**: Search engines use metadata to determine the relevance of your webpage for specific searches, as well as how those search engines rank a specific web page. The more accurate and defined the metadata is, the better a search engine will know to serve it when it matches a query.
- **Social media sharing**: Social media platforms use Open Graph metadata to display your website's content in their feeds, such as Facebook, Twitter, and LinkedIn. This is the information that's used to show stylized cards when a link is pasted in.
- **Content discovery**: Metadata helps users discover content that matches their interests and preferences.
- **Rich snippets**: Search engines can extract structured data from your website to display rich snippets in search results. Below is an example of rich snippets that are displayed with a movie to show ratings:

![An example of rich snippets that are displayed with a movie to show ratings](./schema-pic.png)

## What problems can occur with misconfigured metadata?

A number of issues can arise when metadata is not configured correctly.

For instance, poorly configured metadata can lead to lower search rankings, making it less likely users will find your website (e.g. ranking outside of the first search engine results page (SERP). This can be extremely detrimental to websites that thrive from organic traffic such as online shops. Furthermore, inaccurate or misleading metadata can damage a website's reputation and credibility, leading users to question the trustworthiness of its content.

Broken links and 404 errors can occur when metadata is incorrect or missing, creating a poor user experience. Other issues that create a poor user experience include incomplete page titles, descriptions, or images, duplicate content issues, inconsistent branding across different pages or sections, mobile usability issues, which increase the likelihood of users abandoning  if errors or inconsistencies are encountered.

By properly setting up your metadata, you can avoid these problems and create a better experience for your users.

## How does Next.js handle metadata by default?

By default, Next.js includes basic metadata such as the page title, character set, and viewport settings in the HTML head section of each page. The following is the HTML `head` of a newly generated Next.js project:

```html
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Create Next App</title>
  <meta name="description" content="Generated by create next app" />
  <meta name="next-size-adjust" content="" />
  <!-- script and link tags removed for brevity -->
</head>
```

However, this default behavior does not provide any specific metadata for SEO or social media optimization. You’ll want to add your own metadata for the application, and often on a per-page basis.

## How to customize metadata in Next.js

Next.js offers several solutions for customizing metadata depending on the application's needs.

### Using `next/head`

The first approach uses the built-in `next/head` component that can include Open Graph tags, Twitter cards, and other relevant metadata. This allows you to tailor your metadata to specific pages or routes, giving you more control over how your content is presented in search results and social media platforms.

```tsx
import Head from 'next/head'

export default function Home() {
  return (
    <>
      <Head>
        <title>My Custom Title</title>
        <meta name="description" content="This is my custom description for SEO." />
        <meta name="keywords" content="Next.js, metadata, SEO" />
        <meta property="og:title" content="My Custom Title" />
        <meta property="og:description" content="Open Graph description for sharing." />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main>
        <h1>Welcome to My Page</h1>
      </main>
    </>
  )
}
```

This approach is primarily used for client-side components where the `head` of the page needs to be changed dynamically based on the client interaction. For example, if you need to update the title or favicon of a tab in the browser.

### Exporting the `metadata` object

In versions of Next.js that use the App Router, you may also define the page metadata by exporting a variable aptly named `metadata`. This lets you define your metadata in a structure:

```tsx {{ filename: 'app/page.tsx' }}
export const metadata = {
  title: 'Clerk | Authentication and User Management',
  description:
    'The easiest way to add authentication and user management to your application. Purpose-built for React, Next.js, Remix, and “The Modern Web”.',
  keywords: ['Next.js', 'Authentication', 'User Management'],
  openGraph: {
    title: 'Clerk',
    description: 'The best user management and authentication platform.',
    url: 'https://clerk.com',
  },
}

// Code removed for brevity...
```

If you want to apply the same values across multiple pages, you can also export `metadata` from a layout file as well. This would apply the metadata to any child route of that layout.

Using this approach is best suited for pages with metadata that does not change frequently.

### Generating metadata dynamically

Finally, if you are generating metadata values on page load, you can use the `generateMetadata()` function to dynamically set the values. This is used in situations where a page template renders differently depending on the data that's loaded into it.

Using a blog as an example, there is typically one page template that's used to render every post, with the post slug being passed in as a page parameter. The following snippet demonstrates how this approach works. The slug is used to fetch the data for the post from an API before generating the metadata for that page:

```tsx {{ filename: 'app/blog/[slug]/page.tsx' }}
export async function generateMetadata({ params }) {
  const res = await fetch(`/api/posts/${params.slug}`)
  const post = await res.json()

  return {
    title: post.title,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      url: `https://clerk.com/blog/${params.slug}`,
      images: [{ url: post.image }],
    },
  }
}

export default function BlogPost({ params }) {
  return <h1>Blog Post: {params.slug}</h1>
}
```

Any place where the page is generated dynamically based on a data source would use this method.

## Next.js metadata inheritance

When customizing Next.js metadata, you don't need to specify the same values in all routes.

Next.js will automatically apply inheritance rules from the parent route to any child route if they are not defined. Using the blog example, if the root of the website has the following metadata defined in the topmost layout file:

```tsx
export const metadata = {
  title: 'Clerk | Authentication and User Management',
  description:
    'The easiest way to add authentication and user management to your application. Purpose-built for React, Next.js, Remix, and “The Modern Web”.',
  keywords: ['Next.js', 'Authentication', 'User Management'],
  openGraph: {
    title: 'Clerk',
    description: 'The best user management and authentication platform.',
    url: 'https://clerk.com',
  },
}
```

And also has this function that generates metadata for blog posts:

```tsx
export async function generateMetadata({ params }) {
  const res = await fetch(`/api/posts/${params.slug}`)
  const post = await res.json()

  return {
    title: post.title,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      url: `https://clerk.com/blog/${params.slug}`,
    },
  }
}
```

The `keywords` value would still be set on the blog post even though it is not explicitly defined in the `generateMetadata` function. This is because child metadata overwrites parent metadata, but only if a value is present.

## Conclusion

As you've learned throughout this guide, customizing metadata in Next.js can have a significant impact on user experience and how search engines rank your website. By understanding how to define and generate metadata dynamically, you'll be able to create a seamless and informative experience for your visitors.

---

# How to set environment variables in Node.js
URL: https://clerk.com/blog/how-to-set-environment-variables-in-nodejs.md
Date: 2024-12-27
Category: Company
Description: Explore the best practices and techniques you can use to set environment variables in Node.js, ensuring your app runs smoothly across different environments.

As software developers, we're constantly juggling the need for flexibility, scalability, and security in our applications.

One important aspect of achieving this balance is the proper use of environment variables. In this post, you'll learn various ways to set environment variables in Node.js, exploring best practices for their usage, as well as discussing key considerations for maintaining the security and integrity of your application. From validating environment variables at startup to avoiding committing sensitive files with Git, we'll cover essential guidelines for making the most of environment variables while minimizing potential risks.

Whether you're a seasoned developer or just starting out with Node.js, this piece aims to provide valuable insights and actionable advice for ensuring your applications are secure, maintainable, and scalable.

## What are environment variables?

[Environment variables](/glossary#environment-variables) are key-value pairs stored outside your codebase, often in a configuration file or system settings. They hold data like API keys, database credentials, or other environment-specific settings. This ensures sensitive information is not hardcoded into your application, keeping it secure and easier to manage across different environments, such as development, testing, and production.

In Node.js development, environment variables are an important concept to understand because they allow you to configure your application dynamically without modifying the code. For example, you can use the same codebase but point to different databases or APIs depending on whether you’re in a development or production environment. This flexibility improves security, simplifies deployment, and makes your app more adaptable.

Unlike regular JavaScript variables, environment variables are typically not defined in your code. Instead, they are stored in the operating system or external files (like `.env`) and accessed using `process.env`. Regular variables are scoped to your application, while environment variables exist independently and can influence multiple applications on the same system.

## Accessing environment variables in Node.js

In Node.js, the `process.env` object is used to access and manage environment variables. To retrieve a variable, you reference it using `process.env.VARIABLE_NAME`. For example, `process.env.API_KEY` fetches the value of `API_KEY`. You can technically set the value of an environment variable within the code, but this is generally counterproductive and defeats the purpose of using environment variables.

The following snippet shows how the `API_KEY` would be referenced in an Express API:

```ts
const express = require('express')
const app = express()

// Access API key from environment variables
const apiKey = process.env.API_KEY

if (!apiKey) {
  console.error('Error: API key is not defined.')
  process.exit(1)
}

app.get('/', (req, res) => {
  res.send('API key is successfully loaded.')
})

// Start the server
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
})
```

## Setting environment variables in Node.js

Now that you understand what environment variables are used for and how to access them, let's dive into the various ways to set them.

### 1. Using `dotenv`

One popular method for setting environment variables is the `dotenv` package. This package allows you to separate your environment-specific variables from your code and load them easily into your application.

You can specify the key-value pairs in a `.env` file like so:

```makefile
PORT=3000
DB_USERNAME=dbuser

```

You can then import the package and run the `config()` function which will import the values from `.env` by default:

```ts
import * as dotenv from 'dotenv'
dotenv.config()
```

Then, in your Node.js code, you can access these variables using the `process.env` object:

```ts
console.log(process.env.PORT) // Output: 3000
console.log(process.env.DB_USERNAME) // Output: dbuser
```

You can also load different environment variable files using the `path` parameter:

```ts
dotenv.config({ path: './path/to/another.env' })
```

While `dotenv` is very useful for development, other methods should likely be considered for running in a production environment.

### 2. Setting on the system level

On a Unix-based system (such as Linux or macOS), you can set environment variables at the system level by adding them to your shell configuration file such as  `~/.bashrc` for Bash or  `~/.zshrc` for ZShell. This will apply to all processes running in that shell session.

For example, add the following lines to your `~/.bashrc` file:

```{{ filename: '~/.bashrc' }}
export PORT=3000
export DB_USERNAME=myuser

```

Then, restart your terminal or run `source ~/.bashrc` to apply the changes. You can then access these variables in your Node.js application using the `process.env` object.

If you are running your application as a system process, the same values can be added to `/etc/environment` for global system access.

### 3. Setting in a launch script

You can also set environment variables at launch time by creating a script that sets the variables and then runs your Node.js application. For example, you can create a `launch.sh` file:

```bash
#!/bin/bash
export PORT=3000
export DB_USERNAME=myuser
node app.js
```

Make the script executable with `chmod +x launch.sh`, then run it using `./launch.sh`. This will set the specified environment variables and then run your Node.js application.

### 4. Setting in PM2

PM2 (Process Manager 2) is a popular process manager for Node.js applications. You can set environment variables when starting your application with PM2:

```bash
pm2 start app.js --env PORT=3000 --env DB_USERNAME=myuser
```

This will set the `PORT` and `DB_USERNAME` environment variables when running your application. You can also use a configuration file named `ecosystem.config.js` and specify values for different environments:

```js {{ filaname: 'ecosystem.config.js' }}
module.exports = {
  apps: [
    {
      name: 'my-app',
      script: 'app.js',
      instances: 1,
      env: {
        NODE_ENV: 'development',
        API_KEY: 'dev-key',
      },
      env_production: {
        NODE_ENV: 'production',
        API_KEY: 'prod-key',
      },
    },
  ],
}
```

Then, you can specify the environment to use when starting PM2:

```bash
pm2 start ecosystem.config.js --env production

```

### 5. Setting in Docker

When running a Node.js application in a Docker container, there are multiple ways that environment variables can be set. When defining the `Dockerfile`, you can use the `ENV` keyword to specify default values for environment variables:

```
FROM node:22

WORKDIR /app

ENV PORT=3000
ENV DB_USERNAME=myuser

COPY package*.json ./

RUN npm install

COPY . .

CMD ["node", "app.js"]

```

This will set the environment variables when building and running your Docker image.

When running the container, you can specify them in the command to override the default values as well:

```bash
docker run -e PORT=5173 my-image

```

Finally, when using `docker-compose` to run containers, you can specify the environment variables in the YAML:

```yaml
version: '3.8'

services:
  app:
    image: node:22
    container_name: my-app
    environment:
      - PORT=5173
```

## Best practices for using environment variables in Node.js applications

When working with environment variables in your Node.js application, there are some best practices you can follow to ensure security, maintainability, and scalability. Here are some key guidelines:

### Use descriptive names and document their purpose

When naming your environment variables, use descriptive names that clearly indicate their purpose. It's also a good idea to mention them in the project's README file with a short description documenting what each variable is used form, making it easier for other developers (or you in the future) to understand.

### Validate environment variables on app startup

Before using any environment variables, make sure they are properly set by validating them during startup. This can be done by checking if the variable exists and has a value. If not, you can default to a specific value or throw an error.

For example:

```jsx
const env = process.env
if (!env.USERNAME) {
  throw new Error('USERNAME environment variable is required')
}
```

### Avoid committing `.env` files with Git

By default, `.env` files are committed to your project's version control system (such as Git). If you are using a `.env` file to store environment variables, make sure to exclude your VCS from tracking the file. In Git, it's as simple as adding the following line to your `.gitignore` file:

```{{ filename: '.gitignore' }}
build/
dist/
node_modules

.env

```

### Consider using a KMS to increase security

To further increase the security of your environment variables, consider using a Key Management System (KMS). A KMS provides an additional layer of protection by encrypting and securely storing your sensitive data. This is especially important if you're working with sensitive information such as API keys or encryption keys.

### Specify default values

When defining environment variables, it's a good idea to specify default values for those that may not be set. This ensures that your application will continue to function even if certain environment variables are missing or unset. The default values should not contain sensitive information though.

For example, storing the connection string for a hosted database can be considered a risk. However, assuming that the project setup involves running the database locally, specifying `localhost` for the connection string would not be risky.

### Never expose environment variables in the front end

Most frameworks and tools have naming conventions developers can use to inject environment variables in to the development and building process. For example, you can use the `NEXT_PUBLIC_` prefix and any client-side code will be able to access those variables.

One of the most critical best practices is to never expose sensitive environment variables directly to the front-end. API keys and database connection strings should only ever be accessible from the server. When building with front end frameworks, make sure you understand how to securely use environment variables to avoid leaking secrets.

## How Clerk uses environment variables in Node.js projects

Clerk offers various SDKs supporting different Node.js configurations, with [Express](/docs/quickstarts/express) being our latest.

We use [environment variables](/docs/deployments/clerk-environment-variables) to configure the SDK and associate it with an application in the Clerk dashboard, which also loads any configuration items from the dashboards and allows users of that application to authenticate:

```{{ filename: '.env' }}
CLERK_PUBLISHABLE_KEY=pk_test_YmlnLXdlYXNlbC00NC5jbGVyay5hY2NvdW50cy5kZXYk
CLERK_SECRET_KEY=sk_test_frHrr**************************************

```

This allows Node.js projects to make [backend requests](/docs/backend-requests/handling/nodejs) to Clerk, as well as [front end projects to use Express](/blog/securing-node-express-apis-clerk-react) for validating API requests.

## Conclusion

In today's interconnected world, keeping your application's secrets safe is more crucial than ever. Environment variables are a fundamental part of any modern software project, but they can also be a significant security risk if not handled properly.

In this article, we've explored the best practices for using environment variables in Node.js applications, from validating their existence to avoiding exposing sensitive information directly to the front end. By following these guidelines and being mindful of potential risks, you'll be well on your way to creating secure and maintainable software that's ready for production.

---

# Building a React Login Page Template
URL: https://clerk.com/blog/building-a-react-login-page-template.md
Date: 2024-12-20
Category: Guides
Description: Learn how session-based authentication works and how to implement it in a React app with Express.

[Authentication](/glossary#authentication) is a crucial aspect of any web application that requires users to sign in and manage their accounts.

In this article, we'll be exploring how to implement a basic [authentication system using Express](/docs/quickstarts/express) as well as a signup and login form in [React.js](/glossary#react). You'll learn the difference between the JWT- and session-based authentication and some associated best practices. You'll then learn how to implement session authentication step by step using a real-world demo that's before getting access to a ready-to-use React login page template based on the steps outlined in this guide.

> For a production-ready solution, [learn more about our React support](/react-authentication) which handles all the complexity shown in this guide.

By the end of this tutorial, you'll have a solid understanding of how to create a login page using React, handle user registration, and protect routes from unauthenticated users.

We also have a guide on how to [build this same functionality into a Next.js application](/blog/building-a-nextjs-login-page-template) on our blog.

## Types of authentication

There are different ways to authenticate users, but the primary methods today are JWT-based authentication and session-based authentication.

### Session-based authentication

Session-based authentication is a method of tracking a user's login state by creating a unique [session](/glossary#session) for each authenticated user. When a user logs in, the server generates a session ID and stores session information server-side, typically in memory or a database. This session ID is then sent to the client, usually as a cookie, which the client includes with subsequent requests to prove authentication.

Cookies are used since they are sent back to the server with each request. When the server receives the session ID, it can look up the session in the database to both confirm its validity, as well as reference the associated user.

This article covers how to implement session-based authentication into a React application.

### JWT-based authentication

[JSON Web Token (JWT)](/glossary#json-web-token) authentication is an authentication method where a signed, encoded token is generated upon user login and returned to the client.

The JWT contains information such as the user ID, issue date, expiration date, etc. Once verified by the server using a private key, the server can trust that the information within the JWT correctly identifies the party that made the request.

Unlike session-based authentication, JWTs are self-contained and eliminate the need for server-side session storage.

You can read more about JWT vs. sessions in our article [Combining then benefits of session tokens and JWTs](/blog/combining-the-benefits-of-session-tokens-and-jwts).

## What is required to implement session authentication?

Implementing session-based authentication includes several key requirements.

### Storage

A storage mechanism is required to hold information about the users and the sessions. Using a database would require two tables, one for each entity. The following table diagram outlines the minimum requirements to implement session-based authentication.

![A database table diagram showing the minimum requirements to implement session-based authentication](./dbdiagram.png)

The `users` table will store general information about the user such as their name, email address, and a hashed version of the password. The `sessions` table will store details about each session.

Notice how the sessions table contains a userId field which is a foreign key of the users table. This is used to associate that session with a specific user.

### Handling sign up

Signing up the user will require a [form](/blog/validate-create-style-react-bootstrap-forms) to be created on the front end that is accessible if the user is not already authenticated. This typically includes, at minimum, inputs for username and password.

The server needs a route handler that can accept the user’s credentials when they click “Sign up”. The simplest implementation of this will create a user record and store the username and a hashed version of the password.

NEVER store the user credentials in plain text. If anyone obtains unauthorized access to your database, all of your users’ credentials will be exposed.

It’s also a best practice to implement validation on both the form the user interacts with and the route handler on the server. This ensures that any requirements for the inputs (ex: password complexity) are met before any records are created.

![A sequence diagram showing the steps involved in signing up a user](./sequence-diagram.png)

1. Using the login page in React, the user submits their username and password to the server.
2. The server creates a user record in the database.
3. The database returns the ID of the new record.
4. The server creates a session with the user ID.
5. The database returns the ID of the new record.
6. The server sets the cookie and responds back to the front end.

### Handling sign in

A login form is also required to allow the user to sign in. Again, the simplest implementation is at least a username and password.

As with handling sign-up, a route handler also needs to be created on the server to handle the credentials when they are submitted. Instead of creating a new user record, the credentials are verified with the existing user record.

If the React login page contains the proper credentials and they match when checked with the database, a session is created in the database. The token is added to a cookie that is sent back to the client.

Cookies are used for several reasons:

- They are automatically sent with each request to the server.
- Cookies can be configured to prevent client-side scripts from accessing them, making them more secure than local storage.
- An expiration can be set directly on the cookie, preventing the browser from trying to access a protected route when the session is expired.

![A sequence diagram showing the steps involved in signing in a user](./sequence-diagram.png)

1. Using the login page in React, the user submits their credentials to the server.
2. The server queries the user record to check the credentials.
3. The database returns the user record if found.
4. The server compares the hashed passwords. If successful, create a new session in the database.
5. The database responds with the ID of the session
6. The server sets the session ID in the cookie and sends it back to the client.

While this article will guide you through implementing session authentication in your React app, [check out how Clerk can accomplish it with only a few lines of code!](/docs/quickstarts/react)

### Protecting authorized routes

Finally, routes on both the front end and back end need to be configured to be protected. In a typical React application using `react-router-dom`, Routes can be wrapped in a parent component which will check the authorization status with the server before rendering them. When the React login page is used to authenticate a user, the `<ProtectedRoute />` component will automatically permit access to those views.

```tsx
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider } from '@/hooks/use-auth'
import { ProtectedRoute } from '@/components/protected-route'
import Home from '@/views/home'
import AppView from '@/views/app'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          {/* The following route is protected */}
          <Route
            path="/app"
            element={
              <ProtectedRoute>
                <AppView />
              </ProtectedRoute>
            }
          />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

On the server, the session ID that is sent to the server will be checked with the `sessions` table in the database to make sure the sessions is valid before processing the request. In [Express](https://expressjs.com), middleware can be created that wraps routes or route collections to make sure this is done automatically on the proper requests.

## Follow along with Quillmate

To demonstrate how session-based authentication can be implemented, we’ll use a realistic project called Qulllmate.

Quillmate is an open-source web application where writers can create articles with the help of an AI assistant. The core entity of Quillmate is an article, and each article has its own AI assistant that understands what has already been written and helps when asked questions about the material.

The following video demonstrates the finished product, where the user can sign up, sign in, create articles, and ask AI for assistance:

Quillmate is built with React and uses Express to both serve the application, as well as handle backend requests. The project uses Open AI to ask questions about articles, and Neon to store the data.

Quillmate is built with the following tech stack:

- **React** - The front end of the application is built with React.
- **Express** - The application is hosted with Express. Requests to paths starting with `/api` will be handled by various API routes, whereas any other requests will serve up the React app.
- **Neon** - Neon is a PostgreSQL database platform that is used for storing structured data.
- **Prisma** - To simplify requests to the database, Prisma is used as an ORM.
- **Open AI** - The AI Assistant functionality is backed by requests to the Open AI API.

You may optionally clone the [quillmate-react](https://github.com/bmorrisondev/quillmate-react) repository to follow along yourself with this article. Follow the directions in the readme to get the project running on your own system.

## Adding the database tables

Let’s start by adding the `users` table and `sessions` table. This can be done by modifying the Prisma schema and running a script to apply the changes to the Neon database.

Modify the `prisma/schema.prisma` file and add the `User` and `Session` models:

```{{ filename: 'prisma/schema.prisma', prettier: false }}
model User {
  id           Int       @id @default(autoincrement())
  email        String    @unique
  name         String?
  passwordHash String    @map("password_hash")
  sessions     Session[]
  createdAt    DateTime  @default(now()) @map("created_at")
  updatedAt    DateTime  @default(now()) @updatedAt @map("updated_at")
  articles     Article[]

  @@map("users")
}

model Session {
  id        Int      @id @default(autoincrement())
  token     String   @unique
  userId    Int      @map("user_id")
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  @@map("sessions")
}
```

Next, update the `Article` model to include a relationship with the user:

```{{ filename: 'prisma/schema.prisma', ins: [9,10], prettier: false }}
model Article {
  id        Int      @id @default(autoincrement())
  title     String   @db.VarChar(256)
  content   String   @db.Text
  summary   String?  @db.Text
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
  messages  Message[]
  user      User     @relation(fields: [userId], references: [id])
  userId    Int      @map("user_id")

  @@map("articles")
}
```

Finally, push the schema changes to Neon and update the local client by running the following commands in your terminal:

```bash
npx prisma db push
npx prisma generate
```

## Updating Express to support authentication

Next, you’ll need to update the backend to support creating accounts, creating sessions, and protecting the necessary routes.

### Install dependencies

The following dependencies are required:

- `bcryptjs` - Used for salting and hashing passwords before saving them to the database.
- `cookie-parser` - An Express middleware that makes working with cookies easier.
- [`zod`](https://zod.dev) - Used for type validation.

Run the following commands to install those packages and their types:

```bash
npm install bcryptjs cookie-parser zod
npm install -D @types/bcryptjs @types/cookie-parser
```

### Create the Express auth middleware

Next, create the Express middleware that will be used to protect routes. This will be used by protected routes to make sure that the request is authorized, returning a 401 status if it is not. It will also handle removing the session from the database if it’s expired.

Create a file at `server/middleware/auth.ts` and paste in the following:

```ts {{ filename: 'server/middleware/auth.ts' }}
import { Request, Response, NextFunction } from 'express'
import { prisma } from '../db/prisma'

export async function requireAuth(req: Request, res: Response, next: NextFunction) {
  try {
    const token = req.cookies.session

    if (!token) {
      res.status(401).json({ error: 'Authentication required' })
      return
    }

    const session = await prisma.session.findUnique({
      where: { token },
      include: { user: true },
    })

    if (!session) {
      res.clearCookie('session')
      res.status(401).json({ error: 'Invalid session' })
      return
    }

    if (session.expiresAt < new Date()) {
      await prisma.session.delete({ where: { id: session.id } })
      res.clearCookie('session')
      res.status(401).json({ error: 'Session expired' })
      return
    }

    req.user = {
      id: session.user.id,
      email: session.user.email,
      name: session.user.name,
    }
    req.session = {
      id: session.id,
      token: session.token,
    }

    next()
  } catch (error) {
    console.error('Auth middleware error:', error)
    res.status(500).json({ error: 'Internal server error' })
  }
}
```

If your editor is complaining about setting the `req.user` and `req.session` values, create a file at `server/types/express.d.ts` and paste in the following:

```ts {{ filename: 'server/types/express.d.ts' }}
declare global {
  namespace Express {
    interface Request {
      user?: {
        id: number
        email: string
        name: string | null
      }
      session?: {
        id: number
        token: string
      }
    }
  }
}

export {}
```

### Add routes to handle auth functions

Now let’s add the routes required to enable sign-up and sign-in. The route file will also include validation from  There will be four routes in total:

- `/api/signin` - Used by the React login page to create a session and return it back to the user.
- `/api/signup` - Used to create an account, which we’ll use to also create a session right away.
- `/api/signout` - Used to sign the user out, which deletes the session. This route is protected by the middleware.
- `/api/me` - Used by the front end to verify that the session is valid before loading the articles. This route is also protected by the middleware.

Create a file at `server/routes/auth.ts` and paste in the following:

```ts {{ filename: 'server/routes/auth.ts' }}
import { Router } from 'express'
import { prisma } from '../db/prisma'
import bcrypt from 'bcryptjs'
import { randomBytes } from 'crypto'
import { ZodError } from 'zod'
import { requireAuth } from '../middleware/auth'
import { z } from 'zod'

const router = Router()

const signUpSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
    .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
    .regex(/[0-9]/, 'Password must contain at least one number'),
  name: z.string().optional(),
})

// Sign up
router.post('/signup', async (req, res) => {
  try {
    const result = signUpSchema.safeParse(req.body)
    if (!result.success) {
      res.status(400).json({
        error: 'Validation failed',
        details: result.error.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }

    const { email, password, name } = result.data

    // Validate input
    if (!email || !password) {
      res.status(400).json({ error: 'Email and password are required' })
      return
    }

    // Check if user exists
    const existingUser = await prisma.user.findUnique({
      where: { email },
    })

    if (existingUser) {
      res.status(400).json({ error: 'Email already registered' })
      return
    }

    // Hash password
    const salt = await bcrypt.genSalt(10)
    const passwordHash = await bcrypt.hash(password, salt)

    // Create user
    const user = await prisma.user.create({
      data: {
        email,
        passwordHash,
        name,
      },
    })

    // Create session
    const token = randomBytes(32).toString('hex')
    const expiresAt = new Date()
    expiresAt.setDate(expiresAt.getDate() + 30) // 30 days from now

    await prisma.session.create({
      data: {
        token,
        userId: user.id,
        expiresAt,
      },
    })

    // Set cookie
    res.cookie('session', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      expires: expiresAt,
    })

    res.json({
      id: user.id,
      email: user.email,
      name: user.name,
    })
  } catch (err) {
    console.error('Sign up error:', err)
    if (err instanceof ZodError) {
      res.status(400).json({
        error: 'Validation failed',
        details: err.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }
    res.status(500).json({ error: 'Internal server error' })
  }
})

const signInSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(1, 'Password is required'),
})
// Sign in
router.post('/signin', async (req, res) => {
  try {
    const result = signInSchema.safeParse(req.body)
    if (!result.success) {
      res.status(400).json({
        error: 'Validation failed',
        details: result.error.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }

    const { email, password } = result.data

    // Validate input
    if (!email || !password) {
      res.status(400).json({ error: 'Email and password are required' })
      return
    }

    // Find user
    const user = await prisma.user.findUnique({
      where: { email },
    })

    if (!user) {
      res.status(401).json({ error: 'Invalid credentials' })
      return
    }

    // Verify password
    const isValid = await bcrypt.compare(password, user.passwordHash)
    if (!isValid) {
      res.status(401).json({ error: 'Invalid credentials' })
      return
    }

    // Create session
    const token = randomBytes(32).toString('hex')
    const expiresAt = new Date()
    expiresAt.setDate(expiresAt.getDate() + 30) // 30 days from now

    await prisma.session.create({
      data: {
        token,
        userId: user.id,
        expiresAt,
      },
    })

    // Set cookie
    res.cookie('session', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      expires: expiresAt,
    })

    res.json({
      id: user.id,
      email: user.email,
      name: user.name,
    })
  } catch (err) {
    console.error('Sign in error:', err)
    if (err instanceof ZodError) {
      res.status(400).json({
        error: 'Validation failed',
        details: err.errors.map((err) => ({
          path: err.path.join('.'),
          message: err.message,
        })),
      })
      return
    }
    res.status(500).json({ error: 'Internal server error' })
  }
})

// Sign out
router.post('/signout', requireAuth, async (req, res) => {
  try {
    const token = req.cookies.session
    if (token) {
      await prisma.session.delete({
        where: { token },
      })
      res.clearCookie('session')
    }
    res.json({ message: 'Signed out successfully' })
  } catch (error) {
    console.error('Signout error:', error)
    res.status(500).json({ error: 'Failed to sign out' })
  }
})

// Get current user
router.get('/me', requireAuth, async (req, res) => {
  try {
    const token = req.cookies.session
    if (!token) {
      res.json({ user: null })
      return
    }

    const session = await prisma.session.findUnique({
      where: { token },
      include: { user: true },
    })

    if (!session || session.expiresAt < new Date()) {
      res.clearCookie('session')
      res.json({ user: null })
      return
    }

    res.json({
      id: session.user.id,
      email: session.user.email,
      name: session.user.name,
    })
  } catch (error) {
    console.error('Get current user error:', error)
    res.status(500).json({ error: 'Failed to get current user' })
  }
})

export default router
```

### Update the `/api/articles` route to filter by user

Next, we’ll need to update the `/api/articles` routes to ensure that when database records are created, the user information is saved with the record as well. This will allow queries to return only the articles created by that user.

Update `server/routes/articles.ts` like so:

```ts {{ filename: 'server/routes/articles.ts', ins: [[10, 13], [16, 18], [22, 30], [42, 45], [48, 60], [78, 81], [90, 100], [112, 115], 124, 125, [134, 136], [143, 151], [164, 168], 175, 176] }}
import { Router } from 'express'
import { prisma } from '../db/prisma'
import { requireAuth } from '../middleware/auth'

const router = Router()

// Get all articles
router.get('/', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const allArticles = await prisma.article.findMany({
      where: {
        userId: req.user.id,
      },
      orderBy: {
        updatedAt: 'desc',
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })
    res.json(allArticles)
  } catch (error) {
    console.error('Error fetching articles:', error)
    res.status(500).json({ error: 'Failed to fetch articles' })
  }
})

// Get single article
router.get('/:id', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const article = await prisma.article.findUnique({
      where: {
        id: parseInt(req.params.id),
        userId: req.user.id,
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    if (!article) {
      res.status(404).json({ error: 'Article not found' })
      return
    }

    res.json(article)
  } catch (error) {
    console.error('Error fetching article:', error)
    res.status(500).json({ error: 'Failed to fetch article' })
  }
})

// Create article
router.post('/', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const { title, content, summary } = req.body

    const newArticle = await prisma.article.create({
      data: {
        title,
        content,
        summary,
        userId: req.user.id,
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })
    res.json(newArticle)
  } catch (error) {
    console.error('Error creating article:', error)
    res.status(500).json({ error: 'Failed to create article' })
  }
})

// Update article
router.put('/:id', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const { title, content, summary } = req.body
    const articleId = parseInt(req.params.id)

    // First verify the article belongs to the user
    const article = await prisma.article.findUnique({
      where: {
        id: articleId,
        userId: req.user.id,
      },
    })

    if (!article) {
      res.status(404).json({ error: 'Article not found' })
      return
    }

    const updatedArticle = await prisma.article.update({
      where: {
        id: articleId,
      },
      data: {
        title,
        content,
        summary,
        updatedAt: new Date(),
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    res.json(updatedArticle)
  } catch (error) {
    console.error('Error updating article:', error)
    res.status(500).json({ error: 'Failed to update article' })
  }
})

// Delete article
router.delete('/:id', async (req, res) => {
  try {
    if (!req.user) {
      res.status(401).json({ error: 'User not found' })
      return
    }

    const articleId = parseInt(req.params.id)

    // First verify the article belongs to the user
    const article = await prisma.article.findUnique({
      where: {
        id: articleId,
        userId: req.user.id,
      },
    })

    if (!article) {
      res.status(404).json({ error: 'Article not found' })
      return
    }

    const deletedArticle = await prisma.article.delete({
      where: {
        id: articleId,
      },
    })

    res.json(deletedArticle)
  } catch (error) {
    console.error('Error deleting article:', error)
    res.status(500).json({ error: 'Failed to delete article' })
  }
})

export default router
```

### Update the Express server entry point

The last thing to do on the server is update `server/index.ts` to set up `cookie-parser`, register the `/api/auth` routes, and protect the `/api/articles` and `/api/ai` routes.

Update `server/index.ts` as follows:

```ts {{ filename: 'server/index.ts', ins: [[10, 12], 27, 28, 37, 40, 41] }}
import express from 'express'
import path from 'path'
import cors from 'cors'
import dotenv from 'dotenv'
dotenv.config()

import { createProxyMiddleware } from 'http-proxy-middleware'
import articlesRouter from './routes/articles'
import aiRouter from './routes/ai'
import cookieParser from 'cookie-parser'
import authRoutes from './routes/auth'
import { requireAuth } from './middleware/auth'

console.log(`NODE_ENV: ${process.env.NODE_ENV}`)

const app = express()
const PORT = process.env.PORT || 3000
const VITE_PORT = process.env.VITE_PORT || 5173

// Middleware
app.use(
  cors({
    origin:
      process.env.NODE_ENV === 'production' ? process.env.FRONTEND_URL : 'http://localhost:5173',
    credentials: true,
  }),
)
app.use(cookieParser())
app.use(express.json())

// API routes
app.use('/api', (req, res, next) => {
  console.log(`API Request: ${req.method} ${req.url}`)
  next()
})

// Public routes
app.use('/api/auth', authRoutes)

// Protected routes
app.use('/api/articles', requireAuth, articlesRouter)
app.use('/api/ai', requireAuth, aiRouter)

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' })
})

// Development: Proxy all non-API requests to Vite dev server
if (process.env.NODE_ENV !== 'production') {
  app.use(
    '/',
    createProxyMiddleware({
      target: `http://localhost:${VITE_PORT}`,
      changeOrigin: true,
      ws: true,
      // Don't proxy /api requests
      filter: (pathname: string) => !pathname.startsWith('/api'),
    }),
  )
} else {
  // Production: Serve static files
  app.use(express.static(path.join(__dirname, '../dist')))

  // Handle React routing in production
  app.get('*', (req, res) => {
    if (!req.path.startsWith('/api')) {
      res.sendFile(path.join(__dirname, '../dist/index.html'))
    }
  })
}

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`)
  if (process.env.NODE_ENV !== 'production') {
    console.log(`Proxying non-API requests to http://localhost:${VITE_PORT}`)
  }
})
```

## Implementing the login page in React

With the backend updated to support authentication, let’s update the frontend to do the same.

### Create a context, provider, and hook for global auth

Since the authentication state can affect the way the entire application behaves, we’ll create a React Context and Provider so we can check if the user is logged in from anywhere. We’ll also add the logic to sign up, sign in, and sign out from the context, making it easy to call those functions from various points of the app.

Create the `src/hooks/use-auth.tsx` file and add the following code:

```tsx {{ filename: 'src/hooks/use-auth.tsx' }}
import { createContext, useContext, useState, useEffect } from 'react'
import { User } from '@/types'

interface AuthContextType {
  user: User | null
  signIn: (email: string, password: string) => Promise<void>
  signUp: (email: string, password: string, name?: string) => Promise<void>
  signOut: () => Promise<void>
  isLoading: boolean
}

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

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    checkAuth()
  }, [])

  // Used to check the session status with the server
  const checkAuth = async () => {
    try {
      const response = await fetch('/api/auth/me', {
        credentials: 'include',
      })
      const data = await response.json()
      setUser(data)
    } catch (error) {
      console.error('Failed to check auth status:', error)
      setUser(null)
    } finally {
      setIsLoading(false)
    }
  }

  // Used to create a session and store the user data in the context
  const signIn = async (email: string, password: string) => {
    const response = await fetch('/api/auth/signin', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({ email, password }),
    })

    if (!response.ok) {
      const error = await response.json()
      throw new Error(error.error || 'Failed to sign in')
    }

    const data = await response.json()
    setUser(data)
  }

  // Used to sign up a new user, create a session, and store the user data in the context
  const signUp = async (email: string, password: string, name?: string) => {
    const response = await fetch('/api/auth/signup', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({ email, password, name }),
    })

    if (!response.ok) {
      const error = await response.json()
      throw new Error(error.error || 'Failed to sign up')
    }

    const data = await response.json()
    setUser(data)
  }

  // Used to sign out a user and clear the user data from the context
  const signOut = async () => {
    await fetch('/api/auth/signout', {
      method: 'POST',
      credentials: 'include',
    })
    setUser(null)
  }

  return (
    <AuthContext.Provider value={{ user, signIn, signUp, signOut, isLoading }}>
      {children}
    </AuthContext.Provider>
  )
}

// Custom hook to access the auth context from any component
export function useAuth() {
  const context = useContext(AuthContext)
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider')
  }
  return context
}
```

Next, update the `src/App.tsx` file to wrap the entire application with the provider:

```tsx {{ filename: 'src/App.tsx', ins: [4, 8, 16] }}
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import Home from '@/views/home'
import AppView from '@/views/app'
import { AuthProvider } from '@/hooks/use-auth'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/app" element={<AppView />} />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

### Add sign-up and sign-in views

Next, let’s add the sign-up and sign-in views. These views will both be very similar, each containing form elements where the user can enter their credentials. Both also use `zod` for client-side validation, which prevents an unnecessary network trip to the server if the user does not populate the fields properly. If validation fails, errors will be shown on the respective fields.

The key difference between them is what happens when the user submits the form, specifically, the method called from the `useAuth` hook created earlier.

Create the `src/views/sign-in.tsx` page and populate it with the following code:

```tsx {{ filename: 'src/views/sign-in.tsx' }}
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useAuth } from '@/hooks/use-auth'
import { z, ZodError } from 'zod'

const signInSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(1, 'Password is required'),
})

export default function SignIn() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
  })
  const [errors, setErrors] = useState<{
    email?: string
    password?: string
    submit?: string
  }>({})
  const [isSubmitting, setIsSubmitting] = useState(false)
  const navigate = useNavigate()
  const { signIn } = useAuth()

  const handleSubmit = async (data: typeof formData) => {
    try {
      setIsSubmitting(true)
      setErrors({})

      // Validate the form data
      const validatedData = signInSchema.parse(data)

      // Attempt sign in
      await signIn(validatedData.email, validatedData.password)
      navigate('/app')
    } catch (error) {
      if (error instanceof ZodError) {
        const formattedErrors: Record<string, string> = {}
        error.errors.forEach((err) => {
          if (err.path) {
            formattedErrors[err.path[0]] = err.message
          }
        })
        setErrors(formattedErrors)
      } else {
        setErrors({ submit: 'Failed to sign in. Please try again.' })
      }
    } finally {
      setIsSubmitting(false)
    }
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setFormData((prev) => ({ ...prev, [name]: value }))
    // Clear error when user starts typing
    if (errors[name as keyof typeof errors]) {
      setErrors((prev) => ({ ...prev, [name]: undefined }))
    }
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-purple-50/30 p-4">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle className="text-center text-2xl">Sign In</CardTitle>
        </CardHeader>
        <CardContent>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              handleSubmit(formData)
            }}
            className="space-y-4"
          >
            <div className="space-y-2">
              <label htmlFor="email" className="text-sm font-medium">
                Email
              </label>
              <Input
                id="email"
                name="email"
                type="email"
                value={formData.email}
                onChange={handleChange}
                required
                placeholder="Enter your email"
                className={errors.email ? 'border-red-500' : ''}
              />
              {errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
            </div>
            <div className="space-y-2">
              <label htmlFor="password" className="text-sm font-medium">
                Password
              </label>
              <Input
                id="password"
                name="password"
                type="password"
                value={formData.password}
                onChange={handleChange}
                required
                placeholder="Enter your password"
                className={errors.password ? 'border-red-500' : ''}
              />
              {errors.password && <p className="text-sm text-red-500">{errors.password}</p>}
            </div>
            {errors.submit && <p className="text-center text-sm text-red-500">{errors.submit}</p>}
            <Button type="submit" className="w-full" disabled={isSubmitting}>
              {isSubmitting ? 'Signing In...' : 'Sign In'}
            </Button>
            <div className="text-center text-sm">
              Don't have an account?{' '}
              <a href="/signup" className="text-purple-600 hover:text-purple-500">
                Sign up
              </a>
            </div>
          </form>
        </CardContent>
      </Card>
    </div>
  )
}
```

Do the same with `src/views/sign-up.tsx`:

```tsx {{ filename: 'src/views/sign-up.tsx' }}
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useAuth } from '@/hooks/use-auth'
import { z, ZodError } from 'zod'

const signUpSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z
    .string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
    .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
    .regex(/[0-9]/, 'Password must contain at least one number'),
  name: z.string().optional(),
})

export default function SignUp() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    name: '',
  })
  const [errors, setErrors] = useState<{
    email?: string
    password?: string
    name?: string
    submit?: string
  }>({})
  const [isSubmitting, setIsSubmitting] = useState(false)
  const navigate = useNavigate()
  const { signUp } = useAuth()

  const handleSubmit = async (data: typeof formData) => {
    try {
      setIsSubmitting(true)
      setErrors({})

      // Validate the form data
      const validatedData = signUpSchema.parse(data)

      // Attempt sign up
      await signUp(validatedData.email, validatedData.password, validatedData.name)
      navigate('/app')
    } catch (error) {
      if (error instanceof ZodError) {
        const formattedErrors: Record<string, string> = {}
        error.errors.forEach((err) => {
          if (err.path) {
            formattedErrors[err.path[0]] = err.message
          }
        })
        setErrors(formattedErrors)
      } else {
        setErrors({ submit: 'Failed to create account. Please try again.' })
      }
    } finally {
      setIsSubmitting(false)
    }
  }

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target
    setFormData((prev) => ({ ...prev, [name]: value }))
    // Clear error when user starts typing
    if (errors[name as keyof typeof errors]) {
      setErrors((prev) => ({ ...prev, [name]: undefined }))
    }
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-purple-50/30 p-4">
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle className="text-center text-2xl">Sign Up</CardTitle>
        </CardHeader>
        <CardContent>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              handleSubmit(formData)
            }}
            className="space-y-4"
          >
            <div className="space-y-2">
              <label htmlFor="email" className="text-sm font-medium">
                Email
              </label>
              <Input
                id="email"
                name="email"
                type="email"
                value={formData.email}
                onChange={handleChange}
                required
                placeholder="Enter your email"
                className={errors.email ? 'border-red-500' : ''}
              />
              {errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
            </div>
            <div className="space-y-2">
              <label htmlFor="password" className="text-sm font-medium">
                Password
              </label>
              <Input
                id="password"
                name="password"
                type="password"
                value={formData.password}
                onChange={handleChange}
                required
                placeholder="Create a password"
                className={errors.password ? 'border-red-500' : ''}
              />
              {errors.password && <p className="text-sm text-red-500">{errors.password}</p>}
            </div>
            <div className="space-y-2">
              <label htmlFor="name" className="text-sm font-medium">
                Name (optional)
              </label>
              <Input
                id="name"
                name="name"
                type="text"
                value={formData.name}
                onChange={handleChange}
                placeholder="Enter your name"
                className={errors.name ? 'border-red-500' : ''}
              />
              {errors.name && <p className="text-sm text-red-500">{errors.name}</p>}
            </div>
            {errors.submit && <p className="text-center text-sm text-red-500">{errors.submit}</p>}
            <Button type="submit" className="w-full" disabled={isSubmitting}>
              {isSubmitting ? 'Creating Account...' : 'Create Account'}
            </Button>
          </form>
        </CardContent>
      </Card>
    </div>
  )
}
```

Now register the views in `src/App.tsx`:

```tsx {{ filename: 'src/App.tsx', ins: [5, 6, 14, 15] }}
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import Home from '@/views/home'
import AppView from '@/views/app'
import { AuthProvider } from '@/hooks/use-auth'
import SignIn from '@/views/auth/sign-in'
import SignUp from '@/views/auth/sign-up'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/signin" element={<SignIn />} />
          <Route path="/signup" element={<SignUp />} />
          <Route path="/app" element={<AppView />} />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

### Protecting the App page

To protect routes, we’ll create a separate component that will use the context & provider and check the authentication state before allowing the user to proceed. If the authentication state is being checked by the provider, a “Loading…” message will be rendered for the user. If the user is not logged in, they will be redirected to the sign-in view.

Create the `src/components/protected-route.tsx` file and add the following:

```tsx {{ filename: 'src/components/protected-route.tsx' }}
import { Navigate } from 'react-router-dom'
import { useAuth } from '@/hooks/use-auth'

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, isLoading } = useAuth()

  if (isLoading) {
    return (
      <div className="flex min-h-screen items-center justify-center bg-purple-50/30">
        <div className="text-purple-600">Loading...</div>
      </div>
    )
  }

  if (!user) {
    return <Navigate to="/signin" replace />
  }

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

Update `src/App.tsx` and wrap the element for the `/app` route in the `<ProtectedRoute>` component:

```tsx {{ filename: 'src/App.tsx', ins: [7, 8, [18, 25]], del: [15] }}
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider } from '@/hooks/use-auth'
import SignIn from '@/views/auth/sign-in'
import SignUp from '@/views/auth/sign-up'
import Home from '@/views/home'
import AppView from '@/views/app'
import { ProtectedRoute } from '@/components/protected-route'

function AppRoot() {
  return (
    <AuthProvider>
      <Router>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/app" element={<AppView />} />
          <Route path="/signin" element={<SignIn />} />
          <Route path="/signup" element={<SignUp />} />
          <Route
            path="/app"
            element={
              <ProtectedRoute>
                <AppView />
              </ProtectedRoute>
            }
          />
          <Route path="*" element={<Navigate to="/" replace />} />
        </Routes>
      </Router>
    </AuthProvider>
  )
}

export default AppRoot
```

### Add a sign-out button

The last thing to add is a sign-out button that lets users log out of the app once they are finished. This will use the `signOut` function of the `useAuth` hook to remove the session from the database and clear the cookie set in the browser.

Update `src/components/article-list.tsx` with the following:

```jsx {{ filename: 'src/components/article-list.tsx', ins: [4, 19, [49, 57]], prettier: false }}
import { Button } from "@/components/ui/button"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Article } from "@/types"
import { useAuth } from "@/hooks/use-auth"

interface ArticleListProps {
  articles: Article[]
  selectedArticle: Article | null
  onArticleSelect: (article: Article) => void
  onNewArticle: () => void
}

export function ArticleList({
  articles,
  selectedArticle,
  onArticleSelect,
  onNewArticle,
}: ArticleListProps) {
  const { signOut } = useAuth()

  return (
    <div className="flex h-full flex-col">
      <div className="border-b border-purple-100 p-4 bg-white">
        <div className="flex justify-between items-center">
          <h2 className="text-lg font-semibold">Articles</h2>
          <Button onClick={onNewArticle} size="sm">New</Button>
        </div>
      </div>
      <ScrollArea className="flex-1">
        <div className="space-y-4 p-4">
          {articles.map(article => (
            <div
              key={article.id}
              className={`p-4 rounded-lg cursor-pointer transition-colors ${
                selectedArticle?.id === article.id
                  ? 'bg-purple-100'
                  : 'hover:bg-purple-50'
              }`}
              onClick={() => onArticleSelect(article)}
            >
              <h3 className="font-medium mb-1">{article.title}</h3>
              <div className="text-sm text-gray-500">
                <p>Updated {new Date(article.updatedAt).toLocaleDateString()}</p>
              </div>
            </div>
          ))}
        </div>
      </ScrollArea>
      <div className="border-b border-t border-purple-100 p-4 bg-white">
        <Button
          onClick={() => signOut()}
          variant="outline"
          className="w-full"
        >
          Sign Out
        </Button>
      </div>
    </div>
  )
}
```

## Testing the app

With all the changes in place, you may test the application! Run the application with the following command in your terminal:

```bash
npm run dev
```

Now open `localhost:3000` in your browser and try creating a user to use the application, create an article, and experiment with the AI functionality! I encourage you to also log into Neon and check the contents of the database, specifically the `users` and `sessions` tables as they contain the records needed to support authentication.

## So why Clerk then?

Clerk is a user management and authentication platform, so it might surprise you that we’re publishing an article walking you through how to implement authentication yourself.

This article covers how to implement a single method of authentication, but in reality, user management is much more than just session-based authentication. For example, it’s commonplace in modern web applications to also support social login providers like [Google](/blog/add-google-login-next13-app) or Apple. A password reset flow is also a critical requirement for supporting your own authentication setup.

These are just a few of the many features Clerk supports out of the box, oftentimes with a single line of code.

Using Clerk, you can easily create a sign-in page by just importing and rendering the [`<SignIn />`](/docs/components/authentication/sign-in) [component](/docs/components/overview) like so:

```tsx
import { SignIn } from '@clerk/clerk-react'

export default function SignInView() {
  return <SignIn />
}
```

If you want to learn how easy it is to get Clerk working in a React application, check out the [quickstart on our docs](/docs/quickstarts/react).

If you want to download a template version of the React login page template to use in your own project, check out the [react-session-auth-template](https://github.com/bmorrisondev/react-session-auth-quickstart) repository on GitHub.

## Conclusion

In conclusion, this article has walked you through how to implement a basic authentication system using React and Express. By setting up a session-based authentication flow, we've covered how to create a sign-in page, handle user registration, log users in and out, as well as protect routes with a simple `ProtectedRoute` component.

While implementing a custom authentication system can be a great learning experience, it's also important to consider the larger picture of what user management really entails. In reality, you'll often need to support features like [social login](/blog/social-sso-in-next-js) providers, password reset flows, and more.

That's where Clerk comes in - a [user management and authentication platform](https://clerk.com) that simplifies the process of implementing these features with just a few lines of code. If you're interested in learning more about how easy it is to get Clerk working in a React application, check out the [quickstart guide](/docs/quickstarts/react) on our documentation site.

---

# How to implement per-user OAuth scopes with Clerk
URL: https://clerk.com/blog/implement-per-user-oauth-with-clerk.md
Date: 2024-12-13
Category: Guides
Description: Learn how to implement per-user OAuth scopes with Clerk.

When developing a SaaS application that relies on third-party data, it's important to recognize that not all users will want to grant access to their data unless it's essential for the application's functionality.

In a recent article, I covered how you can use [Clerk Single Sign On (SSO)](/docs/authentication/social-connections/overview) connections to access Google Calendar data on behalf of the user. When configuring SSO, scopes are used to inform the service provider (SP) what kind of access is required for the connection that is being established. It’s important to specify only the scopes that are required and no more, a variation of the least privilege access principle, however, some users might require more access than others.

In this article, you’ll learn about the concept of least privilege access, and how to customize OAuth scopes on a per-user basis.

## What is least privilege access?

[Least privilege access](/glossary#least-priveledge-access) is a security principle where users are given the minimum levels of access or permissions needed.

This approach limits potential damage from system breaches by restricting each user's ability to interact with systems, networks, and data beyond their essential requirements. By granting only the precise access rights necessary for a user's role, organizations can significantly reduce their overall cybersecurity risk and potential attack surface.

Typically least privilege access is used in the context of users accessing a system, however, the same principle can be applied when services are accessing user data in another system.

[OAuth scopes](/glossary#oauth-scopes) allow the user to make an educated decision about what data the system should be able to access. A system that only asks for the minimum access required can not only help ease the user's concern about the data being accessed, but it could also protect the developer in situations where attackers gain access to areas of the system they shouldn’t.

For example, if you build a system that only requires access to [Google Calendar](http://calendar.google.com) data, but you simply ask for access to ALL the user's data, you could be held liable if someone deleted the user's Google Docs!

## Implementing user-specific OAuth scopes with Clerk

To demonstrate how applications can be configured to only request access to the necessary permissions, we’ll use BookMate as a demo.

[BookMate](https://bookmate-sigma.vercel.app) is a lightweight clone of popular scheduling tools like [Cal.com](http://Cal.com) or Calendly. Users can link their Google Calendar so visitors can request a meeting with them using a public profile. When a visitor requests a meeting, Bookmate will send both parties a calendar invite via email.

To accommodate this functionality, BookMate uses the following OAuth scopes from Google:

- `https://www.googleapis.com/auth/calendar.readonly`
- `https://www.googleapis.com/auth/calendar.events.readonly`

These scopes are defined in the [Clerk dashboard](https://dashboard.clerk.com/apps) under the Google SSO settings and automatically apply to all users who sign into the app.

Let’s add a feature that also allows the calendar entry to be added directly to the user’s calendar instead of just sending an invite, further streamlining the process. To accommodate this, we’ll also need the following scope to be added to the login process:

- `https://www.googleapis.com/auth/calendar.events`

This feature will be optional and we wouldn't want to apply this scope to all users since it allows BookMate to directly modify the user's calendar. Therefore we cannot simply add it to the list of scopes in the Clerk dashboard.

To accomplish this new functionality, we’ll take the following steps:

1. Add a toggle to the settings that allows BookMate to add events to the user's calendar. When the toggle is enabled, check the existing scopes on the Google [access token](/glossary#access-token) for the current user.
2. If the access token does not have that scope, initiate a reauthorization that requires the user to allow access to their calendar, adding the scope to the token.
3. Storing the user-specific scopes with the user’s public metadata in Clerk.
4. Add a global provider that will automatically handle reauthorization if the scope is required.

The source code for this article is available [on GitHub](https://github.com/bmorrisondev/bookmate). The following sections will cover the functionality of the code at a high level, I encourage you to check the repository to see more specifically how the code works together!

### Adding the toggle to check the user's scopes

The first step is to add a new checkbox that informs the application that the additional scope is required. This checkbox will also render a dropdown for the user to select the calendar they want to add the event to. If the external account does not have the proper scopes, the application will trigger a reauthorization process with Google SSO to gain access to the proper scopes before saving the preferences to the database.

The following snippet is for the `CalendarSelector` component which contains the logic described above, with notable lines commented:

```tsx
'use client'

import { useState, useEffect } from 'react'
import { Calendar, Loader2 } from 'lucide-react'
import { getGoogleToken } from '../actions'
import { useUser } from '@clerk/nextjs'
import { useToast } from '@/hooks/use-toast'
import { saveCalendars } from '../_actions/save-calendars'
import { getCalendars } from '../_actions/get-calendars'
import { saveCalendarPreferences } from '../_actions/save-calendar-preferences'
import { getCalendarPreferences } from '../_actions/get-calendar-preferences'
import { RefreshButton } from './refresh-button'
import { CalendarItem } from './calendar-item'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'

interface GoogleCalendar {
  id: string
  summary: string
  primary: boolean
  selected: boolean
}

export function CalendarSelector() {
  const [isLoading, setIsLoading] = useState(false)
  const [calendars, setCalendars] = useState<GoogleCalendar[]>([])
  const [error, setError] = useState<string | null>(null)
  const [directBooking, setDirectBooking] = useState(false)
  const [selectedCalendarId, setSelectedCalendarId] = useState<string>('')
  const { user } = useUser()
  const { toast } = useToast()

  // If direct booking is disabled, clear the selected calendar
  useEffect(() => {
    if (!directBooking) {
      setSelectedCalendarId('')
    }
  }, [directBooking])

  // Fetch the user's calendars and calendar preferences when the component mounts
  useEffect(() => {
    async function loadCalendars() {
      try {
        const [savedSelections, savedPreferences] = await Promise.all([
          getCalendars(),
          getCalendarPreferences(),
        ])
        await fetchCalendars(savedSelections)
        setDirectBooking(savedPreferences.directBookingEnabled)
        setSelectedCalendarId(savedPreferences.directBookingCalendarId || '')
      } catch (error) {
        console.error('Error loading calendar settings:', error)
        // If getting saved selections fails, still try to load calendars
        await fetchCalendars()
      }
    }
    void loadCalendars()
  }, [user])

  // Fetch the user's calendars from the Google Calendar API
  const fetchCalendars = async (savedSelections?: { id: string; name: string }[]) => {
    setIsLoading(true)
    setError(null)
    try {
      const getGoogleTokenResponse = await getGoogleToken()
      if (!getGoogleTokenResponse?.token) {
        await reauthAccount(['https://www.googleapis.com/auth/calendar.readonly'])
        return
      }

      const response = await fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList', {
        headers: {
          Authorization: `Bearer ${getGoogleTokenResponse.token}`,
        },
      })

      if (!response.ok) {
        throw new Error('Failed to fetch calendars')
      }

      const data = await response.json()
      const formattedCalendars = data.items.map((calendar: any) => ({
        id: calendar.id,
        summary: calendar.summary,
        primary: calendar.primary || false,
        selected: savedSelections?.some((sel) => sel.id === calendar.id) || false,
      }))
      setCalendars(formattedCalendars)
    } catch (err) {
      console.error('Error fetching calendars:', err)
      setError(err instanceof Error ? err.message : 'Failed to fetch calendars')
    } finally {
      setIsLoading(false)
    }
  }

  // This function initiates a reauthorization flow for the user with the proper scopes
  async function reauthAccount(scopes: string[]) {
    if (user) {
      const googleAccount = user.externalAccounts.find((ea) => ea.provider === 'google')

      const reauth = await googleAccount?.reauthorize({
        redirectUrl: window.location.href,
        additionalScopes: scopes,
      })

      if (reauth?.verification?.externalVerificationRedirectURL) {
        window.location.href = reauth?.verification?.externalVerificationRedirectURL.href
      }
    }
  }

  // This function checks if the user has the required scopes and returns true if they do
  const checkScopes = () => {
    const googleAccount = user?.externalAccounts.find((ea) => ea.provider === 'google')
    if (!googleAccount) return false

    const requiredScopes = [
      'https://www.googleapis.com/auth/calendar.readonly',
      'https://www.googleapis.com/auth/calendar.events',
    ]
    const approvedScopes = googleAccount.approvedScopes?.split(' ')
    return requiredScopes.every((scope) => approvedScopes?.includes(scope))
  }

  // Save the calendars and calendar preferences to the database
  const handleSave = async () => {
    setIsLoading(true)
    setError(null)

    // Check if we need additional scopes for direct booking, and reauthorize if needed
    if (directBooking && !checkScopes()) {
      await reauthAccount([
        'https://www.googleapis.com/auth/calendar.readonly',
        'https://www.googleapis.com/auth/calendar.events',
      ])
      return
    }

    try {
      // Save the calendars and calendar preferences to the database
      await saveCalendars(calendars)
      await saveCalendarPreferences(directBooking, directBooking ? selectedCalendarId : null)
      toast({
        title: 'Success',
        description: 'Calendar settings have been saved.',
      })
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to save calendar settings')
      toast({
        title: 'Error',
        description: 'Failed to save calendar settings. Please try again.',
        variant: 'destructive',
      })
    } finally {
      setIsLoading(false)
    }
  }

  // When a calendar is toggled, update the state
  const handleToggleCalendar = (calendarId: string) => {
    setCalendars((prevCalendars) =>
      prevCalendars.map((cal) =>
        cal.id === calendarId ? { ...cal, selected: !cal.selected } : cal,
      ),
    )
  }

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-2">
          <Calendar className="h-5 w-5" />
          <h2 className="text-lg font-semibold">Google Calendar Selection</h2>
        </div>
        <RefreshButton onClick={() => fetchCalendars()} isLoading={isLoading} />
      </div>

      {/* If there's an error, show it */}
      {error && (
        <div className="rounded-md bg-red-50 p-4">
          <p className="text-sm text-red-600">{error}</p>
        </div>
      )}

      <div className="rounded-lg border p-4">
        {/* If loading, show a loading indicator */}
        {isLoading ? (
          <div className="flex justify-center py-8">
            <Loader2 className="h-6 w-6 animate-spin text-gray-400" />
          </div>
        ) : calendars.length === 0 ? (
          // If there are no calendars, show a message
          <div className="flex justify-center py-8 text-sm text-gray-600">
            No calendars found. Click refresh to try again.
          </div>
        ) : (
          // Render a list of the user's calendars which can be toggled
          <div className="space-y-4">
            <div className="space-y-2">
              {calendars.map((calendar) => (
                <CalendarItem
                  key={calendar.id}
                  id={calendar.id}
                  summary={calendar.summary}
                  primary={calendar.primary}
                  selected={calendar.selected}
                  onToggle={handleToggleCalendar}
                />
              ))}
            </div>

            <div className="space-y-4 border-t pt-4">
              {/* This checkbox allows the user to toggle direct booking */}
              <div className="flex items-center justify-between">
                <label className="flex items-center gap-2">
                  <input
                    type="checkbox"
                    checked={directBooking}
                    onChange={(e) => setDirectBooking(e.target.checked)}
                    className="h-4 w-4 rounded border-gray-300"
                  />
                  <span className="text-sm font-medium">Add bookings directly to calendar</span>
                </label>
              </div>

              {/* If direct booking is enabled, show a dropdown to select a calendar to book with */}
              {directBooking && (
                <div className="space-y-2">
                  <div className="flex items-center gap-2">
                    <Select value={selectedCalendarId} onValueChange={setSelectedCalendarId}>
                      <SelectTrigger className="w-full">
                        <SelectValue placeholder="Select a calendar" />
                      </SelectTrigger>
                      <SelectContent>
                        {calendars.map((calendar) => (
                          <SelectItem key={calendar.id} value={calendar.id}>
                            {calendar.summary}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  </div>
                  {directBooking && !selectedCalendarId && (
                    <p className="text-sm text-red-500">
                      Please select a calendar for direct bookings
                    </p>
                  )}
                </div>
              )}
            </div>

            <div className="flex justify-end pt-4">
              <button
                onClick={handleSave}
                disabled={isLoading || (directBooking && !selectedCalendarId)}
                className="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 disabled:opacity-50"
              >
                Save Changes
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  )
}
```

### Initiate a reauthorization by Clerk

Clerk has a helper function attached to every [`ExternalAccount`](/docs/references/javascript/external-account) object to trigger the reauthorization if needed. The `additionalScopes` can contain an array of scopes that will be added to the OAuth URL along with the global scopes set in the Clerk dashboard. This function will craft the required URL that the user needs to be directed to confirm access to the necessary resources:

```tsx
async function reauthAccount(scopes: string[]) {
  if (user) {
    const googleAccount = user.externalAccounts.find((ea) => ea.provider === 'google')

    const reauth = await googleAccount?.reauthorize({
      redirectUrl: window.location.href,
      additionalScopes: scopes,
    })

    if (reauth?.verification?.externalVerificationRedirectURL) {
      window.location.href = reauth?.verification?.externalVerificationRedirectURL.href
    }
  }
}
```

To prevent the user from having to reauthorize manually every time they sign in, we can store these required scopes with the Clerk User object in the [`publicMetadata`](/docs/users/metadata) and use that data for the following steps.

The following snippet is in the server action that handles saving the calendar preferences for the user, with `enabled` being the parameter for the function if `directBooking` is set:

```tsx
const user = await currentUser()
const client = await clerkClient()
const includesAdditionalScopes = user?.publicMetadata.additionalScopes?.includes(
  'https://www.googleapis.com/auth/calendar.events',
)

// Set public metadata
if (enabled && !includesAdditionalScopes) {
  await client.users.updateUserMetadata(userId, {
    publicMetadata: {
      additionalScopes: ['https://www.googleapis.com/auth/calendar.events'],
    },
  })
} else if (!enabled && includesAdditionalScopes) {
  await client.users.updateUserMetadata(userId, {
    publicMetadata: {
      additionalScopes: [],
    },
  })
}
```

### Add `publicMetadata` to the session claims

[When a user authenticates with Clerk](/blog/combining-the-benefits-of-session-tokens-and-jwts), they receive a JWT used to verify their identity on any subsequent requests to the server. The claims in this JWT can be modified to include the `publicMetadata` which includes the additional scopes required. This saves an extra trip to the Clerk API to check for this data, making the application more performant.

The [session claims](/glossary#claim) can be customized [in the Clerk Dashboard](https://dashboard.clerk.com/last-active?path=sessions) under **Configure** > **Sessions** > **Customize session token**.

![Customize session claims in the Clerk dashboard](./image2.png)

### Automatically handle reauthorization if additional scopes are present

Since the claims are available to both the client and server and include the additional scopes, they can be checked on login to automatically handle reauthorization. Since the user has already approved the scopes, this appears as a simple redirect to Google and then back to the application with no user interaction required.

The following code outlines a `ReauthProvider` component that can wrap the application to handle all of this logic automatically, storing a flag in the browser’s `localStorage` to prevent it from occurring too frequently:

```tsx
'use client'
import { useUser } from '@clerk/nextjs'
import React, { useEffect, useState } from 'react'

const LOCAL_STORAGE_KEY = 'bookmate_isScopeCheckComplete'

interface Props {
  children: React.ReactNode
}

function ReauthProvider({ children }: Props) {
  const [isReauthStarted, setIsReauthStarted] = useState(false)
  const [isLoaded, setIsLoaded] = useState(false)
  const { isSignedIn, user } = useUser()

  useEffect(() => {
    if (isReauthStarted) {
      return
    }
    const isScopeCheckComplete = localStorage.getItem(LOCAL_STORAGE_KEY)

    // If the user is not signed in, remove the localStorage key
    // This should also trigger on logout
    if (!isSignedIn) {
      localStorage.removeItem(LOCAL_STORAGE_KEY)
      return
    }

    // If the flag is set, the scope check has already been completed
    if (isScopeCheckComplete) {
      setIsLoaded(true)
      return
    }

    // Check if additional scopes are required per the user metadata
    const requiredScopes = user?.publicMetadata?.additionalScopes
    if (!requiredScopes) {
      localStorage.setItem(LOCAL_STORAGE_KEY, 'true')
      setIsLoaded(true)
      return
    }

    // If the user is signed in and the scope check has not been completed, check the scopes
    const googleAccount = user?.externalAccounts.find((ea) => ea.provider === 'google')
    const approvedScopes = googleAccount?.approvedScopes?.split(' ')
    const hasAllRequiredScopes = requiredScopes?.every((scope) => approvedScopes?.includes(scope))

    // If the user does not have all required scopes, trigger reauth
    if (!hasAllRequiredScopes) {
      void reauthAcct(requiredScopes)
    } else {
      localStorage.setItem(LOCAL_STORAGE_KEY, 'true')
      setIsLoaded(true)
    }
  }, [user])

  async function reauthAcct(scopes: string[]) {
    setIsReauthStarted(true)

    if (user) {
      const googleAccount = user.externalAccounts.find((ea) => ea.provider === 'google')

      const reauth = await googleAccount?.reauthorize({
        redirectUrl: window.location.href,
        additionalScopes: scopes,
      })

      if (reauth?.verification?.externalVerificationRedirectURL) {
        window.location.href = reauth?.verification?.externalVerificationRedirectURL.href
      }
    }
  }

  if (!isSignedIn) {
    return children
  }

  if (!isLoaded) {
    return null
  }

  return <>{children}</>
}

export default ReauthProvider
```

The provider simply wraps the children in the root layout:

```tsx
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <ClerkProvider>
        <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
          <main className="min-h-screen bg-gradient-to-br from-blue-50 via-pink-50 to-yellow-50 text-gray-800">
            <ReauthProvider>{children}</ReauthProvider>
          </main>
          <Toaster />
        </body>
      </ClerkProvider>
    </html>
  )
}
```

From this point forward, the application will now have the proper rights to add events directly to the user's calendar if needed.

## Why is reauthorization required on every login?

When a user is using OAuth to sign into an application, a special URL is crafted that contains information about the application and the permissions it requires. If you are using a standard set of scopes for all users, those are configured in the Clerk dashboard and are included with the OAuth URL.

![Set default claims in the Clerk dashboard](./image3.png)

The access token stored with Clerk includes the scopes from the most recent authentication attempt. So when users who require additional scopes sign in via Clerk, their scopes will be reset to what’s defined for the entire application.

The reauthorization keeps the proper set of scopes configured at all times.

## Conclusion

You now understand how to implement least privilege access in a SaaS application using [Clerk's SSO](/docs/authentication/social-connections/overview). You've also learned how unique scopes per user can be stored and automatically reused throughout the application lifecycle. The approach provides granular access control and flexible permission management while minimizing potential security risks.

---

# Using Clerk SSO to access Google Calendar and other service data
URL: https://clerk.com/blog/using-clerk-sso-access-google-calendar.md
Date: 2024-12-06
Category: Guides
Description: Learn how to use Clerk to access data on behalf of the current user to request their availability from Google Calendar using an open-source, live demo application.

Allowing users to sign on with services like [Google](/blog/nextjs-google-authentication) and Apple is a staple in modern authentication, but [single sign-on (SSO)](/glossary#single-sign-on-sso) goes farther than that.

When a user signs in with an SSO provider, they receive a token that developers can use to determine who is making the request. With the proper configuration, developers can also access data within that service on behalf of the user. And while most developers know that Clerk can easily allow SSO with a wide range of providers, we also simplify the process involved in accessing user data.

In this article, you'll learn how to leverage Clerk to access data on behalf of the current user to request their availability from Google Calendar using an open-source, live demo application.

## Single sign-on and access tokens

When a user logs into an application using [OAuth](/glossary#oauth), a common protocol for modern SSO, access tokens are created and sent to the client.

Developers will typically store these [access tokens](/glossary#access-token) in a cookie or some other local storage mechanism. With each request to a backend system, the access token minted during the login process is sent along with the request. The system will verify the claims to ensure that the user making the request is who they say they are, and that the token is still valid.

As mentioned in the introduction of this article, access tokens can be used to request the user's data and verify their identity.

When using an SSO provider, developers can define *scopes* for what the specific access tokens minted have access to. Scopes help the service provider inform the user what data the developer is requesting access to so they can make an informed decision to permit or deny access to that data. For example, including the following scopes will enable access tokens minted by this application to read the calendar data for the current user:

- `https://www.googleapis.com/auth/calendar.readonly`
- `https://www.googleapis.com/auth/calendar.events.readonly`

When the user logs in, they will be presented with an access form like so:

![The Google OAuth consent screen](./image2.png)

It's also worth noting that access tokens are time-bound and expire after a certain period.

The login process will often send a longer-lived refresh token that can be used to refresh the access token transparently before it expires. This allows developers to enable users to sign in using their preferred method, all while transparently keeping the access tokens current.

When combined with the proper scopes, developers can use these tokens to verify the user's identity and access data within that service.

## Clerk helps with token management and data access

[Clerk enables SSO](/docs/authentication/configuration/sign-up-sign-in-options#social-connections-o-auth) with [many popular providers](/docs/authentication/social-connections/overview) with a simple toggle, but the capabilities go further than that.

When users log in using Clerk with an SSO provider, we store those tokens in our system and manage the lifecycle for developers. The access tokens are automatically kept current so you don't have to worry about them expiring and having to refresh them. We also provide simple functions to retrieve the access tokens, meaning your application can easily access data within that service on behalf of the user.

This allows you as the developer to focus on building functionality into your application instead of having to manage tokens, as we'll explore in the following demo.

To explore in detail how Clerk manages tokens, read the article “[Combining the benefits of session tokens and JWTs](/blog/combining-the-benefits-of-session-tokens-and-jwts)” on our blog!

## Demo: BookMate

BookMate is an open-source, publicly accessible, lightweight clone of popular booking services like [cal.com](http://cal.com) or [Calendly](https://calendly.com).

Users of the platform can create a profile, set their general availability, and share their profile for others to request meetings.

![The BookMate availability settings screen](./image3.png)

On top of setting general availability, users can also link their Google Calendar to the platform so that when someone tries to request time from them, BookMate will consider events that are already scheduled to avoid users from being double-booked.

![The BookMate calendar settings screen](./image4.png)

Clerk's `getUserOauthAccessToken` is used on the BookMate backend to make requests to Google on behalf of the user.

```ts {{ filename: 'src/app/settings/actions.ts' }}
'use server'
import { auth, clerkClient } from '@clerk/nextjs/server'

export async function getGoogleToken() {
  const { userId } = await auth()

  const client = await clerkClient()
  const token = await client.users.getUserOauthAccessToken(userId || '', 'oauth_google')

  return {
    token: token.data[0].token,
  }
}
```

This token is used to list the calendars the user wants to consider when a visitor tries to book, but also when the visitor tries to check the availability on the user's profile. Note that the following code snippet securely executes regardless of who's logged in:

```ts {{ filename: 'src/lib/google.ts' }}
const client = await clerkClient()
const token = await client.users.getUserOauthAccessToken(userId, 'oauth_google')
const accessToken = token.data[0].token

// Code removed for brevity.

const response = await fetch(
  `https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(
    calendarId,
  )}/events?timeMin=${monthStart.toISOString()}&timeMax=${monthEnd.toISOString()}&singleEvents=true&orderBy=startTime`,
  {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  },
)
```

To experience this app yourself, check it out at [https://bookmate-sigma.vercel.app](https://bookmate-sigma.vercel.app), or explore the [source code on GitHub](https://github.com/bmorrisondev/bookmate).

## Conclusion

In this article, we explored how Clerk simplifies the process of implementing SSO and accessing third-party services like Google Calendar. By managing token lifecycles and providing straightforward functions to retrieve access tokens, Clerk allows developers to focus on building features rather than dealing with authentication infrastructure.

The BookMate demo demonstrates how easy it is to integrate Google Calendar data into your application using Clerk's OAuth capabilities. With just a few lines of code, you can securely access user data from external services while maintaining a smooth user experience.

Whether you're building a scheduling application like BookMate or any other service that requires SSO integration, Clerk's token management and data access features can significantly reduce development complexity while maintaining security best practices.

---

# Streamline enterprise customer onboarding with SAML and Clerk
URL: https://clerk.com/blog/streamline-enterprise-onboarding-saml.md
Date: 2024-11-26
Category: Guides
Description: Learn how to automatically enroll new users into your SAML-enabled enterprise customers.

Enterprise customers can provide massive growth for your B2B applications, but they come with their own unique challenges.

Onboarding is one such challenge. With a potentially large user base that might consistently churn, quickly providing new users the access they need can be a huge win for your B2B applications. Combining [SAML](/glossary#security-assertion-markup-language-saml), a common enterprise single sign-on strategy, with verified domains can automate the entire process of onboarding users into your application, providing a delightful experience for those users as well as the enterprise's support department.

In this article, you’ll learn more about SAML and how verified domains can be configured to automatically enroll new enterprise users.

## What is SAML?

[Secure Assertion Markup Language](/docs/authentication/enterprise-connections/overview) (or SAML) is an [enterprise single sign-on standard](/docs/authentication/enterprise-connections/authentication-flows) enabling different systems to communicate securely.

It allows systems to share user details such as various attributes about the user, the groups they are members of, and supports [Just-in-Time (JIT) Provisioning](/docs/authentication/enterprise-connections/jit-provisioning). Admins can also easily map these attributes if they do not match between the two systems, making the system quite flexible.

When Clerk is configured with a SAML connection, users who log in using an email address associated with the connection will automatically be prompted to log in using the specified identity provider. If they log in using a social provider like Google or GitHub, Clerk will detect the email address through those flows as well and ask the user to authenticate with the enterprise connection.

SAML is commonly used in the enterprise space to streamline the onboarding experience for their users while reducing stress on IT and support.

## Automated enrollment with verified domains

Clerk [organizations](/docs/organizations/overview) provide a way for developers to empower their users to create and manage tenants within an application.

If one of your customers is a large corporation that uses SAML, you can further streamline the onboarding process for their new users by configuring a verified domain with an organization. With verified domains configured, users using an email address with that domain will automatically be invited to join an organization in your application with no further action from IT.

When combined with SAML connections, the users can simply sign up for your service using their work email, use the same credentials they use for other work apps, and immediately get access to the tenant within your application.

## SAML with verified domains in action

With a general understanding of how Clerk’s organizations and verified domains can help you and your SAML customers, let’s see how to configure it. This guide will cover how to configure SAML using Google Workspaces, but field names are relatively standard across any service that supports SAML.

Before following along, make sure you have the following:

- An active [Clerk account](https://dashboard.clerk.com/sign-up).
- A Google Workspace account.

If you want to follow along with this guide in video format, you can watch the video below:

### Configuring SAML

In the Clerk Dashboard sidebar, go to **User & authentication** > [**SSO Connections**](https://dashboard.clerk.com/last-active?path=user-authentication/sso-connections). Select **Add connection**, choose **For specific domains**, and then **Google Workspace**. Complete the form as follows:

- **Name**: An arbitrary name to identify the connection.
- **Domain**: The domain that will use this connection.

Click **Add connection** when you are done.

![The Create connection modal in the Clerk dashboard.](./image-0.png)

Next, access your Google Workspace Admin panel and navigate to **Apps** > [**Web and Mobile Apps**](https://admin.google.com/ac/apps/unified).

![The Google Web Console with Web and mobile apps highlighted.](./image-1.png)

Select **Add app**, then **Add custom SAML app**. Google will open a new view to walk you through configuring the app. You’ll be sharing some information between the Clerk Dashboard and Google.

![The Google Web Console with Add custom SAML app highlighted.](./image-2.png)

Populate the **App Name** with the name of your choice and select **Continue**.

![The Add custom SAML app view on step 1 with the name field populated.](./image-3.png)

Start by selecting **Download Metadata** which will download a file containing the configuration for the Google Workspaces App.

![The Add custom SAML app view on step 2 with Download Metadata highlighted.](./image-4.png)

Back in the Clerk Dashboard, locate the **Identity Provider Configuration** section of the Enterprise Connection you created earlier, select **Upload file**, and upload the file you downloaded from Google.

![The enterprise connection in the Clerk dashboard with the Identity Provider Configuration section highlighted.](./image-5.png)

Once the upload is finished, take note of the **ACS URL** and **Entity ID** values from the Clerk dashboard, you’ll need these for the next step.

![The enterprise connection in the Clerk dashboard with the ACS URL and Entity ID fields highlighted.](./image-6.png)

Back in the Google Admin Console, click **Continue** to move on to step 3 of the setup process if you have not already. Populate the **ACS URL** and **Entity ID** fields you obtained from the Clerk Dashboard.

![The Add custom SAML app view on Step 3 with the ACS URL and Entity ID fields populated.](./image-7.png)

Click **Continue** to move on to step 4. Now you’ll configure attribute mapping which essentially tells Google Workspaces which of its user attributes maps to the attributes in Clerk. Select **Add Mapping** > **Basic Information** > **Primary Email**. In the **App attributes** field, enter “*mail*” as the value.

![The Add custom SAML app view with the Primary email to mail attribute mapping highlighted](./image-8.png)

Select **Finish** to complete this part of the process. You should now be viewing the details screen of the app that was just created. The last step in the Google Admin Console is to enable this app for Workspace users. In the **User access** section, select **View details**.

![The Google Web Console showing User access highlighted in the app that was created.](./image-9.png)

Next, toggle **ON for everyone** and select **Save**.

![The Google Web Console showing the ON for everyone option highlighted in the Service status section.](./image-10.png)

This wraps up the work required in the Google Admin Console. The last step for configuring SAML is to enable the connection in Clerk.

Back in the Clerk dashboard, simply toggle the switch next to Enable connection and then select Save in the bubble that will appear from the bottom of the screen.

![The enterprise connection in the Clerk dashboard with the Enable connection toggle highlighted.](./image-11.png)

If you are stepping through this guide, you should now be able to authenticate using this SAML connection.

### Using verified domains within organizations

To enable automatic enrollment within your application, start by enabling organizations within the Clerk dashboard if you have not already done so. This can be done under **Organization Settings**. Toggle on the following settings then click **Save** at the bottom of the screen:

- **Unlimited membership**
- **Enable verified domains**
- **Automatic invitation**

![The Clerk dashboard Organization Settings with the Unlimited membership and Enable verified domains options highlighted.](./image-12.png)

With these settings enabled, organization admins can use the Organization configuration view provided by Clerk to configure a verified domain for their enterprise to add their domain and automatically invite new users. Admins can access these settings by using the dropdown provided by `<OrganizationSwitcher/>` and selecting the cog icon next to their organization.

![The OrganizationSwitcher dropdown with the cog icon highlighted.](./image-13.png)

This will open the Organization configuration modal. Verified domains can be added in the General view:

![The Organization configuration modal, in the General tab, with the Verified domains section highlighted.](./image-14.png)

Once a domain is added, admins can now specify their preferred enrollment settings:

![The Organization configuration modal with Automatic invitations highlighted.](./image-15.png)

## Conclusion

SAML is a common single sign-on strategy used by enterprises all over the world. Your application can be configured to streamline onboarding for your enterprise customers using SAML by enabling verified domains for the organizations belonging to those customers. The result is a simplified experience for your customers, their support teams, and their users!

---

# Clerk launches EASIE SSO and eliminates SSO fees
URL: https://clerk.com/blog/clerk-launches-easio-sso-and-drops-all-sso-fees.md
Date: 2024-11-20
Category: Engineering
Description: EASIE SSO brings Clerk’s signature simplicity to a notoriously agonizing corner of authentication.

Since we started Clerk, it’s been clear that enterprise SSO is a tremendous disappointment to users, developers, and IT admins alike. Despite being crucial to enterprise security, and despite the high fees usually associated with the feature, there has been a severe underinvestment in the experience for all stakeholders.

That’s why we’re absolutely delighted to share **EASIE SSO** with the world today. It’s enterprise SSO with a healthy dose of Clerk’s signature simplicity, baked right into our dashboard and `<SignIn/>` component. We're leveraging Google and Microsoft OIDC for their ease of setup, then adding a thin layer of enterprise SSO capabilities on top, including:

- Enrollment based on a user's email domain
- Mandatory sign-in through the enterprise's choice of Google or Microsoft OIDC
- Just-in-time provisioning during sign-in
- Automatic deprovisioning and session revocation

Plus, **we’re eliminating usage-based SSO connection fees** (previously $50/mo each) to make enterprise SSO more accessible than ever, including SAML SSO connections!

EASIE SSO can be enabled through the **SSO Connections** page of your dashboard: just enter the enterprise's email domain and choose if they should sign in with Google or Microsoft… Clerk will handle the rest!

## Why EASIE SSO?

SSO is vital for enterprise security, but existing solutions are complex and frustrating for all parties. We've met countless developers who try to mimic enterprise SSO behavior by forcing authentication through Google or Microsoft and then verifying the email domain. This is easier than “real SSO,” but often carries security oversights:

- Verifying the email domain alone doesn't confirm that the user signed in through the correct tenant, and can be exploited through a [tenant](https://trufflesecurity.com/blog/google-oauth-is-broken-sort-of) [swap](https://www.youtube.com/watch?v=ceeA3FmKxtM) attack
- When a user is deactivated through the Google or Microsoft admin console, their sessions are not revoked, so terminated teammates can continue accessing tools and services as long as they don’t sign out

**EASIE SSO** is a thin layer on top of OIDC that solves these security challenges, while retaining the simple setup of verifying users based on email domain. There is no need to manage per-enterprise credentials like SAML metadata files or one-off OIDC secrets.

The technical details and future plans for EASIE SSO are maintained on [easie.dev](https://easie.dev) as an open specification, and we encourage others to implement the strategy even if they are not using Clerk.

## The future of enterprise SSO

It’s clear that enterprise SSO has been sitting at a crossroads. While there is unanimous agreement of its benefits, the challenges associated have limited its adoption to only the most security-conscious and cost-insensitive enterprise software buyers.

Clerk believes in a future where the benefits of enterprise SSO are realized far more frequently, among both the largest and smallest businesses. We’re confident this future can be achieved, and we expect EASIE SSO and the elimination of SSO connection fees to be a leap forward in the right direction.

If you are also interested in creating this future, we would love to hear from you! To connect with the EASIE team, please email [easie@clerk.dev](mailto:easie@clerk.dev).

---

# How to secure Liveblocks Rooms with Clerk in Next.js
URL: https://clerk.com/blog/secure-liveblocks-rooms-clerk-nextjs.md
Date: 2024-11-19
Category: Guides
Description: Learn how to use Clerk user data to secure access to rooms in Liveblocks.

Real-time apps make life seamless, but without proper security, they can expose users’ most sensitive information in an instant.

Liveblocks is a platform that enables developers to build collaborative platforms faster with real-time APIs.  Liveblocks takes a component-driven approach to development, where wrapping areas of your application in one of the provided components will automatically add real-time interaction for your users. When coupled with Clerk, you can not only secure your Liveblocks-enabled application but also integrate user information for easy traceability.

In this article, you’ll learn how to configure Clerk and Liveblocks to ensure that only authorized users can access Liveblocks rooms.

## What is Liveblocks?

[Liveblocks](https://liveblocks.io) is a platform that enables developers to easily integrate collaborative features and custom real-time functionality within their applications.

Using one of their [pre-built components](https://liveblocks.io/docs/products/comments/default-components), you can easily add functionality like comments, mentions, notifications, live text editors, and multiplayer canvas collaboration. One of the core entities of Liveblocks is the Room, which is essentially an isolated, virtual space where users can collaborate. Each project contains multiple rooms for users to enter to collaborate with others currently active in that room.

When developing a solution with Liveblocks, [Rooms](https://liveblocks.io/docs/concepts/how-liveblocks-works#Rooms) will often be a one-to-one mapping with an entity in your application.

## How room security works

Rooms also offer a security boundary that can be configured to only allow access to users who should have access.

By default, everyone has access to a room based on the public key that’s embedded into the application. You can also request a security token whenever the room is being accessed. This is done using a specially crafted API endpoint that is called during the initialization process. Using this API endpoint for security provides an opportunity to check what the [current user](/docs/references/nextjs/current-user) should have access to.

Using a Next.js route, you can use Clerk to check the currently logged-in user and perform any necessary security checks to make sure the user has access to what they are requesting before issuing a security token.

## The Liveblocks authorization route

As mentioned in the previous section, you’ll need to create an API route in Next.js to use with Liveblocks.

For context, this article is built around the concept of a team-based task manager, where multiple people can share task lists using [organizations](/docs/organizations/overview) in Clerk. The following code snippet defines the route needed to perform the necessary checks, including:

- Check that the request is being made by an authenticated user.
- Check the database to ensure the task exists.
- Verify that the user is permitted to work with that task based on their ID or the active [organization ID](/docs/references/backend/organization/get-organization).
- Create the Liveblocks session token and include the necessary user details to display the user within Liveblocks components.

```ts {{ filename: 'src/api/liveblocks-auth/route.ts' }}
import { getOneTask } from '@/app/actions'
import { useSession } from '@clerk/nextjs'
import { auth, currentUser } from '@clerk/nextjs/server'
import { Liveblocks } from '@liveblocks/node'

export async function POST(req: Request) {
  // Use Clerk to get the session claims for the current user.
  // Return a 401 response if the claims are not present (the user is not logged in)
  const { sessionClaims } = auth()
  if (!sessionClaims) {
    return new Response('Not authorized', { status: 401 })
  }

  // Use Clerk to get more details about the current user.
  const user = await currentUser()
  if (!user) {
    return new Response('Not authorized', { status: 401 })
  }

  // Parse the task ID from the request and query it from the database
  const { room } = await req.json()
  const [task] = await getOneTask(+room)
  if (!task) {
    return new Response('Not authorized', { status: 401 })
  }

  // If the task was not created by the user or within the organization,
  // send back a 401
  if (task.owner_id !== user.id && task.owner_id !== sessionClaims?.org_id) {
    return new Response('Not authorized', { status: 401 })
  }

  // All security checks passed so create the session and include the name and
  // avatar of the user, which will be shown within Liveblocks components
  const liveblocks = new Liveblocks({
    secret: process.env.LIVEBLOCKS_SECRET_KEY as string,
  })
  const session = liveblocks.prepareSession(user.id, {
    userInfo: {
      name: user.fullName,
      avatar: user.imageUrl,
    },
  })
  session.allow(room, session.FULL_ACCESS)
  const { body, status } = await session.authorize()

  // Return the response
  return new Response(body, { status })
}
```

## Configure `LiveblocksProvider` to use the authorization route

With the auth API route configured, the next step is to tell Liveblocks to use it.

Luckily this is a relatively straightforward process. If you’ve followed the Liveblocks quickstart for Next.js, your `LiveblocksProvider` probably looks similar to this:

```tsx
function ChatRoom({ taskId, children }: { taskId: number; children: ReactNode }) {
  return (
    <LiveblocksProvider publicApiKey={'pk_dev_06dAgs...'}>
      <RoomProvider id={`${taskId}`}>
        <ClientSideSuspense fallback={<div>Loading…</div>}>{children}</ClientSideSuspense>
      </RoomProvider>
    </LiveblocksProvider>
  )
}
```

The only change we need to perform is to replace the `publicApiKey` prop with the `authEndpoint` prop and set the value of that prop to the API route you want to use:

```tsx
function ChatRoom({ taskId, children }: { taskId: number; children: ReactNode }) {
  return (
    <LiveblocksProvider authEndpoint="/api/liveblocks-auth">
      <RoomProvider id={`${taskId}`}>
        <ClientSideSuspense fallback={<div>Loading…</div>}>{children}</ClientSideSuspense>
      </RoomProvider>
    </LiveblocksProvider>
  )
}
```

And that’s it! From now on, Liveblocks will use the configured API route to create the security tokens used to access Liveblock room data.

## See it in action

The following animation demonstrates what to expect when this integration is properly implemented. Note how commenting on specific tasks also displays the user’s name and avatar, which is populated from the Clerk data when the security token is created:

The above demo is from an open-source project, which you can explore in more detail [on GitHub](https://github.com/bmorrisondev/team-todo-demo/tree/liveblocks-comments).

## Conclusion

After reading this article you now understand the Liveblocks authorization process and how Clerk user data can be used to verify the user has access to the room. You also understand how Clerk user data can enhance the Liveblocks session, leading to a seamless experience for your users.

---

# Securing Node.js Express APIs with Clerk and React
URL: https://clerk.com/blog/securing-node-express-apis-clerk-react.md
Date: 2024-09-26
Category: Guides
Description: In this guide, we will cover how to use Clerk with Express to authenticate API requests using ClerkExpressWithAuth() and ClerkExpressRequireAuth() middleware.

When calling multiple external APIs from an application using Clerk for authentication, you need to ensure that each API request is properly authenticated and secured.
Implementing authentication in Express or Node.js is crucial for ensuring your API's integrity, security, and reliable operation. This reassures you that your APIs are protected and functioning as intended.

In this tutorial, we'll explore how to use Clerk with Express to authenticate API requests using `ClerkExpressWithAuth()` and `ClerkExpressRequireAuth()` middleware, and build a secure and robust backend for your React application.

> This guide focuses on API security. For frontend React authentication, [learn more about our React support](/react-authentication).

![Diagram showing how multiple APIs can be authenticated with Clerk](./diagram.png)

## Prerequisites

To following along with this article, make sure you have the following:

- Create a [Clerk account](https://dashboard.clerk.com/sign-up).
- Create a Clerk application.
- [Node.js and npm](https://nodejs.org/en) installed on your computer.
- [Postman](https://postman.com) installed on your computer.

## Securing API Endpoints

To secure multiple external APIs with your application and Clerk, you can use Clerk's authentication middleware to protect your API endpoints. Clerk provides middleware that can be used to authenticate requests to your API endpoints. This middleware can be applied to routes that need authentication, ensuring that only authenticated users can access these endpoints.

For Express applications, Clerk offers two middleware options:

- `ClerkExpressWithAuth()`: This middleware attaches the authenticated user's session to the request object, allowing you to access user information in your route handlers.
- `ClerkExpressRequireAuth()`: This middleware ensures that only authenticated requests can access the protected routes and will throw an error if the request is not authenticated.

Let's explore how both middlewares can be used with Express.

## Create the API with Express

Start by opening an empty directory on your computer and initializing a new Node project with the following command:

```bash
npm init -y
```

Now open the `package.json` file and update it to add the `"type": "module"` setting as shown below:

```json {{ filename: 'package.json', ins: [6] }}
{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

Next, we need to install the following packages:

- `express` is the web framework for Node.js we're going to use. Express is a great option for running Node servers because it's fast and minimal.
- `cors` allows us to easily call the endpoint from a client
- `dotenv` is used to read environmental variables in Node.
- `@clerk/clerk-sdk-node` is the Node.js SDK for the Clerk user management platform.

These can be installed by running the following command in the your terminal:

```bash
npm install express cors dotenv @clerk/clerk-sdk-node
```

Once installation completes, find your  `CLERK_PUBLISHABLE_KEY` & `CLERK_SECRET_KEY` in the [Clerk dashboard](https://dashboard.clerk.com/) on the Quickstart screen if this is a new application, or in the **Configure** tab in the sidebar under **API keys**.

Because you are building these routes on the backend, it is safe to include the `CLERK_SECRET_KEY` in your environment variables.

To learn how to set environemt variables for your project, check out the [How to set environment variables in Node.js](/blog/how-to-set-environment-variables-in-nodejs) article.

Finally, create a file named `server.js` and add the following code to it. Note that both Clerk middleware functions are being used on different routes to test the behavior of each. Below those routes is an error-handling middleware to address any errors that are thrown. If an error occurs in any middleware function that is run before the `ClerkExpressRequireAuth` middleware, this function will be called.

```ts {{ filename: 'server.js' }}
import 'dotenv/config' // To read CLERK_SECRET_KEY and CLERK_PUBLISHABLE_KEY
import express from 'express'
import { ClerkExpressRequireAuth, ClerkExpressWithAuth } from '@clerk/clerk-sdk-node'
import cors from 'cors'

const port = process.env.PORT || 3000

const app = express()
app.use(cors())

// Use the strict middleware that throws when unauthenticated
app.get('/protected-auth-required', ClerkExpressRequireAuth(), (req, res) => {
  res.json(req.auth)
})

// Use the lax middleware that returns an empty auth object when unauthenticated
app.get('/protected-auth-optional', ClerkExpressWithAuth(), (req, res) => {
  res.json(req.auth)
})

// Error handling middleware function
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(401).send('Unauthenticated!')
})

// Route not utilizing any authentication
app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})
```

Let's start the API server by running the following command in the terminal:

```bash
node server.js
```

You should now see that message from in your `app.listen(…)` terminal:

```
Example app listening at http://localhost:3000
```

## Testing the route with Postman

Postman is a tool that's used to dispatch requests to various endpoints and analyze the results. With Postman open, create a new request by selecting **File** > **New**. In the modal that appears, select **HTTP** as the request type.

In the URL field, type in `http://localhost:3000/protected-auth-required` and click Send. The results will be displayed in the Response tab like so:

![The response window from Postman showing a 401 status and an "Unauthenticated!" string in the response body.](./postman.png)

While accessing the `/protected-auth-required` route without authentication will return a 401 response as defined in the error handling middleware, accessing the `/protected-auth-optional` route will return an empty JSON object instead. Feel free to create a new request in Postman and send the request.

This is the expected response:

```json
{
  "sessionClaims": null,
  "sessionId": null,
  "session": null,
  "userId": null,
  "user": null,
  "actor": null,
  "orgId": null,
  "orgRole": null,
  "orgSlug": null,
  "organization": null,
  "claims": null
}
```

## Testing authentication with React

Let's create a React app so we can add Clerk to it and test the API endpoints after being authenticated. Open an empty directory in your terminal and run the following command to initialize a new React app:

```bash
npm create vite@latest
```

You'll be guided through a series of questions, use the following answers for each:

- Ok to proceed? (y) **y**
- Project name: **app**
- Select a framework: **React**
- Select a variant: **TypeScript**

Next, run the following command to switch directories, install the dependencies, and launch the project:

```bash
cd app
npm install
npm run dev
```

You should now be able to open your browser to `http://localhost:5173/` to access the React app.

### Set up Clerk

Next, follow the [Clerk React Quickstart Guide](/docs/quickstarts/react) to add Clerk to the app you just created. Once done, replace the code in `src/App.jsx` to render the `<SignInButton>` if the user is not signed in, or the `<UserButton>` if they are:

```tsx {{ filename: 'src/App.tsx' }}
import './App.css'
import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'

function App() {
  return (
    <>
      <p>Hello World!</p>
      <SignedOut>
        <SignInButton />
      </SignedOut>
      <SignedIn>
        <UserButton />
      </SignedIn>
    </>
  )
}

export default App
```

Now click the **Sign-in button** and test that you can sign-in using your preferred method. After fully authenticating, the Sign-in button will be replaced with your avatar, which is the `<UserButton>` component.

### Accessing the API via React

Next, we'll update `App.tsx` again with the following changes. This code will add the `useAuth` hook provided by the Clerk React SDK, which contains the `getToken` function used to obtain the JWT for the currently authenticated user.

That token will be used in the `Authorization` header of the `fetch` requests to our API, each of which can be called by clicking the appropriate button that is rendered in the browser. Finally, we're storing the responses of each call in a `data` state and simply displaying that as a string in the browser.

```tsx {{ filename: 'src/App.tsx', ins: [3, 4, [7, 30], [38, 42]], del: [2] }}
import './App.css'
import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'
import { SignInButton, SignedIn, SignedOut, UserButton, useAuth } from '@clerk/clerk-react'
import { useState } from 'react'

function App() {
  const { getToken } = useAuth()
  const [data, setData] = useState({})

  async function callProtectedAuthRequired() {
    const token = await getToken()
    const res = await fetch('http://localhost:3000/protected-auth-required', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
    const json = await res.json()
    setData(json)
  }

  async function callProtectedAuthOptional() {
    const token = await getToken()
    const res = await fetch('http://localhost:3000/protected-auth-optional', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
    const json = await res.json()
    setData(json)
  }

  return (
    <>
      <p>Hello World!</p>
      <SignedOut>
        <SignInButton />
      </SignedOut>
      <SignedIn>
        <UserButton />
        <button onClick={callProtectedAuthRequired}>Call /protected-auth-required</button>
        <button onClick={callProtectedAuthOptional}>Call /protected-auth-optional</button>
        <h1>Data from API:</h1>
        <p>{JSON.stringify(data, null, 2)}</p>
      </SignedIn>
    </>
  )
}

export default App
```

In the browser, click each of the newly rendered buttons to display a payload similar to the following JSON on the page:

```json
{
  "sessionClaims": {
    "azp": "http://localhost:5173",
    "exp": 1720558905,
    "iat": 1720558845,
    "iss": "https://boss-squid-85.clerk.accounts.dev",
    "nbf": 1720558835,
    "sid": "sess_2j1ZjdrAVrALRRTkn8ZFXHDQ7Qy",
    "sub": "user_2iQUovqEkfcRDScQrXvAIfGQgUn"
  },
  "sessionId": "sess_2j1ZjdrAVrALRRTkn8ZFXHDQ7Qy",
  "userId": "user_2iQUovqEkfcRDScQrXvAIfGQgUn",
  "claims": {
    "azp": "http://localhost:5173",
    "exp": 1720558905,
    "iat": 1720558845,
    "iss": "https://boss-squid-85.clerk.accounts.dev",
    "nbf": 1720558835,
    "sid": "sess_2j1ZjdrAVrALRRTkn8ZFXHDQ7Qy",
    "sub": "user_2iQUovqEkfcRDScQrXvAIfGQgUn"
  }
}
```

### Conclusion

Authentication should be swift and efficient to ensure it gets done, rather than sitting in your backlog for months or leaving your endpoints vulnerable to attackers. By using Clerk Middlewares, In this case, `ClerkExpressWithAuth()` or `ClerkExpressRequireAuth()`, you can secure any endpoint and integrate authentication with Express without the complexity of building it from scratch.

---

# Combining the benefits of session tokens and JWTs
URL: https://clerk.com/blog/combining-the-benefits-of-session-tokens-and-jwts.md
Date: 2024-09-11
Category: Engineering
Description: Learn about how leveraging the benefits of both session token authentication and JWTs results in the best of both approaches.

Neither session tokens nor JWTs alone are sufficient as an optimal authentication strategy.

Session token authentication has been in use for decades and is still widely implemented in modern applications, but can become a bottleneck when scaling an application. JWT authentication enables fast request validation, making it suitable for scaling distributed applications, yet struggles with session invalidation. While both approaches have their tradeoffs, the two methods can be combined to amplify their benefits and cancel out their drawbacks.

Let's explore these two authentication strategies, and discuss how rethinking the approach to JWTs can result in the best of both worlds.

## Session token authentication

Session token authentication functions by tracking the state of a signed-in user on the server using the concept of a “session”.

When a user signs into a system, the server will check their provided credentials against a database to validate the information. If correct, the server will create a session with a unique identifier along with information about the session such as the user's ID, the creation time, expiration time, session status, etc. These details will be tracked in persistent storage, such as a database, and the session identifier will be sent back to the client to be used with future requests.

The client will often store these session identifiers in browser cookies, which are small pieces of information that are automatically sent with every request that matches the domain for which they were created. When a web server receives a request that contains a cookie with a session identifier, the server will look up the session among the list of tracked sessions, ensure its validity, and tie the request to the user associated with the session.

This allows the server to identify *who* is making the request.

Because sessions are stored on the server, they can be easily invalidated as needed, resulting in subsequent requests being denied.

While traditionally not an issue with most small to moderately-sized applications, the latency associated with checking sessions on each request can become a problem as the application grows. Geographically distributed services are especially impacted based on the additional latency between parts of the system, more so than when utilizing a single database.

## JSON Web Token (JWT) authentication

JWT authentication is an alternative to session tokens that largely solves the latency issue by embedding session details directly into the object sent back to the client, instead of only an identifier.

### What are JWTs?

A JWT itself is an encoded version of a JavaScript object with a cryptographic signature attached to it to prevent tampering.

JWTs are composed of three parts: the header, the payload, and the signature. The **header** contains general information about the token type and the algorithm used for the signature. The **payload** is simply a JavaScript object that contains all of the information you need to transmit. The **signature** is the result of combining the header, payload, and a secret (typically the private key of an [asymmetrical key pair](https://www.cloudflare.com/learning/ssl/what-is-asymmetric-encryption/)) being [hashed](https://www.geeksforgeeks.org/what-is-hashing/) using the algorithm specified in the header.

Each of these three sections is base64 encoded and joined with a period (.) to obtain the final result.

![Combining The Benefits Of Session Tokens And Jwts guide illustration](./jwt.png)

Any modification to the JWT will make the signature invalid, allowing recipients of a valid JWT to trust the authenticity of the data it contains.

### How are JWTs used in authentication?

When a user signs into a system using JWT authentication, the server still needs to validate the credentials as with session token authentication. Instead of a session being created and tracked on the server, much of the information typically associated with a session (user ID, creation time, expiration time, etc.) is added to the payload of a JWT and sent back to the client.

When a request is received by the server, it will verify the signature of the JWT instead of querying a database for session information. If the signature is valid, the server can trust the details embedded into the JWT such as the user ID and expiration date.

JWT authentication solves the latency issues in distributed applications by removing the overhead associated with querying a database on each request, however, the system that creates the JWT has little control over it once it leaves the server. If a JWT is obtained by an unauthorized party after it's been created, that JWT can be used to impersonate the party for whom it was created with no way to invalidate it since the server trusts the combination of the payload and signature.

This is especially problematic considering most implementations of JWT authentication set the expiration value of JWTs as they would sessions. This results in JWTs created with long lifetimes, sometimes days or even weeks.

Ultimately, JWT authentication provides faster request validation with the tradeoff of being able to easily revoke JWTs. This shifts part of the trust associated with the authentication system away from the server since you need to trust that the client will not allow the JWT to leak.

## Using a hybrid approach for optimal results

So the big question now is how to get the speed of JWT authentication while still enabling the revocation ability of session token authentication.

The first step is to create JWTs with extremely short lifetimes that will be used to authenticate requests. While leaked JWTs can still be used for impersonation, the window of opportunity to utilize the JWT is so short that it is extremely unlikely that any meaningful action can be taken by attackers.

This change means that JWTs used for authentication will need to be created more frequently to keep the user signed in. At first glance, this may seem like a downside, but it creates an opportunity for the system to decide whether or not it *should* create a new JWT for that user.

To make that decision, the system creates sessions upon user sign-in that will be managed and tracked on the server. This part of the hybrid model follows the same approach as with session token authentication, but instead of using sessions to validate each request, the system will only use them to mint new JWTs prior to their expiration.

The state of the session (valid, expired, revoked, etc.) would be used to determine if the new JWT should be created or not. If a session is no longer valid, the request to renew the JWT will be denied, which allows the server to retain control over the user's ability to access the service.

### Implementing the hybrid model

Using this model, the client would receive a session identifier (as defined with session token authentication) as well as a JWT upon sign-in.

As requests are made to the server, the JWT is used to authenticate the request and grant the user access to that resource.

Before the JWT's short expiration window elapses, the session identifier is used to renew the authentication JWT before it expires to maintain access to the system. This renewal process would occur as a background task, outside of the typical interaction between the application and the server.

Clerk SDKs perform this operation in the browser by setting an interval that executes every minute to request a new JWT. This operation is performed in the background without any user interaction.

If a session is expired or invalidated on the server, the authentication system will no longer create a JWT, effectively signing the user out of the system.

By decoupling the session lifetime from the JWT used to authenticate requests and instead tracking it elsewhere, you can have a hybrid approach to authentication that combines the best of both JWT authentication and session token authentication.

## Conclusion

Both JWT and session token authentication have their tradeoffs. While it is common to debate between using one over the other, this assumes that the benefits of each are exclusive to their respective method.

By instead combining the perks of each approach, we can provide fast responses from JWTs while enabling developers and users to invalidate sessions.

---

# Build a task manager with Next.js, Supabase, and Clerk
URL: https://clerk.com/blog/nextjs-supabase-clerk.md
Date: 2024-09-06
Category: Guides
Description: Learn how to integrate Clerk with Supabase by building a task manager.

[Supabase](https://supabase.com/) is an open-source backend-as-a-service platform that provides Postgres databases, authentication, instant APIs, realtime data subscriptions, and more to help developers quickly build scalable applications.

[Integrating Supabase with Clerk](/blog/how-clerk-integrates-nextjs-supabase) gives you the benefits of using a Supabase database while leveraging [Clerk's Next.js authentication](/nextjs-authentication), prebuilt components, and webhooks.

To get the most out of Supabase with Clerk, you must implement custom [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security) (RLS) policies. RLS works by validating database queries according to the restrictions defined in the RLS policies applied to the table. This guide will show you how to create RLS policies that restrict access to data based on the user's Clerk ID. This way, users can only access data that belongs to them. To set this up, you will:

- Create a function in Supabase to parse the Clerk user ID from the authentication token.
- Create a `user_id` column that defaults to the Clerk user's ID when new records are created.
- Create policies to restrict what data can be read, inserted, updated, and deleted.
- Use the Clerk Supabase integration helper in your code to authenticate with Supabase and execute queries.

This guide will have you create a new table in your [Supabase project](https://supabase.com/dashboard/projects), but once you've learned the concepts and the process, you can apply them to any existing table.

The source code for the final version can be found [here](https://github.com/clerk/clerk-supabase-nextjs).

> \[!WARNING]
> This guide is now outdated. For the latest information, see [our Supabase integration guide in the docs](/docs/integrations/databases/supabase).

## Project setup

### Clone the Clerk Next.js quickstart

To get started, clone the [Clerk Next.js quickstart](https://github.com/clerk/clerk-nextjs-app-quickstart) and install the dependencies:

```sh
git clone https://github.com/clerk/clerk-nextjs-app-quickstart
cd clerk-nextjs-app-quickstart
npm install
```

If you're wondering how this project was created, check out the [Next.js quickstart](https://github.com/clerk/clerk-nextjs-app-quickstart).

### Set up a Clerk project

If you do not have a Clerk account, [create one](https://clerk.com/sign-up) before proceeding. If you already have an account, [create a new project](https://dashboard.clerk.com/apps/new) for this guide.

Once you create an application, you'll be presented with the quickstarts. For this guide, follow the Next.js quickstart and only complete step 2, which instructs you to create the `.env.local` file and populate it with the necessary environment variables. The remaining steps are already completed as part of the quickstart repository that you cloned in the previous step.

Run your project with the following command:

```sh
npm run dev
```

Visit your app's homepage at [`http://localhost:3000`](http://localhost:3000). Sign up to create your first user and test everything works as expected.

### Set up a Supabase project

If you do not have a Supabase account, [create one](https://supabase.com/dashboard/sign-in) before proceeding. If you already have an account, [create a new project in the Supabase dashboard](https://supabase.com/dashboard/projects).

## Create a SQL query that checks the user's Clerk ID

Now that you've set up your project, it's time to get to work.

Create a function named `requesting_user_id()` that will parse the Clerk user ID from the authentication token. This function will be used to set the default value of `user_id` in a table and in the RLS policies to ensure the user can only access their data.

1. In the sidebar of your [Supabase dashboard](https://supabase.com/dashboard/projects), navigate to **SQL Editor**. This is where you will run all your SQL queries for the rest of this guide. Paste the following into the editor:
   ```sql
   CREATE OR REPLACE FUNCTION requesting_user_id()
   RETURNS TEXT AS $$
       SELECT NULLIF(
           current_setting('request.jwt.claims', true)::json->>'sub',
           ''
       )::text;
   $$ LANGUAGE SQL STABLE;
   ```
2. To execute the query and create the `requesting_user_id()` function, select **Run**. The results will be displayed in the **Results** tab, and should say "Success. No rows returned". Throughout this guide, keep an eye on this tab when running queries as it will display any errors that occur.

## Create a table and enable RLS on it

Next, you'll create a `"tasks"` table and enable RLS on that table. The `"tasks"` table will also contain a `user_id` column that will use the `requesting_user_id()` function you just created as its default value. **This column will be used in the RLS policies to only return or modify records scoped to the user's account.**

To create the `"tasks"` table and enable RLS on it, you'll run the following two queries. The first query creates the table and the second query enables RLS on the table.

Paste the following in the **SQL Editor** and select **Run**:

```sql
-- Create a "tasks" table
create table tasks(
  id serial primary key,
  name text not null,
  user_id text not null default requesting_user_id()
);

-- Enable RLS on the table
alter table "tasks" enable row level security;
```

If you want to see the table you just created, in the sidebar, select **Table Editor** and select the `"tasks"` table. You should see three empty columns: `id`, `name`, and `user_id`.

## Create ID-based RLS policies

Now, you need to create RLS policies that permit users to read, insert, update, and delete content associated with their user IDs only.

Paste the following in the **SQL Editor** and select **Run**:

```sql
-- This policy will enforce that only tasks where the `user_id` matches the Clerk user ID are returned.
CREATE POLICY "Select tasks policy" ON "public"."tasks" AS PERMISSIVE FOR
SELECT
  TO authenticated USING (requesting_user_id () = user_id);

-- This policy will enforce the user_id field on INSERT statements matches the Clerk user ID.
CREATE POLICY "Insert tasks policy" ON "public"."tasks" AS PERMISSIVE FOR INSERT TO authenticated
WITH
  CHECK (requesting_user_id () = user_id);

-- This policy will enforce that only tasks where the `user_id` matches the Clerk user ID can be updated.
CREATE POLICY "Update tasks policy" ON "public"."tasks" AS PERMISSIVE
FOR UPDATE
  TO authenticated USING (requesting_user_id () = user_id);

-- This policy will enforce that only tasks where the `user_id` matches the Clerk user ID can be deleted.
CREATE POLICY "Delete tasks policy" ON "public"."tasks" AS PERMISSIVE FOR DELETE TO authenticated USING (requesting_user_id () = user_id);
```

## Get your Supabase JWT secret key

Now that your table is set up and ready to be populated with data, it's time to set up your Clerk application and get Supabase integrated.

Get your Supabase JWT secret key:

1. In the sidebar, navigate to **Project Settings > API**.
2. Under the **JWT Settings** section, save the value in the **JWT Secret** field somewhere secure. This value will be used in the next step.

## Create a Supabase JWT template

When sending requests to Supabase, Supabase needs to verify that the user is who they say they are. Because you're using Clerk to authenticate your users, you need Clerk to tell Supabase who the user is. This is where JWTs come in. For each authenticated user, Clerk issues JWTs that contain information about the user, like their ID. You're going to create a custom template for a JWT that Supabase can use.

To create a JWT template for Supabase:

1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=jwt-templates).
2. In the navigation sidebar, select **JWT Templates**.
3. Select the **New template** button, then select **Supabase** from the list of options.
4. Configure your template:
   - The value of the **Name** field will be required when using the template in your code. For this tutorial, name it `supabase`.
   - **Signing algorithm** will be `HS256` by default. This algorithm is required to use JWTs with Supabase. [Learn more in their docs](https://supabase.com/docs/guides/resources/glossary#jwt-signing-secret).
   - Under **Signing key**, add the value of your Supabase **JWT secret key** from [the previous step](#get-your-supabase-jwt-secret-key).
   - You can leave all other fields at their default settings or customize them to your needs. See the [JWT template guide](/docs/backend-requests/making/jwt-templates#creating-a-template) to learn more about these settings.
   - Select **Save** from the notification bubble to complete setup.

## Install the Supabase client library

Add the Supabase client library to your project by running the following command in your terminal:

```bash {{ filename: 'terminal' }}
npm i @supabase/supabase-js
```

## Set up your environment variables

Add the Supabase project URL and key to your `.env.local` file.

1. In the sidebar of the [Supabase dashboard](https://supabase.com/dashboard/projects), select **Settings** > **API**.
2. Add the **Project URL** to your `.env.local` file as `SUPABASE_URL`.
3. In the **Project API keys** section, add the value beside `anon` `public` to your `.env.local` file as `SUPABASE_KEY`.

> \[!IMPORTANT]
> If you are using Next.js, the `NEXT_PUBLIC_` prefix is required for environment variables that are used in the client-side code.

## Fetch Supabase data in your code

Let's get to coding!

You want to load a list of tasks for the user and allow the user to add new tasks, mark tasks as complete, and delete tasks. You can do this either on the client-side or server-side.

### Client-side rendering (CSR)

The following example demonstrates how to fetch data from Supabase in a client-side rendered page.

The `createClerkSupabaseClient()` function uses [Supabase's `createClient()` method](https://supabase.com/docs/reference/javascript/initializing) to initialize a new Supabase client, but modifies it to inject the Clerk token you [created with the Supabase JWT template](#create-a-supabase-jwt-template) into the request headers sent to Supabase. The `requesting_user_id()` function that was created in the Supabase dashboard will parse the user ID from the Clerk token and use it when querying data from the `tasks` table.

Paste the following code into the `app/page.tsx` file:

```tsx {{ filename: 'app/page.tsx' }}
'use client'
import { useEffect, useState } from 'react'
import { useSession, useUser } from '@clerk/nextjs'
import { createClient } from '@supabase/supabase-js'

export default function Home() {
  const [tasks, setTasks] = useState<any[]>([])
  const [loading, setLoading] = useState(true)
  const [name, setName] = useState('')
  // The `useUser()` hook will be used to ensure that Clerk has loaded data about the logged in user
  const { user } = useUser()
  // The `useSession()` hook will be used to get the Clerk session object
  const { session } = useSession()

  // Create a custom supabase client that injects the Clerk Supabase token into the request headers
  function createClerkSupabaseClient() {
    return createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_KEY!,
      {
        global: {
          // Get the custom Supabase token from Clerk
          fetch: async (url, options = {}) => {
            const clerkToken = await session?.getToken({
              template: 'supabase',
            })

            // Insert the Clerk Supabase token into the headers
            const headers = new Headers(options?.headers)
            headers.set('Authorization', `Bearer ${clerkToken}`)

            // Now call the default fetch
            return fetch(url, {
              ...options,
              headers,
            })
          },
        },
      },
    )
  }

  // Create a `client` object for accessing Supabase data using the Clerk token
  const client = createClerkSupabaseClient()

  // This `useEffect` will wait for the User object to be loaded before requesting
  // the tasks for the signed in user
  useEffect(() => {
    if (!user) return

    async function loadTasks() {
      setLoading(true)
      const { data, error } = await client.from('tasks').select()
      if (!error) setTasks(data)
      setLoading(false)
    }

    loadTasks()
  }, [user])

  // Add a task into the "tasks" database
  async function createTask(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    await client.from('tasks').insert({
      name,
    })
    window.location.reload()
  }

  // Update a task when its completed
  async function onCheckClicked(taskId: number, isDone: boolean) {
    await client
      .from('tasks')
      .update({
        is_done: isDone,
      })
      .eq('id', taskId)
    window.location.reload()
  }

  // Delete a task from the database
  async function deleteTask(taskId: number) {
    await client.from('tasks').delete().eq('id', taskId)
    window.location.reload()
  }

  return (
    <div>
      <h1>Tasks</h1>

      {loading && <p>Loading...</p>}

      {!loading &&
        tasks.length > 0 &&
        tasks.map((task: any) => (
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
            <input
              type="checkbox"
              checked={task.is_done}
              onChange={(e) => onCheckClicked(task.id, e.target.checked)}
            />
            <p>{task.name}</p>
            <button onClick={() => deleteTask(task.id)}>Delete</button>
          </div>
        ))}

      {!loading && tasks.length === 0 && <p>No tasks found</p>}

      <form onSubmit={createTask}>
        <input
          autoFocus
          type="text"
          name="name"
          placeholder="Enter new task"
          onChange={(e) => setName(e.target.value)}
          value={name}
        />
        <button type="submit">Add</button>
      </form>
    </div>
  )
}
```

## Server-side rendering (SSR)

The following example demonstrates how to fetch data from Supabase in a server-side rendered page. It requires creating multiple files as you will use [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) to handle adding, deleting, and updating tasks.

The `createClerkSupabaseClientSsr()` function uses [Supabase's `createClient()` method](https://supabase.com/docs/reference/javascript/initializing) to initialize a new Supabase client, but modifies it to inject the Clerk token you [created with the Supabase JWT template](#create-a-supabase-jwt-template) into the request headers sent to Supabase. The `requesting_user_id()` function that was created in the Supabase dashboard will parse the user ID from the Clerk token and use it when querying data from the `tasks` table.

It is stored in a separate file so that it can be reused in multiple places, such as in both your `page.tsx` and your Server Action file.

Create the `src/app/ssr/client.ts` file and paste the following code into it:

```ts {{ filename: 'src/app/ssr/client.ts' }}
import { auth } from '@clerk/nextjs/server'
import { createClient } from '@supabase/supabase-js'

export function createClerkSupabaseClientSsr() {
  // The `useAuth()` hook is used to access the `getToken()` method
  const { getToken } = auth()

  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_KEY!,
    {
      global: {
        // Get the custom Supabase token from Clerk
        fetch: async (url, options = {}) => {
          const clerkToken = await getToken({
            template: 'supabase',
          })

          // Insert the Clerk Supabase token into the headers
          const headers = new Headers(options?.headers)
          headers.set('Authorization', `Bearer ${clerkToken}`)

          // Now call the default fetch
          return fetch(url, {
            ...options,
            headers,
          })
        },
      },
    },
  )
}
```

Create the `src/app/ssr/page.tsx` file and paste the following code into it:

```tsx {{ filename: 'src/app/ssr/page.tsx' }}
import AddTaskForm from './AddTaskForm'
import { createClerkSupabaseClientSsr } from './client'
import TaskRow from './TaskRow'

export default async function Home() {
  // Use the custom Supabase client you created
  const client = createClerkSupabaseClientSsr()

  // Query the 'tasks' table to render the list of tasks
  const { data, error } = await client.from('tasks').select()
  if (error) {
    throw error
  }
  const tasks = data

  return (
    <div>
      <h1>Tasks</h1>

      <div>
        {tasks?.map((task: any) => (
          <TaskRow key={task.id} task={task} />
        ))}
      </div>

      <AddTaskForm />
    </div>
  )
}
```

Create a `src/app/ssr/actions.ts` file which will include [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) for adding, deleting, and updating tasks. Paste the following code into that file:

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

import { createClerkSupabaseClientSsr } from './client'

const client = createClerkSupabaseClientSsr()

export async function addTask(name: string) {
  try {
    const response = await client.from('tasks').insert({
      name,
    })
    console.log('Task successfully added!', response)
  } catch (error: any) {
    console.error('Error adding task:', error.message)
    throw new Error('Failed to add task')
  }
}

export async function deleteTask(taskId: number) {
  try {
    const response = await client.from('tasks').delete().eq('id', taskId)
    console.log('Task successfully deleted!', response)
  } catch (error: any) {
    console.error('Error deleting task:', error.message)
    throw new Error('Failed to delete task')
  }
}

export async function setTaskState(taskId: number, isDone: boolean) {
  try {
    const response = await await client
      .from('tasks')
      .update({
        is_done: isDone,
      })
      .eq('id', taskId)
    console.log('Task successfully updated!', response)
  } catch (error: any) {
    console.error('Error updating task:', error.message)
    throw new Error('Failed to update task')
  }
}
```

Your form for adding tasks will use the `addTask()` Server Action that you created in the previous file. The form is in a separate file than your `page.tsx` file because it must be a client component. Create the `src/app/ssr/AddTaskForm.tsx` file and paste the following code into it:

```ts {{ filename: 'src/app/ssr/AddTaskForm.tsx' }}
'use client'
import React, { useState } from 'react'
import { addTask } from './actions'
import { useRouter } from 'next/navigation'

function AddTaskForm() {
  const [taskName, setTaskName] = useState('')
  const router = useRouter()

  async function onSubmit() {
    await addTask(taskName)
    setTaskName('')
    router.refresh()
  }

  return (
    <form action={onSubmit}>
      <input
        autoFocus
        type="text"
        name="name"
        placeholder="Enter new task"
        onChange={(e) => setTaskName(e.target.value)}
        value={taskName}
      />
      <button type="submit">Add</button>
    </form>
  )
}
export default AddTaskForm
```

Create the `src/app/ssr/TaskRow.tsx` file which will display the tasks in your list. It's a separate file from your `page.tsx` because it uses the `useRouter()` hook, so it must be a client component. Paste the following code into that file:

```ts {{ filename: 'src/app/ssr/TaskRow.tsx' }}
// This must be a separate file because useRouter() can't be used in server components
'use client'

import { useRouter } from 'next/navigation'
import { deleteTask, setTaskState } from './actions'

export default function TaskRow({ task }: { task: any }) {
  const router = useRouter()

  async function onCheckClicked(taskId: number, isDone: boolean) {
    // Update a task when its completed
    await setTaskState(taskId, isDone)
    router.refresh()
  }

  async function onDeleteClicked(taskId: number) {
    // Delete a task from the database
    await deleteTask(taskId)
    router.refresh()
  }

  return (
    <div key={task.id}>
      <p>{task.name}</p>
      <input
        type="checkbox"
        checked={task.is_done}
        onChange={(e) => onCheckClicked(task.id, e.target.checked)}
      />
      <button onClick={() => onDeleteClicked(task.id)}>Delete</button>
    </div>
  )
}
```

### Test your integration

Now it's time to test your code.

Run your project with the following command:

```sh
npm run dev
```

Sign in and test viewing, creating, updating, and deleting tasks. Sign out and sign in as a different user, and repeat.

If you have the same tasks across multiple accounts, double-check that RLS is enabled, or that the RLS policies were properly created. Check the table in the Supabase dashboard. You should see all the tasks between both users but with differing values in the `user_id` column.