# Clerk Blog — Company — Page 2

# Postmortem: June 26, 2025 service outage
URL: https://clerk.com/blog/postmortem-jun-26-2025-service-outage.md
Date: 2025-06-26
Category: Company
Description: Learn more about our service outage, including the timeline of events and our next steps.

On June 26, 2025, all Clerk services were down from 6:16 UTC to 7:01 UTC, caused by an outage of our compute infrastructure that impacted all Clerk customers.

We are deeply sorry for this outage. Clerk is a critical infrastructure component for our customers, and we take our reliability and uptime seriously. We know that any amount of downtime is unacceptable, and regardless of the cause, our system’s reliability is our responsibility and we fell short of our standards and your expectations.

![Graph of request throughput to our services during the outage. (GMT+3)](./graph.png)

*Graph of request throughput to our services during the outage. (GMT+3)*

## Timeline of events

- 6:16 UTC: Downtime begins and the team starts its investigation
- 6:20 UTC: The team determines that there was neither a deploy coincident with the failure,  nor a spike in traffic
- 6:28 UTC: The team identifies that our Google Cloud Run containers are in a continuous restart loop, and receiving `SIGINT` shutdown signals immediately on start
- 6:32 UTC: The team decides to begin preparing a new release, to test if the `SIGINT`s are related to the particular container
- 6:40 UTC: A fresh container is prepared and deployed, and it also immediately receives a `SIGINT`
- 6:41 UTC: Unable to find a root cause, a P1 incident is filed with Google and we begin speaking with their support
- 6:49 UTC: We receive the first indication that there is an incident at Google: *“I’ve inspected your Cloud Run service and we suspect that you’re being impacted by the internal incident. Please allow me some time to confirm more on this while I reach out to the Specialist”*
- 6:50 UTC: We ask Google which incident, since none has been posted on its status page
- 6:55 UTC: Google responds: *”Yes, this seems to be not yet confirmed hence I’m checking with the Cloud Run Specialist to confirm the same”*
- 7:01 UTC: Service is restored
- 7:32 UTC: We receive the first official confirmation of an incident from Google, via an event from their Service Health API:
  ```text
  {
    @type: "type.googleapis.com/google.cloud.servicehealth.logging.v1.EventLog"
    category: "INCIDENT"
    description: "We are experiencing an  issue with Cloud Run beginning at Wednesday, 2025-06-25 23:16 PDT.
    
    Our engineering team continues to investigate the issue.
    
    We will provide an update by Thursday, 2025-06-26 00:45 PDT with current details.
    
    We apologize to all who are affected by the disruption."
    detailedCategory: "CONFIRMED_INCIDENT"
    detailedState: "CONFIRMED"
    impactedLocations: "['us-central1']"
    impactedProductIds: "['9D7d2iNBQWN24zc1VamE']"
    impactedProducts: "['Cloud Run']"
    nextUpdateTime: "2025-06-26T07:45:00Z"
    relevance: "RELATED"
    startTime: "2025-06-26T06:16:42Z"
    state: "ACTIVE"
    symptom: "The impacted customers in the us-central1 region may observe the service issues while using Cloud Run DirectVPC."
    title: "Cloud Run customers are experiencing an issue in us-central1 region"
    updateTime: "2025-06-26T07:32:13.864860Z"
    workaround: "None at this time."
  }
  ```

## What specifically went wrong?

We architected Clerk to be resilient against failures in Google Cloud availability zones, but not entire regions. [Cloud Run is stated to provide zonal redundancy](https://cloud.google.com/run/docs/zonal-redundancy), which implies that this incident was caused by a full regional failure.

On the other hand, if this was truly a regional failure, we would expect many more services to be impacted than just Clerk. While there was [some discussion on Hacker News](https://news.ycombinator.com/item?id=44384860), the blast radius of this event is surprisingly small for a regional failure.

We are awaiting more information from Google about exactly which system failed, and will update this post when it’s received.

**Update (June 27, 12:40AM UTC):** Google has notified us that their root cause analysis would be published by June 30.

**Update (July 8):** Google provided an RCA Summary, titled *"Cloud Run customers are experiencing an issue in “us-central1” region"* (Event ID: `MLFZZXV`)

- On June 25th 23:16 PDT, Cloud Run Direct VPC workloads in the `us-central1` region experienced downtime for a duration of 51 minutes (June 26 00:07 PDT).
- Workloads in our cloud regions are served out of multiple partitions. Every app deployment is randomly assigned to a primary partition (this cannot be controlled by customers, and is not visible to them). In this incident, only one partition was impacted. As a result approximately 15% of Direct VPC workloads in “us-central1” experienced downtime.
- Workloads in a serving partition are typically served from multiple capacity pools. Our capacity management system uses a load balancer configuration for each capacity pool to signal whether or not the capacity in the pool is online. However, for safety reasons the capacity management system is designed to fail open — that is, if the load balancer configuration is not present, customer workloads can still serve from this capacity.
- This incident was the result of the above tooling implementation alongside the design of our scaling for Direct VPC workloads. Post mitigation and confirmation of the issue’s resolution, our product and SRE teams have deep dived into the architecture which led to this incident and is committed to improving our service to ensure there is no recurrence of this issue.

> **Clerk's interpretation** This RCA indicates that a design flaw with Google Cloud Run's zonal redundancy caused our traffic to fail completely, instead of failover into a different capacity pool. This outcome is what we anticipated, and our remediation of regional failovers should prevent similar failures in the future.

## Remediations

When incidents like this happen, we immediately turn our attention toward preventing their recurrence. Regardless of the root cause, it is our responsibility to build a service that is resilient to failures within our infrastructure providers. To that end, we are starting the following remediations:

### Regional failover for compute (immediate)

This incident could have been mitigated with a failover that shifted our Cloud Run traffic to a different region when `us-central1` began failing. Work is starting on this immediately.

### Multi-cloud redundancy for compute

Although Google Cloud Platform (GCP) was remarkably stable for Clerk’s early years, we have faced three major server disruptions since May 2025 that we attribute to GCP incidents. This shows that we need to explore additional redundancy outside of a single cloud vendor.

We will begin investigating multi-cloud redundancy for our compute infrastructure. This would make Clerk resilient to complete service failures of Cloud Run, as well as failure of Google’s Cloud Load Balancer.

### Additional service isolation and redundancy for session management

Any incident in our Session Management service has an outsized impact on our customers, since it results in complete downtime of their service.

Following an incident in February, we isolated our Session Management service from our User Management service, ensuring that bugs in our User Management codebase would not impact the availability of our Session Management service.

Unfortunately, in the event of a compute outage at origin like we saw in this incident, both services still come down.

To further mitigate session management failures, we are exploring architectural changes that will allow Clerk to continue issuing session tokens for a greater variety of incidents. Though a longer-term project, this will include bringing distributed storage and compute to our Session Management service.

## Looking ahead

This list of remediations we are exploring is not exhaustive, and doesn’t represent a final state for our efforts to make Clerk as resilient as possible. We will continue to invest in stability and scalability to make sure our customers can rely on Clerk as a critical service provider.

This was a serious outage, and we know that businesses rely on Clerk. We are again deeply sorry for the impact on our customers and will continue working to improve our reliability going forward.

For any questions, please [contact support](https://clerk.com/contact).

---

# Add subscriptions to your SaaS with Clerk Billing
URL: https://clerk.com/blog/add-subscriptions-to-your-saas-with-clerk-billing.md
Date: 2025-05-20
Category: Company
Description: Learn how to quickly monetize your SaaS with subscriptions powered by Clerk Billing.

Monetizing your application is often the next logical step after building something users love.

Subscription plans are a common strategy to build sustainable revenue into your SaaS, enabling premium features for individual users or teams with active plans. However, implementing subscriptions from scratch can be time-consuming and error-prone due to the complexity of the underlying infrastructure and logic.

**That's why we built Billing**.

Just as Clerk streamlines authentication and user management, it now does the same for subscriptions. You get a polished UI that allows your users to easily select and manage their preferred plan, as well as helper functions to easily gate access to premium features, all without writing custom billing logic from scratch.

In this article, you'll learn what Clerk Billing is, how it works, and how to implement it in a real-world application.

## What Is Clerk Billing?

[**Clerk Billing**](/billing) is our latest offering that brings subscription management directly into your existing authentication stack. With just a few clicks in the [Dashboard](https://dashboard.clerk.com/), you can define subscription plans and their associated features, which are displayed in your application with the drop-in [`<PricingTable />`](/docs/components/pricing-table) component.

Clerk integrates directly with your Stripe account, letting Stripe handle the actual payment processing while Clerk handles the user interface and entitlement logic. During development, you can even work in a sandbox environment without requiring a Stripe account. This mirrors the way Clerk handles SSO, where development instances use shared credentials until you're ready to go live.

## How Billing Works

You'll start in the [Clerk dashboard](https://dashboard.clerk.com/), where you define your **plans** and add **features** to them.

**Features** are essentially flags that indicate what a user has access to. For example, if your “Pro” plan includes advanced analytics, you'd create an “Analytics” feature and assign it to the plan. Features can be shared across multiple plans, allowing you to build a pricing structure thats increases access to your application as the selected plan increases.

You can configure your plans to be billed monthly or offer discounts for annual subscribers. Once your plans are created, you're ready to display them in your app.

### The `<PricingTable />` component

The core UI component used with Billing is the [`<PricingTable />`](/docs/components/pricing-table). It's a single-line component that renders a fully functional plan selector and payment form inside your app.

![The PricingTable component](./pricingtable.png)

When a user selects a plan, a modal drawer will open to collect their payment details. It's a smooth and familiar experience for users and requires no custom form building on your part.

Users can also subscribe to new plans and manage existing subscriptions directly through the [`<UserProfile />`](/docs/components/user/user-profile) component. The new Billing tab also includes invoice history and linked payment sources. This centralizes authentication, profile management, and billing into one cohesive experience.

### Verifying the User's access

One of the most powerful features in Clerk is the [`has()`](/docs/references/backend/types/auth-object#has) helper. Originally built to power B2B access controls, it checks whether a user has a specific role or permission. With Billing, it now supports checking a user's plan or feature (entitlement) access.

```tsx
const { has } = await auth()

const hasPremiumPlan = has({ plan: 'gold' })
const hasWidgets = has({ feature: 'widgets' })
```

This makes it incredibly easy to gate access to premium content or features with a single, readable function call.

### Managing Subscriptions

Once users are subscribed, you can manage their subscriptions directly from the Clerk dashboard. There's a new [**Subscriptions**](https://dashboard.clerk.com/last-active?path=billing/plans) tab where you can search for users, view their subscription status, and even cancel plans if needed.

Cancelled plans won't immediately remove access, they'll simply stop renewing, giving your users access until the current billing cycle ends. You can also view a user's plan details at a glance, which is especially useful for support and admin workflows.

## Implementing Billing in Quillmate

To demonstrate how easy it is to implement Billing into an application, I'm going to add subscriptions to **Quillmate**, a web-based writing platform built with Next.js, Clerk, and Supabase. The Pro plan for Quillmate offers an AI assistant that users can access while writing new articles. If the user is not a subscriber, they will be prompted to subscribe when they attempt to access the chat assistant.

> \[!NOTE]
> You can access the completed version of the project on [GitHub](https://github.com/bmorrisondev/quillmate/tree/billing).

### Creating the plans

I'll start in the [Clerk dashboard](https://dashboard.clerk.com/) and navigate to **Configure > Subscription Plans**, then click **Add User Plan**.

![The Subscription Plans configurate page with a red arrow pointing to the "Add User Plan" button](./1-creating_subscription_plans.png)

In the next screen I'll name the plan (which will auto-populate the slug), add a description, and set the monthly price.

![The plan configuration page](./2-naming-plans.png)

Before saving I'll scroll down a bit to create the feature that's associated with the plan by click **Add Feature**. I'll name the feature “AI Assistant” and provide a description before saving. Take note of the slug as it will be used in the code to check if the user can access this feature.

![The plan configuration page with a red arrow pointing to the "Add Feature" button](./3-add_feature.png)

### Add the pricing table

Now that my plan and feature are created, I can start updating the code. The first thing I'm going to do is create a new page that shows the available subscriptions. This will be a page that users can access at `/subscribe`. The page itself contains some promo text, but the main thing to note is the [`<PricingTable />`](/docs/components/pricing-table) component which is all I need to render the available plans and features:

```tsx {{ filename: 'src/app/subscribe/page.tsx', ins: [1, 47] }}
import { PricingTable } from '@clerk/nextjs'
import React from 'react'
import Link from 'next/link'

function SubscribePage() {
  return (
    <>
      {/* Navigation Bar */}
      <nav className="fixed top-0 left-0 z-20 flex w-full items-center justify-between border-b border-gray-200 bg-white/70 px-4 py-3 backdrop-blur-md dark:border-gray-800 dark:bg-gray-950/70">
        <Link
          href="/"
          className="bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-lg font-semibold text-transparent transition-opacity hover:opacity-80"
        >
          ← Back to Home
        </Link>
      </nav>
      {/* Main Content */}
      <div className="flex min-h-svh items-center justify-center bg-gradient-to-b from-white to-gray-50 pt-20 dark:from-gray-950 dark:to-gray-900">
        <div className="flex w-full max-w-2xl flex-col items-center gap-8 p-8 text-center">
          <h1 className="bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-4xl font-bold text-transparent">
            Unlock AI Superpowers
          </h1>
          <p className="text-xl text-gray-600 dark:text-gray-300">
            Become a member to access exclusive AI features, priority support, and early access to
            new tools. Join our growing community and take your productivity to the next level!
          </p>
          <ul className="mx-auto mb-4 w-full max-w-md text-left text-base text-gray-700 dark:text-gray-200">
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Unlimited AI queries and content generation</span>
            </li>
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Early access to new AI-powered features</span>
            </li>
            <li className="mb-2 flex items-center gap-2">
              ✅ <span>Priority email & chat support</span>
            </li>
            <li className="flex items-center gap-2">
              ✅ <span>Member-only resources and tutorials</span>
            </li>
          </ul>
          <div className="mb-4">
            <span className="text-lg font-semibold text-blue-600 dark:text-blue-400">
              Ready to get started? Choose your plan below:
            </span>
          </div>
          <div className="flex w-full justify-center">
            <PricingTable />
          </div>
        </div>
      </div>
    </>
  )
}

export default SubscribePage
```

This page shows a list of available plans and features for Quillmate users:

![The Quillmate subscribe page](./4-quillmate_plans.png)

Selecting **Get Started** under the plan will simply open a drawer where I can enter my payment information to be processed by Stripe!

![The subscribe page with the checkout drawer open](./5-checkout.png)

### Protecting the AI Chat feature

For each article, Quillmate has a floating action button in the lower right that users can click to access the assistant. This feature should only be available to users who subscribe to the Pro plan, or more specifically, a plan with the “AI Assistant” feature.

The code for the floating action button has a simple check that uses the `has` helper from the Clerk SDK to check if the current user has a plan that includes the `ai_assistant` feature, which is the slug of the feature created earlier in this guide:

```tsx {{ filename: 'app/(protected)/components/ChatFAB.tsx', ins: [14, 17] }}
'use client'
import { useState } from 'react'
import { ChatBubbleOvalLeftEllipsisIcon } from '@heroicons/react/24/outline'
import { useAuth } from '@clerk/nextjs'
import SubscriptionModal from './SubscriptionModal'
import ChatInterface from './ChatInterface'

interface ChatFABProps {
  articleId: string
  articleContent: string
}

export default function ChatFAB({ articleId, articleContent }: ChatFABProps) {
  const { has } = useAuth()
  const [open, setOpen] = useState(false)

  const canUseAi = has?.({ feature: 'ai_assistant' })

  return (
    <>
      {/* FAB */}
      <button
        onClick={() => setOpen(true)}
        className="fixed right-4 bottom-12 z-50 rounded-full bg-blue-600 p-4 text-white shadow-lg hover:bg-blue-700 focus:outline-none"
        style={{ display: open ? 'none' : 'block' }}
        aria-label="Open Chat"
      >
        <ChatBubbleOvalLeftEllipsisIcon className="h-7 w-7" />
      </button>

      {/* Subscription Modal */}
      {open && !canUseAi && <SubscriptionModal onClose={() => setOpen(false)} />}

      {/* Chatbox */}
      {open && canUseAi && (
        <ChatInterface
          onClose={() => setOpen(false)}
          articleId={articleId}
          articleContent={articleContent}
        />
      )}
    </>
  )
}
```

As a best practice, we also want to protect any backend API calls that are used by the chat feature. The `has` function can also be used on server-side code as well:

```ts {{ filename: 'src/app/api/chat/route.ts', ins: [3, 7, 10] }}
import { CoreMessage, generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export async function POST(req: Request) {
  const { has } = await auth()

  if (!has({ feature: 'ai_assistant' })) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
  }

  const { messages }: { messages: CoreMessage[] } = await req.json()

  const { response } = await generateText({
    model: openai('gpt-4'),
    system: 'You are a helpful assistant. Format all responses as markdown.',
    messages,
  })

  return NextResponse.json({ messages: response.messages })
}
```

When a user is subscribed, they are then able to access the AI chat:

Users can also manage their plan directly from the [`<UserButton />`](/docs/components/user/user-button) component in the new Billing tab:

![The UserButton component with the Billing tab open](./6-user_button.png)

## Conclusion

[Clerk Billing](/billing) takes all the usual friction out of implementing subscriptions—no need to wire up your own Stripe forms, manage customer data, or create custom logic for checking user plans. It's fully integrated into your authentication layer, shares the same DX principles as the rest of Clerk, and gets you from “idea” to “monetized” in record time.

Whether you're just validating a pricing model or launching a full-featured SaaS product, Clerk Billing is built to get you there faster, with fewer moving parts.

---

# Getting started with Clerk Billing
URL: https://clerk.com/blog/intro-to-clerk-billing.md
Date: 2025-05-14
Category: Company
Description: Learn how to build a complete billing experience with Clerk and Stripe, from subscriptions and usage-based pricing to role-based access—no custom UI or webhooks required.

In this episode of Stripe Developer Office Hours, Clerk CTO and co-founder Braden Sidoti shares how you can build a complete billing experience—without webhooks, custom UIs, or Stripe session management. Instead of abstracting Stripe Billing, Clerk connects directly to your Stripe account: Stripe handles payments, and Clerk takes care of the user interface, entitlement logic, and session-aware billing flows.

You'll learn how to set up subscriptions, usage-based pricing, and org-level billing using Clerk's prebuilt components and APIs. Braden also walks through how Clerk supports role-based access, secure upgrades, and customer self-service, all tightly integrated with your existing auth layer.

The conversation also touches on why Clerk approaches infrastructure this way, how to go from prototype to production without glue code, and how tying billing to identity can simplify everything from user onboarding to plan upgrades. If you're looking to ship payments faster and with less complexity, this is a blueprint worth exploring.

### Try Clerk Billing Today

Clerk Billing works in every [country supported by Stripe](https://stripe.com/global) and syncs directly with your existing Clerk application.

---

# Multi-tenant analytics with Tinybird and Clerk
URL: https://clerk.com/blog/tinybird-and-clerk.md
Date: 2025-05-02
Category: Company
Description: How to use Clerk's Tinybird JWT template to secure Tinybird APIs for fast, easy, and secure user-facing analytics in your multi-tenant application.

When you run analytics for internal use, you often don't think much about role-based access control or multi-tenancy. You just connect a BI tool to your database or data warehouse and start running some queries.

But when you're serving analytics to your end users in your product or application, then you have to think about multi-tenancy, rate limiting, and access control down to the database level.

In traditional databases, this can be pretty challenging. It's why products like Clerk are popular; they abstract the complexities of auth and access control, typically to the transactional database that stores information about users, their IDs, and their metadata.

Adding real-time, user-facing analytics to the mix presents some additional challenges. Using Clerk JWT templates and Tinybird real-time analytics APIs with row-level security policies addresses those complexities.

Here's what you'll learn in this post:

1. How Tinybird APIs are secured using static tokens or JWTs
2. How to use the Clerk JWT template for Tinybird
3. How to modify Tinybird API definitions to support Clerk JWTs
4. How to create a React context provider for auth to Tinybird APIs
5. How to update Clerk Middleware to set the token

> \[!NOTE]
> Everything covered below can be gleaned from the [open source Tinybird Clerk JWT template](https://www.tinybird.co/templates/clerk-jwt).

## Getting familiar with Tinybird

[Tinybird](https://www.tinybird.co/) is an analytics backend for your application. You use Tinybird to build [real-time data APIs](https://www.tinybird.co/docs/publish/api-endpoints) over large amounts of data such as logs, event streams, or other time series data. Tinybird gives you the tooling and infrastructure to store, query, and serve analytics and metrics to end users of your application without having to fuss with the complexities of a real-time analytics database.

And when it comes to authentication and multi-tenancy, Tinybird offers some nice perks: Every API you build with Tinybird can be secured by either static tokens or [JSON Web Tokens (JWTs)](https://www.tinybird.co/docs/forward/get-started/administration/auth-tokens#json-web-tokens-jwts). Within those tokens, you can define security policies that limit access based on user metadata.

For example, here are three Tinybird JWTs with claims that limit access at the user, team, or organization level. You'll notice that these look almost identical, but with the `fixed_params` object modified to support the security policy we want to implement.

**User level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "user_123_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "user_id": "user123"
      }
    }
  ]
}
```

**Team level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "team_abc_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "team_id": "team_abc"
      }
    }
  ]
}
```

**Organization level**

```json
{
  "workspace_id": "31048b76-52e8-497b-90a4-0c6a5513920d",
  "name": "org_acme_jwt",
  "exp": 123123123123,
  "scopes": [
    {
      "type": "PIPE:READ",
      "resource": "my_api_endpoint",
      "fixed_params": {
        "organization_id": "org_acme"
      }
    }
  ]
}
```

Tinybird APIs are defined using SQL queries (called "pipes" in Tinybird parlance), extended with a [Jinja](https://jinja.palletsprojects.com/en/stable/)-like templating functions to add [advanced logic](https://www.tinybird.co/docs/cli/advanced-templates) or [query parameters](https://www.tinybird.co/docs/forward/work-with-data/query-parameters).

For example, consider the pipe called `my_api_endpoint` referenced in the above JWTs, which might look this:

```sql
SELECT
    toStartOfDay(timestamp) AS day,
    sum(some_number) AS total
FROM app_events
WHERE 1
{% if defined(user_id) %}
    AND user_id = {{String(user_id)}}
{% elif defined(team_id) %}
    AND team_id = {{String(team_id)}}
{% else %}
    AND organization_id = {{String(organization_id)}}
{% end %}
```

This pipe uses Tinybird's templating language to define three query parameters: `user_id`, `team_id`, and `organization_id` for the API endpoint. When supplied in the request, those parameters will trigger the query to filter.

For example, the following request would trigger a query against the database filtering only by events belonging to `user123` and return the response:

```bash
curl -d https://api.tinybird.co/v0/pipes/my_api_endpoint?user_id=user123&token=<static_token>
```

Of course, this is not a particularly secure implementation. We're passing both the `user_id` and the static token in the URL. If we were making a request directly from the browser, this would be insecure; the token would be compromised, and the `user_id` could easily be modified to access another user's data.

JWTs solve this for us. They're not stored server-side, so they're less likely to leak. They're validated by the backend service using a secret key and contain an embedded expiration time. The JWT contains data about the requesting party and is passed to the server in the request headers. Nothing gets exposed, the data returned is properly scoped, everybody wins.

Let's see how to use Clerk's JWT templates to secure a Tinybird API.

## Using Clerk JWTs to secure Tinybird endpoints

Everything I share below references the [Tinybird Clerk JWT template](https://www.tinybird.co/templates/clerk-jwt), which includes an open-source code example, video tutorial, and live demo. Feel free to go straight there and follow the guide, or follow along here.

### Setting up the Clerk JWT template

Go to the Clerk dashboard, and select **Configure** > **JWT Template**. Select the **Tinybird JWT template**.

![The Create Jwt Template modal in the Clerk dashboard](./image1.png)

Tinybird JWTs must be signed using the admin token for the workspace where the Tinybird resources are hosted. In Clerk, make sure to enable **Custom signing key** with **HS256** signing algorithm, and paste in the Workspace admin token:

![A screenshot of the Tinybird Clerk JWT template in the Clerk dashboard](./image2.png)

You can also set an optional token lifetime. Tinybird's API endpoints will return a 403 if requested with an expired token.

Modify the claims as needed for your application:

```json
{
  "name": "frontend_jwt",
  "scopes": [
    {
      "type": "PIPES:READ",
      "resource": "<YOUR-TINYBIRD-PIPE-NAME>",
      "fixed_params": {
        "organization_id": "{{org.slug}}",
        "user_id": "{{user.id}}"
      }
    }
  ],
  "workspace_id": "<YOUR-TINYBIRD-WORKSPACE-ID>"
}
```

The example above uses the Clerk shortcodes `org.slug` and `user.id` to reference the unique identifiers for the organization and user stored in Clerk. These get passed to the Tinybird resources secured by the JWT.

### Bonus: Rate limiting

Rate limiting can be an important part of multi-tenant architectures to prevent one or a few users from monopolizing resources. Tinybird JWTs support rate limiting through a `limits` claim:

```json {{ prettier: false }}
{
    "limits": {
        "rps": 10
    },
    …
}
```

When you specify a `limits.rps` field in the payload of the JWT, Tinybird uses the name specified in the payload of the JWT to track the number of requests being made. If the number of requests per second goes beyond the limit, Tinybird starts rejecting new requests and returns an "HTTP 429 Too Many Requests" error.

### Configuring your Tinybird APIs

The only thing you need to do is double-check (or update) your Tinybird APIs and ensure logic exists to filter on the passed parameters. This logic is customizable so you can handle requests in a way that makes sense for your use case. In a multi-tenant architecture, it often makes sense to filter by default at the organization level, making an `organization_id` required in the endpoint, then optionally adding user-level filtering for resources where a user might need to see their specific data:

```sql
SELECT * FROM table
WHERE organization_id = {{String(organization_id, required=True)}}
{% if defined(user_id) %}
    AND user_id = {{UUID(user_id)}}
{% end %}
```

## Configuring a frontend-only app for multi-tenant analytics

Once you've created the JWT template in Clerk and implemented the filtering logic in your Tinybird APIs, it's relatively simple to implement the logic in your application. The example below is for a TypeScript/Next.js app, but can easily be extended to any other language or framework.

### Basic project structure

The core components of this implementation in Next.js are:

- `TinybirdProvider.tsx` - A React context provider that manages the Tinybird JWT token
- `page.tsx` - The main page component that demonstrates token usage

### TinybirdProvider component

The `TinybirdProvider` component is a React context provider that manages the authentication token needed to access Tinybird's API endpoints. This provider automatically fetches and stores the user's token (provided by Clerk on sign-in) in its state and makes it available to any component in the app through the `useTinybirdToken` hook (by using a hook, we can avoid prop drilling across components).

```tsx {{ filename: 'src/app/providers/TinybirdProvider.tsx' }}
'use client'

import { useSession } from '@clerk/nextjs'
import { createContext, useContext, useState, ReactNode, useEffect } from 'react'

interface TinybirdContextType {
  token: string
  setToken: (token: string) => void
}

const TinybirdContext = createContext<TinybirdContextType | undefined>(undefined)

export function TinybirdProvider({ children }: { children: ReactNode }) {
  const [token, setToken] = useState('')
  const { session } = useSession()

  useEffect(() => {
    if (!session) return

    async function populateToken() {
      const token = await session?.getToken({ template: 'tinybird' })
      if (!token) return
      setToken(token)
    }

    populateToken()
  }, [session])

  return <TinybirdContext.Provider value={{ token, setToken }}>{children}</TinybirdContext.Provider>
}

export function useTinybirdToken() {
  const context = useContext(TinybirdContext)
  if (context === undefined) {
    throw new Error('useTinybirdToken must be used within a TinybirdProvider')
  }
  return context
}
```

The provider automatically fetches the token when a user signs in and updates it when the session changes.

### Using the token in your application

To use the Tinybird token in your application, simply wrap your app with the `TinybirdProvider` and use the `useTinybirdToken` hook where needed:

```tsx {{ filename: 'src/app/layout.tsx' }}
import { TinybirdProvider } from './providers/TinybirdProvider'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ClerkProvider>
          <TinybirdProvider>{children}</TinybirdProvider>
        </ClerkProvider>
      </body>
    </html>
  )
}
```

In any component that needs to make Tinybird API calls, use the `useTinybirdToken` hook to get the token and make requests:

```tsx {{ filename: 'src/app/components/MyComponent.tsx', prettier: false }}
import { useTinybirdToken } from './providers/TinybirdProvider'

function MyComponent() {
  const { token } = useTinybirdToken()

  const fetchData = async () => {
    const response = await fetch('https://api.tinybird.co/v0/pipes/your_pipe.json', {
      headers: {
        Authorization: `Bearer ${token}`
      }
    })
    // Handle response
  }

  return (
    // Your component JSX
  )
}
```

This implementation provides a clean, efficient way to handle multi-tenant analytics with Clerk and Tinybird, while keeping the authentication logic on the client side. The provider automatically manages the token, and components can easily access it through the `useTinybirdToken` hook.

## Demo

Want to see how this works in practice? Check out this [live Clerk JWT demo for a Tinybird API.](https://clerk-tinybird.vercel.app/) If you want to check out the code, you can find it in [this repository](https://github.com/tinybirdco/clerk-tinybird).

## Get started

Building real-time analytics features into your application is pretty simple with Clerk and Tinybird. Just create a JWT template, add filtering parameters to your Tinybird APIs, and use Clerk middleware to set the token header in the request.

If you'd like to build a user-facing analytics MVP, [sign up for Tinybird](https://cloud.tinybird.co/signup) (it's free, no time limit) and follow the quick start. You'll be able to build your first API in a few minutes and have it secured in your application just as quickly using Clerk.

---

# How Clerk integrates with a Next.js application using Supabase
URL: https://clerk.com/blog/how-clerk-integrates-nextjs-supabase.md
Date: 2025-03-31
Category: Company
Description: Learn how Supabase works with Next.js to increase security and reduce development hours, and how Clerk integrates with this stack.

Supabase is one of the most popular backend platforms on the web, but it breaks from traditional architecture patterns when accessing data.

Full-stack applications typically have distinct frontend and backend codebases. When a user needs to access or modify data, the frontend proxy requests through the backend to the database. This prevents third parties from obtaining the connection string used for database operations.  In comparison, Supabase allows you to build applications *without* a backend, yet still keep your data safe.

In this article, you’ll learn how Supabase functions with Next.js applications, and how Clerk integrates with this configuration.

This article assumes you have [familiarity with Next.js and Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs), and have ideally built an application using them both.

## How Supabase works with Next.js applications

Supabase provides a way to access data from the client directly without going through your own backend.

As a full-stack JavaScript framework, Next.js supports accessing Supabase through server-side code, but most developers take advantage of the Supabase API to gain direct access to the underlying data. In this setup, the client will request data from the database through the API and include an authorization token so that the service can associate the request with a specific user.

To protect the data, Supabase uses a feature of Postgres (the underlying database engine) called Row Level Security (RLS). RLS is a way to secure database tables on a per-record by using policies to evaluate each request and determine if the user making a data access/modification request is authorized to do so on the specific rows.

> \[!NOTE]
> To learn a more about how the Supabase API works with RLS, check out [our article comparing Supabase Auth and Clerk](/blog/how-clerk-integrates-with-supabase-auth) where we dive deeper into both implementations.

## How Clerk integrates with Next.js and Supabase

When a user signs in with Clerk, they receive short-lived tokens that are used by backend services to validate requests. In a traditional configuration, this would be the backend code you write for your application. As stated in the previous section, applications using Supabase often bypass any custom backend code and send requests directly to Supabase, so there are a few considerations to be taken so that Clerk will work properly with Supabase.

### Verifying JWTs with the JWKS endpoint

In the traditional configuration, Clerk’s SDKs will automatically verify your request using the public key associated with Clerk application.

Supabase does not support this since you do not control the backend code that powers the Supabase APIs. However, Supabase does support integrating with Clerk as a third party authentication provider, where JWTs sent to the Supabase backend will automatically be verified with your Clerk application using the JWKS endpoint for that application.

A JSON Web Key Set (JWKS) is a JSON object that contains a set of keys used to verify the signatures of JWTs. This object is often publicly available through a standard URL so they can be used by external systems and integrations to perform this verification.

The following diagram shows what this flow looks like:

![JWT Verification Flow](./diagram.png)

1. The user signs in using Clerk
2. Clerk issues JWT to the user
3. User makes request to Supabase, including JWT
4. Supabase verifies with the Clerk application JWKS endpoint
5. Clerk verifies token
6. Supabase response to the user with the requested data

### Setting the correct role

Supabase supports a number of different roles which each provide different levels of access for managing database and storage operations. The role which the request assumes is set in the JWT itself as the `role` claim.

To adhere to Supabase standards, the `role` in the JWT should be set to `authenticated` so that the request has the correct level of security. The following snippet shows what the claims of the JWT are when properly configured:

```json
{
  "app_metadata": {},
  "aud": "authenticated",
  "azp": "http://localhost:3000",
  "email": "brian@clerk.dev",
  "exp": 1742938129,
  "iat": 1742938069,
  "iss": "https://modest-hog-24.clerk.accounts.dev",
  "jti": "01a722552fe233fc649b",
  "nbf": 1742938064,
  "role": "authenticated", // Note the 'role' claim
  "sub": "user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7"
}
```

### Using the Clerk JWT for requests to Supabase

Clerk stores its token in cookies that automatically get sent with every request in the same domain. Without some infrastructure considerations, your Supabase application is likely not going to be accessible on the same domain as your application.

Fortunately, you can obtain the current session token using the `getToken` function of the SDK:

```tsx
import { useSession } from '@clerk/nextjs'

const { session } = useSession()
session.getToken()
```

When creating a Supabase client in your front end, you can provide the token created using the JWT template into the `accessToken` option of the Supabase client:

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

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_KEY!,
  {
    // Use the session.getToken() method from Clerk
    accessToken: () => session?.getToken(),
  },
)
```

This will ensure that each request sent to Supabase will contain the custom token created with the JWT secret so that Supabase can validate it, extract the claims, and use it to access the requested data.

> \[!NOTE]
> Interested in more content featuring Supabase? [Let us know!](https://feedback.clerk.com/roadmap?id=f95553cd-204d-43b8-b2b5-1f84ecf1bd59)

### Using the Clerk user ID in RLS policies

Once the token has been verified, the claims within that token are accessible to Postgres using the built-in `auth.jwt()` function (used to access the JWT claims) and accessing the `sub` claim.

For example, say a `tasks` table has the following RLS policy defined:

```sql
create policy "select_by_user_id" on tasks
for select using (auth.jwt()->>'sub' = user_id);
```

Assuming the Clerk user ID is `user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7`, running a `select` statement will effectively apply a filter to restrict what data is returned as shown below:

```sql
select * from tasks;
-- Turns into this:
select * from tasks where user_id = 'user_2s2XJgQ2iQDUAsTBpem9QTu8Zf7';
```

This can provide an additional layer of security for applications with a dedicated backend, or save the developer from having to build one in the first place, if implemented properly.

## Examining an implementation of Next.js using Clerk and Supabase

To see this implemented in a real-world application, we’ll explore the code for Quillmate. Quillmate is an open-source, web-based writing tool built with Next.js, Supabase, and Clerk.

> \[!NOTE]
> The source code for Quillmate is available [on GitHub](https://github.com/bmorrisondev/quillmate).

### Configuring the integration

To integrate Supabase with Clerk, you’d start in the Supabase Dashboard and navigate to **Authentication** > **Sign In/Up** > **Third Party Auth** and add Clerk as the provider. The modal that appears will prompt you for a **Clerk Domain**, but also contains a link to open the [Supabase Integration Setup page in Clerk](https://dashboard.clerk.com/setup/supabase) to select your application and enable the integration:

![Supabase Integration Setup](./supabase-dash.png)

Once completed, you’ll be provided with the Clerk Domain for your application and instance. You simply need to copy and paste this into the Supabase Dashboard and click **Create connection**.

![Supabase Integration Setup](./connect-supabase.png)

Supabase now knows that when a JWT is received that was issued by Clerk, it should use the JWKS endpoint available on the provided Clerk Domain for verification. On the Clerk side, the session claims have been updated to include `"role": "authenticated"` with all new JWTs created:

![Managed claim](./managed-claim.png)

### Accessing Supabase with a Clerk JWT

The following code demonstrates how the JWT template is accessed when creating the Supabase client. This code uses the React Context API to simplify creating and accessing the client with the Clerk `getToken` function, but you may create the client however you need as long as you can pass in the `getToken` function:

```tsx
'use client'

import { createClient, SupabaseClient } from '@supabase/supabase-js'
import { useSession } from '@clerk/nextjs'
import { createContext, useContext, useEffect, useState } from 'react'

type SupabaseContext = {
  supabase: SupabaseClient | null
  isLoaded: boolean
}

const Context = createContext<SupabaseContext>({
  supabase: null,
  isLoaded: false,
})

type Props = {
  children: React.ReactNode
}

export default function SupabaseProvider({ children }: Props) {
  const { session } = useSession()
  const [supabase, setSupabase] = useState<SupabaseClient | null>(null)
  const [isLoaded, setIsLoaded] = useState(false)

  useEffect(() => {
    if (!session) return

    const client = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_KEY!,
      {
        accessToken: () => session?.getToken(),
      },
    )

    setSupabase(client)
    setIsLoaded(true)
  }, [session])

  return (
    <Context.Provider value={{ supabase, isLoaded }}>
      {!isLoaded ? <div>Loading...</div> : children}
    </Context.Provider>
  )
}

export const useSupabase = () => {
  const context = useContext(Context)
  if (context === undefined) {
    throw new Error('useSupabase must be used within a SupabaseProvider')
  }
  return {
    supabase: context.supabase,
    isLoaded: context.isLoaded,
  }
}
```

For the sake of this explanation, the only table of interest is the `articles` table. The `articles` table itself has a relatively simple schema:

```sql
create table if not exists public.articles (
  id bigint primary key generated always as identity,
  title text not null,
  content text not null,
  user_id text not null,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);
```

Quillmate will request a list of records from the `articles` table using the following code. Note that I am not leveraging the user ID within these queries. This is because the Clerk/Supabase integration will automatically parse the user ID from the request.

```tsx
const { data, error } = await supabase
  .from('articles')
  .select('*')
  .order('updated_at', { ascending: false })
```

And is protected from the following RLS policies. Note the use of `auth.jwt()->>'sub'` which extracts the `sub` claim (which is the user ID) from the JWT used on the request and compares it with the `user_id` column in the table.

```sql
CREATE POLICY "Users can view their own articles"
  ON public.articles
  FOR SELECT
  USING (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can create their own articles"
  ON public.articles
  FOR INSERT
  WITH CHECK (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can update their own articles"
  ON public.articles
  FOR UPDATE
  USING (auth.jwt()->>'sub' = user_id);

CREATE POLICY "Users can delete their own articles"
  ON public.articles
  FOR DELETE
  USING (auth.jwt()->>'sub' = user_id);
```

## Conclusion

Building an application with Supabase can challenge the architectural norms that developers have used for decades. However, once you properly understand how the data is accessed and secured by Supabase, building with this new approach can save development time and potentially increase table security by ensuring that each request is evaluated with RLS on a row-by-row basis.

This includes B2B applications, which is covered in our follow up article on [building multi-tenancy applications with Supabase and Clerk](/blog/multitenancy-clerk-supabase-b2b).

---

# Next.js CVE-2025-29927
URL: https://clerk.com/blog/cve-2025-29927.md
Date: 2025-03-23
Category: Company
Description: On March 21, 2025, Next.js disclosed a critical security vulnerability, CVE-2025-29927, that may impact your application.

On March 21, 2025, Next.js disclosed a critical security vulnerability, [CVE-2025-29927](https://nextjs.org/blog/cve-2025-29927), that may impact your application.

This vulnerability allows attackers to bypass middleware-based authentication and authorization protections, potentially allowing unauthorized access to your application.

## Impacted applications

> \[!IMPORTANT]
> If your application is not using Next.js, or if it is hosted on Vercel or Netlify, it is not impacted.

Yesterday, we mistakenly [announced on X](https://x.com/clerk/status/1903497002828120426) that all applications using Clerk were not impacted. Since then, we have discovered two scenarios where your application may be impacted.

1. You use Clerk's middleware for protecting routes that do not directly read user data. This is most likely to impact static applications that solely rely on middleware for authentication checks. **If you call any of the following methods in routes protected by middleware, your page or endpoint is safe:**
   - `auth()`
   - `getAuth()`
   - `protect()`
   - `currentUser()`

2. Or, you have not upgraded to `@clerk/nextjs@5.2` or higher, which was released in June 2024.

## Patching your application

If your application is impacted, the remediation is to upgrade your `next` package as follows:

- For Next.js 15.x, this issue is fixed in `15.2.3` forward
- For Next.js 14.x, this issue is fixed in `14.2.25` forward
- For Next.js 13.x, this issue is fixed in `13.5.9` forward

If patching to a safe version is infeasible, it is recommended that you prevent external user requests which contain the `x-middleware-subrequest` header from reaching your Next.js application. If your application uses Cloudflare, this can safely be accomplished with a [Managed WAF](https://developers.cloudflare.com/changelog/2025-03-22-next-js-vulnerability-waf/) rule.

Even if you are not impacted, we strongly recommend that you upgrade to the latest versions of `next` and `@clerk/nextjs`.

## Additional support is available

We have also sent an email to the administrators of all Clerk applications that are potentially impacted by this vulnerability. If you have questions, need help determining whether your application is at risk, or need help with mitigation, reply to the email you received or reach out directly to [support@clerk.com](mailto:support@clerk.com)

## Security at Clerk

Our announcement on X that Clerk applications were not impacted was a significant error. We apologize, and will be reflecting on and improving our procedures for zero-day vulnerabilities to ensure it does not happen again.

Going forward, we are pleased that the Next.js team has committed to giving Clerk advance notice on vulnerabilities. We will be seeking similar relationships with other framework authors.

---

# Postmortem: February 6, 2025 service outage
URL: https://clerk.com/blog/postmortem-feb-6-2025-service-outage.md
Date: 2025-02-11
Category: Company
Description: Learn more about our service outage, including the timeline of events and our remediations.

On Thursday, February 6th, 2025, a database query was directly executed to deprecate a feature for 3,700 customers, and an error in the query resulted in immediate downtime for those customers. In addition, the downtime triggered automatic retries elsewhere in our service which nearly overloaded our infrastructure, and created significant delays for our other customers for 4 minutes, until the retry backoff took effect.

The incident lasted a total of 26 minutes, from the initial error to when the query was successfully reversed, and our systems returned to normal.

As a provider of mission-critical infrastructure, we recognize that this outage is unacceptable. After a detailed review of the incident, we have determined several actions that can be taken to mitigate its recurrence. Some have already been implemented, while others will require more significant engineering efforts.

In this postmortem, we discuss the timeline of events, and our complete set of remediations.

## Timeline of events

- **9:43 UTC** — Erroneous update query runs, setting `false` values to `"true"` within a jsonb field.
- **9:45 UTC** — Engineers receive error alerts and begin investigating.
- **9:47 UTC** — First customer outage reports arrive.
- **9:48 UTC** — Internal incident is declared.
- **9:50 UTC** — Status page updated (initially with an incorrect start time).
- **10:00-10:04 UTC** — Engineers begin manually restoring service for customers while a bulk resolution is prepared.
- **10:05 UTC** — Bulk update query is executed to correct the issue.
- **10:06 UTC** — Bulk update query completes, service health is restored.
- **10:10 UTC** — Status page updated to reflect restored status with accurate start/end times.

## Remediations

### Tuning automatic retry mechanisms

One of our retry mechanisms was misconfigured to retry too aggressively on 500-class errors, which increased the blast radius of this event. An adjustment to the mechanism has already been applied, and an audit of other retry mechanisms is being conducted.

### Further limiting direct database access

Direct database access at Clerk is already significantly limited, with only a small subset of our most senior team having this permission. However, our processes indicated they should use their own judgement for when it is safe and appropriate to leverage the capability.

Going forward, these team members will retain access, but our policies will dictate that it is only leveraged in true emergency situations, when downtime is actively impacting our customers. Other changes must be executed from within our change management tooling.

### Mandating staged rollouts for all changes to critical infrastructure

In 2024, Clerk’s platform team developed several new mechanisms for staged rollouts. As Clerk has grown, we have seen a healthy culture where our engineers *demand* that staged rollout infrastructure is in place. In many cases, we’ve delayed launches to build more mechanisms where they are missing.

In our review since the incident, we confirmed that the vast majority of changes to our critical systems leverage staged rollouts. However, when our team noted exceptions, it was always because the change was considered simple, including the one that led to this incident.

In addition, our review revealed that different projects have approached building cohorts for staged rollouts differently.

Going forward, we will be mandating that all changes to critical infrastructure require staged rollouts. We will also codify a process for building and ordering cohorts, which will incorporate the number of active users an application is supporting, and the subscription plan that applications are enrolled on.

### Improving SDK resilience for session management service outages

Clerk’s session management service is designed with a once-per-minute JWT refresh. We leverage this design in three critical areas of our service:

- **Session revocation:** When a session is revoked administratively – either by the user or by an application administrator – the revocation is achieved by blocking new JWTs from being generated. Using a short-lived JWT means we can guarantee revocation within one minute.
- **Abuse detection and prevention:** CAPTCHAs during sign up have become less effective recently as AI has gained the ability to solve them. At the same time, freemium and trial pricing have become commonplace. We’re engaged in a constant cat-and-mouse game with these attackers, and have found that our once-per-minute session refresh mechanism is a much more effective place to detect and prevent abuse than sign up.
- **XSS mitigation:** JavaScript-accessible JWTs are an expectation of many application architectures, despite seeming antithetical to web security best practices on its face. The concern is that script-accessible JWTs can be exfiltrated during XSS attacks, which would allow continued use of the JWT even after the XSS is patched. Clerk can safely allow script access because our JWTs expire every minute, which ensures that successful exfiltration would not meaningfully extend an XSS attack.

In normal operation, our once-per-minute refresh is an implementation detail that most of our customers are not aware of. However, in the event of an outage like Thursday’s, it means our customers have a strong uptime dependency on Clerk.

Going forward, we would like to eliminate as much of this strong uptime dependency as possible. We believe we can update our SDKs so that if our session management service goes down, existing sessions are maintained throughout the outage, while new session creation, session revocation, abuse prevention, and XSS mitigation are not operational. This would result in future outages having less impact on our customers.

In the interest of full disclosure, we want to highlight that this is not a simple adjustment and will take time to develop. As a simple example of a challenge, we will need to ensure the `/.well-known/jwks.json` endpoint is hardened to avoid the downtime, and/or we need provide a mechanism to self-host the JWT public key. Regardless of the effort it takes, we are placing high priority on this project.

### Completely decoupling session management from user management

At a high level, Clerk operates two services: user management, which covers sign up, sign in, and user profiles, and session management, which only handles sessions. These two systems started tightly coupled, but have naturally decoupled with time as they represent significantly different workloads:

- User management requires relatively low read and write, but it has many moving pieces. There are many different settings, and our customers use thousands of different permutations of those settings. In addition, we’re frequently introducing new settings and modifying existing settings as authentication evolves.
- Session management is the opposite. It’s extremely high read, low write, and has relatively few moving pieces.

In this incident, an error in our user management service brought down our session management service.

Going forward, we plan to decouple session management from user management as much as possible. They will still be tightly *integrated*, since sign up and sign in lead to the creation of a session, but downtime in user management should not lead downtime in session management.

### Eliminating the use of JSON column types for structured and typed data

Some application settings are stored in JSON column types. These columns have been used primarily for convenience, with types being enforced at our compute layer. In this incident, strict typing was not enforced for the query because it was executed directly against the database, which led to the outage.

Going forward, in addition to further limiting direct database access, we are ceasing additional use of JSON column types for structured and typed data. Instead, we will use strongly typed database columns, which would have prevented the erroneous query from being executed. Over time, we will also migrate and deprecate our existing usage of JSON column types.

## Looking Ahead

We regret the impact this incident had on our customers. At Clerk, reliability is a top priority, and this postmortem reflects our commitment to transparency and continuous improvement.

Some fixes are already in place, while others—like enhanced SDK resilience and service decoupling—are being prioritized to prevent future incidents and strengthen our platform.

For any questions, please [contact support](/contact)

---

# Implement Role-Based Access Control in Next.js 15
URL: https://clerk.com/blog/nextjs-role-based-access-control.md
Date: 2025-02-07
Category: Company
Description: Learn Role-Based Access Control (RBAC) by building a complete Q&A platform.

Assigning permissions to individual users is a complex task, especially when you have a large number of users.

[Role-Based Access Control (RBAC)](/glossary#role-based-access-control-rbac) is a popular approach to managing access permissions in software applications, allowing you to assign different roles and permissions to different users.

This article will guide you through building a Q\&A platform using Next.js and Neon, and show you how to implement authentication and RBAC with [Clerk](/nextjs-authentication).

## What is Role-Based Access Control?

Before we get started, let's understand RBAC and how it will be implemented in the Q\&A platform.

RBAC is a security method that allows users to interact with features of an application or system based on their roles and the permissions granted to those roles.

This approach simplifies access management by grouping permissions into roles instead of assigning them to individual users, making it easier to maintain and scale as your application grows.

By implementing RBAC, organizations can enforce the [principle of least privilege](https://www.cyberark.com/what-is/least-privilege/), ensuring users only have access to the resources necessary for their specific responsibilities.

Below is a breakdown of the permissions for each role in the Q\&A platform:

| Role        | Description                                                               | Permissions                                                                                                                            |
| ----------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Viewer      | Users not signed in or logged into the Q\&A platform.                     | • View questions and answers                                                                                                           |
| Contributor | Registered users who can ask or answer questions in the Q\&A platform.    | Everything from the Viewer role + • Post questions• Answer questions• Edit own questions and answers• Delete own questions and answers |
| Moderator   | Users with additional permissions to manage content in the Q\&A platform. | Everything from the Contributor role + • Admin dashboard access• Approve and disapprove questions• Approve and disapprove answers      |
| Admin       | Users with full control over the Q\&A platform.                           | Everything from the Moderator role + • Edit others' questions and answers• Delete others' questions and answers• Manage user roles     |

With that in mind, let us jump straight into building the Q\&A platform and implement RBAC using Clerk.

> \[!IMPORTANT]
> Clerk provides two approaches to RBAC.
>
> [Organizations](/docs/organizations/overview) is ideal for B2B applications like GitHub or Notion, where your customers are teams or companies who need to invite team members and manage their roles. It includes built-in role management and components for role-based rendering.
>
> [User metadata](/docs/references/nextjs/basic-rbac), demonstrated in this guide, is better suited for B2C applications with simple permission structures where individual users have different access levels but don't belong to teams.

## What you'll build

Before writing any code, let's take a look at how our platform works.

The landing page enables users to sign up or sign in using the **Start Exploring** button. It features a clean design with a navigation menu and welcome message.

Once signed in, users can ask questions and provide answers. Each question displays the author's name, timestamp, and interaction options. Users can edit or delete their own content, and view answers from other contributors.

![Homepage of a Q\&A Platform with a centered welcome message, navigation menu at the top showing Home, Q\&A, and Admin options, and a user profile icon. The main content features a large 'Welcome to Our Q\&A Platform' heading, followed by a subtitle encouraging community participation and a 'Start Exploring' button. The page has a clean, minimal design with a white background and footer copyright text.](./home-page.png)

![Q\&A Platform page showing a question input field at the top with an 'Ask' button. Below are two example questions: 'What is React used for?' and 'How many days are in a week?' Each question displays the author's name (Brian Morrison), timestamp, and has edit and delete icons. Questions include their answers with similar metadata and interaction options. The page maintains the same header with navigation menu and user profile icon as the homepage.](./qa-page.png)

Administrators can review and moderate all submitted content through the Admin Dashboard. The page displays questions and answers with approval status indicators, allowing admins to approve or reject content using simple checkmark and X icons.

![Admin Dashboard showing moderation controls for Q\&A content. The page displays questions and answers with approval status indicators - green 'Approved' tags for accepted content and red 'Disapproved' tags for rejected content. Each entry has approve/reject buttons (green checkmark and red X icons). The dashboard includes a 'Set Roles' button at the top right. Questions shown include 'What is React used for?' and 'How many days are in a week?' with their respective answers and moderation statuses. The page maintains the consistent header with navigation and user profile.](./admin-dashboard.png)

Finally, administrators can manage user permissions through the role management page. Using a search interface, admins can find users and assign appropriate roles (Admin, Moderator, Contributor, or Viewer) or remove existing roles.

![User role management page with a search bar and Submit button at the top. Below shows a user profile for Brian Morrison with their current role listed as admin. A row of role management buttons includes 'Make Admin', 'Make Moderator', 'Make Contributor', 'Make Viewer', and a red 'Remove Role' button. The page maintains the standard Q\&A Platform header with navigation menu and user profile icon.](./user-roles-page.png)

## Building the frontend

In this section, we will build the frontend of the Q\&A platform. In order to follow along, you should have a basic understanding of React or Next.js, as well as Node.js installed.

> If you're new to Next.js authentication, check out our [Ultimate Guide to Next.js Authentication](/blog/nextjs-authentication) for foundational concepts.

### Install dependencies

Start by running the following command in your terminal to bootstrap a Next.js application, accepting the default options as they are presented:

```bash
npx create-next-app@latest qa-app
cd qa-app
```

Next, run the following commands to initialize shadcn/ui, once again accepting the default configuration options as they are presented:

```bash
npx shadcn@latest init
```

Add the necessary components to build the UI components of the Q\&A platform:

```bash
npx shadcn@latest add button input card separator badge
```

Finally, install Lucide React, which will be used to render the icons in the UI components:

```bash
npm install lucide-react
```

With the dependencies in place, let's create the components that used with the layout, starting with the type definitions we'll use throughout the process.

### Creating the header and updating the homepage

The header component will allow our users to navigate to different parts of the application, as well as hold the Clerk `UserButton` component (to be done later in this guide).

Create the `src/components/Header.tsx` file and paste in the following code:

```tsx {{ filename: 'src/components/Header.tsx' }}
'use client'

import Link from 'next/link'
import { Button } from '@/components/ui/button'

const Header = () => {
  return (
    <header className="border-b bg-white">
      <div className="container mx-auto px-4">
        <div className="flex h-16 items-center justify-between">
          <Link href="/" className="text-xl font-bold">
            Q&A platform
          </Link>
          <nav>
            <ul className="flex space-x-4">
              <li>
                <Link href="/">
                  <Button variant="ghost">Home</Button>
                </Link>
              </li>
              <li>
                <Link href="/qa">
                  <Button variant="ghost">Q&A</Button>
                </Link>
              </li>

              <li>
                <Link href="/admin">
                  <Button variant="ghost">Admin</Button>
                </Link>
              </li>
            </ul>
          </nav>
        </div>
      </div>
    </header>
  )
}

export default Header
```

With the header component in place, replace the code in `app/page.tsx` with the following, which will update the homepage to use the header component and match the demo:

```tsx {{ filename: 'app/page.tsx' }}
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import Header from '@/components/Header'

export default function Home() {
  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="flex-grow">
        <section className="flex w-full items-center justify-center py-12 md:py-24 lg:py-32 xl:py-48">
          <div className="container px-4 md:px-6">
            <div className="flex flex-col items-center space-y-4 text-center">
              <div className="space-y-2">
                <h1 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl lg:text-6xl/none">
                  Welcome to Our Q&A platform
                </h1>
                <p className="mx-auto max-w-[700px] text-gray-500 md:text-xl dark:text-gray-400">
                  Join our community to ask questions, share knowledge, and learn from others.
                </p>
              </div>
              <div className="space-x-4">
                <Link href="/qa">
                  <Button size="lg">Start Exploring</Button>
                </Link>
              </div>
            </div>
          </div>
        </section>
      </main>
      <footer className="flex w-full items-center justify-center bg-gray-100 py-6 dark:bg-gray-800">
        <div className="container px-4 md:px-6">
          <p className="text-center text-sm text-gray-500 dark:text-gray-400">
            (c) 2024 Q&A platform. All rights reserved.
          </p>
        </div>
      </footer>
    </div>
  )
}
```

### Create the Q\&A section

Next, you'll build out the Q\&A section, where signed-in users can ask and answer questions and anonymous users can view questions. We'll start by creating a few shared components that will be used throughout the application before creating the page that will display the Q\&A section.

Let's start by creating a file named `types/types.d.ts` to define the types we'll be using. Populate it with the following code:

```ts {{ filename: 'types/types.d.ts' }}
interface Answer {
  id: number | null
  ans: string
  approved?: boolean | null
  contributor: string
  contributorId: string
  questionId: number
  timestamp?: string // ISO 8601 string format
}

interface Question {
  id: number | null
  quiz: string
  approved: boolean | null
  answers: Answer[]
  contributor: string
  contributorId: string
  timestamp?: string // ISO 8601 string format
}

type Roles = 'admin' | 'moderator' | 'contributor' | 'viewer'
```

Now you'll create the component that renders the form where users can ask questions. Create the `src/components/QuestionForm.tsx` file and paste in the following:

```tsx {{ filename: 'src/components/QuestionForm.tsx' }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

interface QuestionFormProps {
  onSubmit: (question: string) => void
}

export default function QuestionForm({ onSubmit }: QuestionFormProps) {
  const [quiz, setQuiz] = useState('')
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (quiz.trim()) {
      onSubmit(quiz)
      setQuiz('')
      setShowSubmitText(true)
    }
  }

  return (
    <form onSubmit={handleSubmit} className="mb-6">
      <div className="flex gap-2">
        <div className="flex-grow">
          <Input
            type="text"
            name="quiz"
            id="quiz"
            value={quiz}
            onChange={(e) => setQuiz(e.target.value)}
            placeholder="Ask a question..."
            className="flex-grow"
          />
          <div className="h-4 text-sm text-green-500 transition-all">
            {showSubmitText ? 'Your question has been submitted for review.' : ''}
          </div>
        </div>
        <Button type="submit">Ask</Button>
      </div>
    </form>
  )
}
```

Next, modify the `src/lib/utils.ts` file to add a helper function used to format dates in a more friendly way, which will be used in the `QuestionItem` and `AnswerItem` components you'll create in a moment:

```ts {{ filename: 'src/lib/utils.ts', ins: [[8, 17]], del: [] }}
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

export function formatDate(dateString: string) {
  const date = new Date(dateString)
  return date.toLocaleString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  })
}
```

Now create a `QuestionItem` component that manages and displays a question and its answers. Moreover, the component allows adding, editing, and deleting of questions or answers.

Create the `src/components/QuestionItem.tsx` file and paste the following code into the file:

```tsx {{ filename: 'src/components/QuestionItem.tsx' }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Pencil, Trash2 } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import AnswerItem from './AnswerItem'
import { formatDate } from '@/lib/utils'

interface Props {
  question: Question
  onEditQuestion: (id: number, newText: string) => void
  onDeleteQuestion: (id: number) => void
  onAddAnswer: (questionId: number, answerText: string) => void
  onEditAnswer: (answerId: number, newText: string) => void
  onDeleteAnswer: (answerId: number) => void
}

export default function QuestionItem({
  question,
  onEditQuestion,
  onDeleteQuestion,
  onAddAnswer,
  onEditAnswer,
  onDeleteAnswer,
}: Props) {
  const [answer, setAnswer] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [editedQuestion, setEditedQuestion] = useState(question.quiz)
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (answer.trim()) {
      if (question.id !== null) {
        onAddAnswer(question.id, answer)
        setShowSubmitText(true)
      }
      setAnswer('')
    }
  }

  const handleQuestionEdit = () => {
    if (editedQuestion.trim() && editedQuestion !== question.quiz) {
      if (question.id !== null) {
        onEditQuestion(question.id, editedQuestion)
      }
      setIsEditing(false)
    }
  }

  const handleAnswerEdit = async (answerId: number | null, newText: string) => {
    if (answerId !== null && question.id !== null) {
      await onEditAnswer(answerId, newText)
    }
  }

  const handleAnswerDelete = async (answerId: number | null) => {
    if (answerId !== null && question.id !== null) {
      await onDeleteAnswer(answerId)
    }
  }

  return (
    <Card>
      <CardHeader>
        {isEditing ? (
          <div className="flex gap-2">
            <Input
              value={editedQuestion}
              onChange={(e) => setEditedQuestion(e.target.value)}
              className="flex-grow"
            />
            <Button onClick={handleQuestionEdit}>Save</Button>
            <Button variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <div>
            <div className="mb-2 flex items-center justify-between">
              <CardTitle>{question.quiz}</CardTitle>

              <div>
                <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                  <Pencil className="h-4 w-4" />
                </Button>
                <Button
                  variant="ghost"
                  size="icon"
                  onClick={() => question.id !== null && onDeleteQuestion(question.id)}
                >
                  <Trash2 className="h-4 w-4" />
                </Button>
              </div>
            </div>
            <div className="text-sm text-gray-500">
              <span>{question.contributor}</span>
              <span> • </span>
              <span>{question.timestamp && formatDate(question.timestamp)}</span>
            </div>
          </div>
        )}
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.filter((a) => a.approved !== false).length > 0 ? (
          <ul className="space-y-4">
            {question.answers
              .filter((a) => a.approved !== false)
              .map((answer, index, filteredAnswers) => (
                <li key={answer.id}>
                  <AnswerItem
                    answer={answer}
                    onEditAnswer={(newText) => handleAnswerEdit(answer.id, newText)}
                    onDeleteAnswer={() => handleAnswerDelete(answer.id)}
                  />
                  {index < filteredAnswers.length - 1 && <Separator className="my-2" />}
                </li>
              ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>

      <CardFooter>
        <form onSubmit={handleSubmit} className="w-full">
          <div className="flex gap-2">
            <div className="flex-grow">
              <Input
                type="text"
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Add an answer..."
              />

              <div className="h-4 text-sm text-green-500 transition-all">
                {showSubmitText ? 'Your answer has been submitted for review.' : ''}
              </div>
            </div>
            <Button type="submit">Answer</Button>
          </div>
        </form>
      </CardFooter>
    </Card>
  )
}
```

Your editor may be displaying an error regarding the `AnswerItem` component, which does not yet exist. This component is used to render answers for each question.

Create the `src/components/AnswerItem.tsx` file and paste the following code into the file:

```tsx {{ title: 'src/components/AnswerItem.tsx' }}
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Pencil, Trash2 } from 'lucide-react'
import { formatDate } from '@/lib/utils'

interface Props {
  answer: Answer
  onEditAnswer: (newText: string) => void
  onDeleteAnswer: () => void
}

function AnswerItem({ answer, onEditAnswer, onDeleteAnswer }: Props) {
  const [isEditing, setIsEditing] = useState(false)
  const [editedAnswer, setEditedAnswer] = useState(answer.ans)

  const handleEdit = () => {
    if (editedAnswer.trim() && editedAnswer !== answer.ans) {
      onEditAnswer(editedAnswer)
      setIsEditing(false)
    }
  }

  return (
    <div>
      {isEditing ? (
        <div className="flex w-full gap-2">
          <Input
            value={editedAnswer}
            onChange={(e) => setEditedAnswer(e.target.value)}
            className="flex-grow"
          />
          <Button onClick={handleEdit}>Save</Button>
          <Button variant="outline" onClick={() => setIsEditing(false)}>
            Cancel
          </Button>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="flex items-start justify-between">
            <p>{answer.ans}</p>

            <div>
              <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                <Pencil className="h-4 w-4" />
              </Button>
              <Button variant="ghost" size="icon" onClick={onDeleteAnswer}>
                <Trash2 className="h-4 w-4" />
              </Button>
            </div>
          </div>
          <div className="text-sm text-gray-500">
            <span>{answer.contributor}</span>
            <span> • </span>
            <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default AnswerItem
```

Now you'll create the page that manages and displays questions and answers, handles form submissions, and interacts with the user. Note that the code below contains function placeholders that will be implemented later in this article to interact with the database.

Create the `app/qa/page.tsx` file and paste the following code into the file:

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

import { useState, useEffect } from 'react'
import QuestionForm from '../../components/QuestionForm'
import QuestionItem from '@/components/QuestionItem'
import Header from '../../components/Header'

export default function QAPage() {
  const [questions, setQuestions] = useState<Question[]>([])

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

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const addQuestion = async (question: string) => {}
  const editQuestion = async (id: number, newText: string) => {}
  const deleteQuestion = async (id: number) => {}
  const addAnswer = async (questionId: number, answer: string) => {}
  const editAnswer = async (answerId: number, newText: string) => {}
  const deleteAnswer = async (answerId: number) => {}

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <QuestionForm onSubmit={addQuestion} />
        {Array.isArray(questions) && (
          <div className="space-y-4">
            {questions.map((question) => (
              <QuestionItem
                key={question.id}
                question={question}
                onEditQuestion={editQuestion}
                onDeleteQuestion={deleteQuestion}
                onAddAnswer={addAnswer}
                onEditAnswer={editAnswer}
                onDeleteAnswer={deleteAnswer}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}
```

### Creating the admin area

With the home page and Q\&A page created, it's time to create the admin area. The admin area will be used to manage questions and answers.

Start by creating the `src/components/QuestionCard.tsx` which is used by the admin page for approving and managing questions and answers. Paste the following into that file:

```tsx {{ filename: 'src/components/QuestionCard.tsx' }}
'use client'

import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { CheckCircle, XCircle } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { formatDate } from '@/lib/utils'

interface Props {
  question: Question
  onQuestionApproved: (id: number) => void
  onQuestionDisapproved: (id: number) => void
  onAnswerApproved: (answerId: number) => void
  onAnswerDisapproved: (answerId: number) => void
}

export default function QuestionCard({
  question,
  onQuestionApproved,
  onQuestionDisapproved,
  onAnswerApproved,
  onAnswerDisapproved,
}: Props) {
  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex flex-col">
          <div className="mb-2 flex items-start justify-between">
            <div className="flex items-center space-x-2">
              <span className="text-xl">{question.quiz}</span>
              <ApprovalBadge approved={question.approved} />
            </div>

            <div>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => question.id !== null && onQuestionApproved(question.id)}
              >
                <CheckCircle className="h-4 w-4 text-green-500" />
              </Button>
              <Button
                variant="ghost"
                size="icon"
                onClick={() => question.id !== null && onQuestionDisapproved(question.id)}
              >
                <XCircle className="h-4 w-4 text-red-500" />
              </Button>
            </div>
          </div>
          <div className="text-sm text-gray-500">
            <span>{question.contributor}</span>
            <span> • </span>
            <span>{question.timestamp && formatDate(question.timestamp)}</span>
          </div>
        </CardTitle>
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.length > 0 ? (
          <ul className="space-y-4">
            {question.answers.map((answer) => (
              <li key={answer.id} className="flex flex-col">
                <div className="mb-2 flex items-start justify-between">
                  <div className="flex items-center space-x-2">
                    <span>{answer.ans}</span>
                    <ApprovalBadge approved={answer.approved ?? null} />
                  </div>
                  <div>
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => {
                        if (question.id !== null && answer.id !== null) {
                          onAnswerApproved(answer.id)
                        }
                      }}
                    >
                      <CheckCircle className="h-4 w-4 text-green-500" />
                    </Button>
                    <Button
                      variant="ghost"
                      size="icon"
                      onClick={() => {
                        if (question.id !== null && answer.id !== null) {
                          onAnswerDisapproved(answer.id)
                        }
                      }}
                    >
                      <XCircle className="h-4 w-4 text-red-500" />
                    </Button>
                  </div>
                </div>
                <div className="text-sm text-gray-500">
                  <span>{answer.contributor}</span>
                  <span> • </span>
                  <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
                </div>
              </li>
            ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>
    </Card>
  )
}

function ApprovalBadge({ approved }: { approved: boolean | null }) {
  if (approved === true) {
    return (
      <Badge variant="outline" className="border-green-300 bg-green-100 text-green-800">
        Approved
      </Badge>
    )
  } else if (approved === false) {
    return (
      <Badge variant="outline" className="border-red-300 bg-red-100 text-red-800">
        Disapproved
      </Badge>
    )
  } else {
    return (
      <Badge variant="outline" className="border-yellow-300 bg-yellow-100 text-yellow-800">
        Pending
      </Badge>
    )
  }
}
```

Create the `app/admin/page.tsx` file, used to render the area for admins and moderators, and paste the following code into the file:

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

import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import Header from '@/components/Header'
import QuestionCard from '@/components/QuestionCard'

export default function AdminPage() {
  const [questions, setQuestions] = useState<Question[]>([])

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

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const onQuestionApproved = async (id: number) => {}
  const onQuestionDisapproved = async (id: number) => {}
  const onAnswerApproved = async (answerId: number) => {}
  const onAnswerDisapproved = async (answerId: number) => {}

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <h1 className="mb-6 text-3xl font-bold">Admin Dashboard</h1>
        <div className="mb-4 flex justify-end">
          <Button>
            <Link href="/admin/set-user-roles">Set Roles</Link>
          </Button>
        </div>
        <div className="space-y-4">
          {questions.map((question) => (
            <QuestionCard
              key={question.id}
              question={question}
              onQuestionApproved={onQuestionApproved}
              onQuestionDisapproved={onQuestionDisapproved}
              onAnswerApproved={onAnswerApproved}
              onAnswerDisapproved={onAnswerDisapproved}
            />
          ))}
        </div>
      </main>
    </div>
  )
}
```

### Testing the application

With all of our pages created, you can test the application by running the following command in the terminal, which will start the dev server and allow you to view the application in your browser:

```bash
npm run dev
```

By default it runs on `localhost:3000`, but may use a different port if `3000` is already in use. Use the provided URL to access the application.

## Adding Clerk for authentication and authorization

Now let's add authentication to the application using Clerk. In your browser, go to [the Clerk dashboard](https://dashboard.clerk.com/) to create an account if you don't already have one, which will automatically walk you through setting up your first application with Clerk. If you already have an account, sign in and create a new application, which will also guide you through setting up Clerk.

Follow steps 1-3 shown in the onboarding guide to install and configure Clerk in your Next.js application. Return to this page once you are finished to continue the tutorial.

### Setting up Clerk in the application

At this point, the Clerk SDK should be installed, and the middleware should be defined per the quickstart instructions. Next, you'll need to wrap the application with the `<ClerkProvider>` which will allow Clerk to protect pages that require authentication.

Update the `app/layout.tsx` file to wrap the application with the `<ClerkProvider>`:

```tsx {{ filename: 'app/layout.tsx', ins: [4, 27, 33], del: [] }}
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import { ClerkProvider } from '@clerk/nextjs'

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

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

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

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
          {children}
        </body>
      </html>
    </ClerkProvider>
  )
}
```

Add the following environment variables to your `.env.local` file, which tell Clerk where to redirect users when they sign up or sign in:

```env {{ filename: '.env.local' }}
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/
```

Next, update the `header` component to include a `UserButton` if the user is signed in, or a `SignInButton` if the user is not signed in:

```tsx {{ filename: 'src/app/components/header.tsx', ins: [3, [34, 43]], del: [] }}
'use client'

import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import Link from 'next/link'
import { Button } from '@/components/ui/button'

const Header = () => {
  return (
    <header className="border-b bg-white">
      <div className="container mx-auto px-4">
        <div className="flex h-16 items-center justify-between">
          <Link href="/" className="text-xl font-bold">
            Q&A platform
          </Link>
          <nav>
            <ul className="flex space-x-4">
              <li>
                <Link href="/">
                  <Button variant="ghost">Home</Button>
                </Link>
              </li>
              <li>
                <Link href="/qa">
                  <Button variant="ghost">Q&A</Button>
                </Link>
              </li>

              <li>
                <Link href="/admin">
                  <Button variant="ghost">Admin</Button>
                </Link>
              </li>

              <SignedIn>
                <li className="flex items-center">
                  <UserButton />
                </li>
              </SignedIn>
              <SignedOut>
                <li className="flex items-center rounded bg-black px-2 font-bold text-white">
                  <SignInButton mode="modal" />
                </li>
              </SignedOut>
            </ul>
          </nav>
        </div>
      </div>
    </header>
  )
}

export default Header
```

### Test it out

If your application is no longer running, start it up again with `npm run dev` and use the updated header to log into the application. This will let you create a user account and redirect you back to the home page.

Once logged in, notice how the header includes the `UserButton` instead of the `SignInButton`.

## Configuring RBAC with Clerk

Now let's add RBAC to the application using Clerk metadata. The role for a specific user will be set in the Clerk metadata, which is arbitrary data that is stored alongside a user that can be accessed and modified through the Clerk API, as well as directly in the Dashboard.

### Set a role for the user

Log into the Clerk dashboard and navigate to the **Users** page and select your user account. Scroll down to the *User metadata* section and select **Edit** next to the *Public* option.

Add the following JSON and select Save to manually add the admin role to your own user account in order for it to have all the system permissions. Later in the tutorial, you will add a basic admin tool to change a user's role.

```json
{
  "role": "admin"
}
```

### Include the user role with the Clerk metadata

Next, you'll need to update the token created by Clerk to include the metadata when it's created. This will allow you to check the role of the user without having to make an additional API call.

In the Clerk Dashboard, navigate to the **Sessions** page. Under the *Customize session token* section, select **Edit**. In the modal that opens, enter the following JSON and select **Save**.

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

### Declare the role types for the Metadata

Go back to the project and create a global type file to add type definitions for the metadata. Create the `types/global.d.ts` file and paste the following code into the file:

```ts {{ filename: 'types/global.d.ts' }}
export {}

declare global {
  interface CustomJwtSessionClaims {
    metadata: {
      role?: Roles
    }
  }
}
```

### Updating your middleware

The middleware is used to check each request it comes in and apply authentication logic. Let's update the middleware to check the role of the user and redirect them to the appropriate page.

Update `src/middleware.ts` as follows:

```ts {{ filename: 'src/middleware.ts', ins: [4, 5, [8, 17]], del: [7] }}
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

// The route matcher defines routes that should be protected
const isAdminRoute = createRouteMatcher(['/admin(.*)'])

export default clerkMiddleware()
export default clerkMiddleware(async (auth, req) => {
  // Fetch the user's role from the session claims
  const userRole = (await auth()).sessionClaims?.metadata?.role

  // Protect all routes starting with `/admin`
  if (isAdminRoute(req) && !(userRole === 'admin' || userRole === 'moderator')) {
    const url = new URL('/', req.url)
    return NextResponse.redirect(url)
  }
})

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

### Setting user roles from the application

Now we can define a new page in the application that will let admins set user roles. We'll start by creating the server actions that will be used to set the user role by passing in the role name.

Create the `src/app/admin/set-user-roles/actions.ts` file and paste the following code into the file:

```ts {{ filename: 'src/app/admin/set-user-roles/actions.ts' }}
'use server'

import { clerkClient } from '@clerk/nextjs/server'
import { checkRole } from './utils'

export async function setRole(formData: FormData): Promise<void> {
  const client = await clerkClient()

  try {
    const res = await client.users.updateUser(formData.get('id') as string, {
      publicMetadata: { role: formData.get('role') },
    })
    console.log({ message: res.publicMetadata })
  } catch (err) {
    throw new Error(err instanceof Error ? err.message : String(err))
  }
}

export async function removeRole(formData: FormData): Promise<void> {
  const client = await clerkClient()

  try {
    const res = await client.users.updateUser(formData.get('id') as string, {
      publicMetadata: { role: null },
    })
    console.log({ message: res.publicMetadata })
  } catch (err) {
    throw new Error(err instanceof Error ? err.message : String(err))
  }
}
```

Finally, we'll create a page that allows admins to search through users using the Clerk Backend API and the above server actions to set their role.

Create a file called `src/app/admin/set-user-roles/page.tsx` and paste in the following code to populate the page:

```tsx {{ filename: 'src/app/admin/set-user-roles/page.tsx' }}
// import { SearchUsers } from "./SearchUsers";
import { clerkClient } from '@clerk/nextjs/server'
import { removeRole, setRole } from './actions'
import Header from '@/components/Header'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

export default async function AdminDashboard(params: {
  searchParams: Promise<{ search?: string }>
}) {
  const query = (await params.searchParams).search

  const client = await clerkClient()
  const users = query ? (await client.users.getUserList({ query })).data : []

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <form className="mb-6">
          <div className="flex flex-col gap-2">
            <label htmlFor="search">Search for users</label>
            <div className="flex gap-2">
              <Input id="search" name="search" type="text" className="flex-grow" />
              <Button type="submit">Submit</Button>
            </div>
          </div>
        </form>
        {users.map((user) => (
          <div key={user.id} className="flex min-h-screen flex-col">
            <div className="space-y-4 rounded-md bg-white p-4 shadow-md">
              <div className="text-lg font-semibold text-gray-800">
                {user.firstName} {user.lastName}
              </div>

              <div className="text-sm text-gray-600">
                {
                  user.emailAddresses.find((email) => email.id === user.primaryEmailAddressId)
                    ?.emailAddress
                }
              </div>

              <div className="text-sm font-medium text-blue-600">
                Role: {user.publicMetadata.role as string}
              </div>
              <div className="mt-2 flex space-x-4">
                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="admin" name="role" />
                  <Button type="submit">Make Admin</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="moderator" name="role" />
                  <Button type="submit">Make Moderator</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="contributor" name="role" />
                  <Button type="submit">Make Contributor</Button>
                </form>

                <form action={setRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <input type="hidden" value="viewer" name="role" />
                  <Button type="submit">Make Viewer</Button>
                </form>

                <form action={removeRole} className="mt-2">
                  <input type="hidden" value={user.id} name="id" />
                  <Button
                    type="submit"
                    className="rounded-md bg-red-600 px-4 py-2 text-white transition hover:bg-red-700"
                  >
                    Remove Role
                  </Button>
                </form>
              </div>
            </div>
          </div>
        ))}
      </main>
    </div>
  )
}
```

Add three more users to the Q\&A platform, then go to Admin page and click the **Set Roles** button. Search for the users you added and set their roles by clicking either the **Make Admin**, **Make Moderator**, **Make Contributor**, or **Make Viewer** button.

## Integrate with Postgres using Neon

In this section, you will learn how to integrate Neon Postgres with Clerk in the Q\&A platform, using `drizzle-orm` and `drizzle-kit` to interact with the database.

### Creating the database

Start by creating a new Neon database. Open your browser and go to neon.tech. Create an account if you don't already have one, then create a new database. Once the database is created, you'll be presented with a Quickstart screen. Select the **Copy snippet** button to copy it to your clipboard:

![The Neon Quickstart screen](./neon-connection-string.png)

Paste it into your `.env.local` file as `DATABASE_URL` like so:

```env {{ filename: '.env.local' }}
DATABASE_URL=postgresql://neondb_owner:***************@ep-black-boat-a8ryq543-pooler.eastus2.azure.neon.tech/neondb?sslmode=require
```

### Install the dependencies

Now you'll need to install the following dependencies:

- `drizzle-orm` - The ORM that the application will use to interact with the database.
- `drizzle-kit` - The tool that will generate migrations and interact with the database.
- `@neondatabase/serverless` - The driver that will be used to connect to the database.

Run the following command to install the dependencies:

```bash
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit 
```

### Setting up the database schema

Next you'll create the schema file which `drizzle-orm` will use to interact with the database, while `drizzle-kit` will be used to apply schema changes to the database.

Create a new file called `src/db/schema.ts` and paste in the following code:

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

// Questions table
export const questions = pgTable('questions', {
  id: serial('id').primaryKey(),
  quiz: text('quiz').notNull(),
  approved: boolean('approved'),
  contributor: text('contributor').notNull(),
  contributorId: text('contributor_id').notNull(),
  timestamp: timestamp('timestamp', { withTimezone: true }).defaultNow(),
})

// Answers table
export const answers = pgTable('answers', {
  id: serial('id').primaryKey(),
  ans: text('ans').notNull(),
  approved: boolean('approved'),
  contributor: text('contributor').notNull(),
  contributorId: text('contributor_id').notNull(),
  questionId: integer('question_id')
    .notNull()
    .references(() => questions.id),
  timestamp: timestamp('timestamp', { withTimezone: true }).defaultNow(),
})

// Define relationships using Drizzle's relations function
export const questionsRelations = relations(questions, ({ many }) => ({
  answers: many(answers),
}))

export const answersRelations = relations(answers, ({ one }) => ({
  question: one(questions, {
    fields: [answers.questionId],
    references: [questions.id],
  }),
}))
```

Create the `src/db/index.ts` file and paste in the following code, which is used by the application to establish a connection to the database:

```ts {{ filename: 'src/db/index.ts' }}
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import { questions, answers, questionsRelations, answersRelations } from './schema'

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be a Neon postgres connection string')
}
const sql = neon(process.env.DATABASE_URL!)

export const db = drizzle(sql, {
  schema: { questions, answers, questionsRelations, answersRelations },
})
```

In the root of the project, create the `drizzle.config.ts` used by `drizzle-kit` to manage the database schema:

```ts {{ filename: 'drizzle.config.ts' }}
import { defineConfig } from 'drizzle-kit'
import { loadEnvConfig } from '@next/env'

loadEnvConfig(process.cwd())

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL must be a Neon postgres connection string')
}

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

Finally, run the following command from your terminal to push the schema to the Neon database:

```bash
npx drizzle-kit push
```

If you go to the tables section in your Neon dashboard, you should see that two tables named `questions` and `answers` were created.

### Defining database interactions

Now that the database schema is set up, you can start defining database interactions. We're going to start with the database calls used by the main Q\&A section of the app.

Create `src/app/qa/actions.ts` and paste in the following:

```ts {{ filename: 'src/app/qa/actions.ts' }}
'use server'
import { db } from '@/db/index'
import { questions, answers } from '@/db/schema'
import { and, desc, eq } from 'drizzle-orm'
import { currentUser } from '@clerk/nextjs/server'

// Fetches all questions, available to authenticated and anonymous users
export async function getAllQuestions(): Promise<Question[]> {
  const data = await db
    .select()
    .from(questions)
    .where(eq(questions.approved, true))
    .orderBy(desc(questions.timestamp))
  const res: Question[] = data.map((question) => ({
    id: question.id,
    quiz: question.quiz,
    approved: question.approved,
    contributor: question.contributor,
    contributorId: question.contributorId,
    timestamp: question.timestamp?.toISOString(),
  }))
  for (const question of res) {
    const answerData = await db
      .select()
      .from(answers)
      .where(and(eq(answers.questionId, question.id as number), eq(answers.approved, true)))
      .orderBy(desc(answers.timestamp))
    question.answers = answerData.map((answer) => ({
      id: answer.id,
      ans: answer.ans,
      approved: answer.approved,
      contributor: answer.contributor,
      contributorId: answer.contributorId,
      questionId: answer.questionId,
      timestamp: answer.timestamp?.toISOString(),
    }))
  }
  return res
}

// Creates a new question, available only to authenticated users
export const createQuestion = async (quiz: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  await db.insert(questions).values({
    quiz: quiz,
    contributor: user.fullName as string,
    contributorId: user.id,
  })
}

// Creates a new answer, available only to authenticated users
export const createAnswer = async (answer: string, questionId: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  await db.insert(answers).values({
    ans: answer,
    contributor: user.fullName as string,
    contributorId: user.id,
    questionId: questionId,
  })
}

// Deletes a question, available only to the question's contributor
export const deleteQuestion = async (id: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    const result = await db
      .delete(questions)
      .where(and(eq(questions.id, id), eq(questions.contributorId, user.id)))
    return result
  } catch (error) {
    console.error('Error deleting question:', error)
    throw new Error('Failed to delete question')
  }
}

// Deletes an answer, available only to the answer's contributor
export const deleteAnswer = async (id: number) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db.delete(answers).where(and(eq(answers.id, id), eq(answers.contributorId, user.id)))
  } catch (error) {
    console.error('Error deleting answer:', error)
    throw new Error('Failed to delete answer')
  }
}

// Updates a question, available only to the question's contributor
export const updateQuestion = async (id: number, newText: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db
      .update(questions)
      .set({ quiz: newText })
      .where(and(eq(questions.contributorId, user.id), eq(questions.id, id)))
  } catch (error) {
    console.error('Error updating question:', error)
    throw new Error('Failed to update question')
  }
}

// Updates an answer, available only to the answer's contributor
export const updateAnswer = async (id: number, newText: string) => {
  const user = await currentUser()
  if (!user) {
    throw new Error('Unauthorized')
  }

  try {
    await db
      .update(answers)
      .set({ ans: newText })
      .where(and(eq(answers.contributorId, user.id), eq(answers.id, id)))
  } catch (error) {
    console.error('Error updating answer:', error)
    throw new Error('Failed to update answer')
  }
}
```

Now we can wire up the placeholder functions in `src/app/qa/actions.ts` with the new database interactions we just created.

Update the `src/app/qa/page.tsx` file like so:

```tsx {{ filename: 'src/app/qa/page.tsx', ins: [7, [25, 58]], del: [[16, 23]] }}
'use client'

import { useState, useEffect } from 'react'
import QuestionForm from '../../components/QuestionForm'
import QuestionItem from '@/components/QuestionItem'
import Header from '../../components/Header'
import * as actions from '../../app/qa/actions'

export default function QAPage() {
  const [questions, setQuestions] = useState<Question[]>([])

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

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const addQuestion = async (question: string) => {}
  const editQuestion = async (id: number, newText: string) => {}
  const deleteQuestion = async (id: number) => {}
  const addAnswer = async (questionId: number, answer: string) => {}
  const editAnswer = async (questionId: number, answerId: number, newText: string) => {}
  const deleteAnswer = async (questionId: number, answerId: number) => {}

  const fetchQuestions = async () => {
    const questions = await actions.getAllQuestions()
    setQuestions(questions)
  }

  const addQuestion = async (quiz: string) => {
    await actions.createQuestion(quiz)
    fetchQuestions()
  }

  const editQuestion = async (id: number, newText: string) => {
    await actions.updateQuestion(id, newText)
    fetchQuestions()
  }

  const deleteQuestion = async (id: number) => {
    await actions.deleteQuestion(id)
    fetchQuestions()
  }

  const addAnswer = async (questionId: number, answer: string) => {
    await actions.createAnswer(answer, questionId)
    fetchQuestions()
  }

  const editAnswer = async (answerId: number, newText: string) => {
    await actions.updateAnswer(answerId, newText)
    fetchQuestions()
  }

  const deleteAnswer = async (answerId: number) => {
    await actions.deleteAnswer(answerId)
    fetchQuestions()
  }

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <QuestionForm onSubmit={addQuestion} />
        {Array.isArray(questions) && (
          <div className="space-y-4">
            {questions.map((question) => (
              <QuestionItem
                key={question.id}
                question={question}
                onEditQuestion={editQuestion}
                onDeleteQuestion={deleteQuestion}
                onAddAnswer={addAnswer}
                onEditAnswer={editAnswer}
                onDeleteAnswer={deleteAnswer}
              />
            ))}
          </div>
        )}
      </main>
    </div>
  )
}
```

Now the server actions will prevent users from editing questions or answers that do not belong to them, but we can create a better user experience by making sure only the person who posted the question or answer can edit or delete it. The `useUser` hook from Clerk can be used to get the current user's information.

Update the `QuestionItem` component like so:

```tsx {{ filename: 'src/components/QuestionItem.tsx', ins: [9, 28, 94, 107], del: [] }}
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'
import { Pencil, Trash2 } from 'lucide-react'
import { Separator } from '@/components/ui/separator'
import AnswerItem from './AnswerItem'
import { formatDate } from '@/lib/utils'
import { useUser } from '@clerk/nextjs'

interface Props {
  question: Question
  onEditQuestion: (id: number, newText: string) => void
  onDeleteQuestion: (id: number) => void
  onAddAnswer: (questionId: number, answerText: string) => void
  onEditAnswer: (answerId: number, newText: string) => void
  onDeleteAnswer: (answerId: number) => void
}

export default function QuestionItem({
  question,
  onEditQuestion,
  onDeleteQuestion,
  onAddAnswer,
  onEditAnswer,
  onDeleteAnswer,
}: Props) {
  const { user } = useUser()
  const [answer, setAnswer] = useState('')
  const [isEditing, setIsEditing] = useState(false)
  const [editedQuestion, setEditedQuestion] = useState(question.quiz)
  const [showSubmitText, setShowSubmitText] = useState(false)

  useEffect(() => {
    if (showSubmitText) {
      setTimeout(() => {
        setShowSubmitText(false)
      }, 7000)
    }
  }, [showSubmitText])

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (answer.trim()) {
      if (question.id !== null) {
        onAddAnswer(question.id, answer)
        setShowSubmitText(true)
      }
      setAnswer('')
    }
  }

  const handleQuestionEdit = () => {
    if (editedQuestion.trim() && editedQuestion !== question.quiz) {
      if (question.id !== null) {
        onEditQuestion(question.id, editedQuestion)
      }
      setIsEditing(false)
    }
  }

  const handleAnswerEdit = async (answerId: number | null, newText: string) => {
    if (answerId !== null && question.id !== null) {
      await onEditAnswer(answerId, newText)
    }
  }

  const handleAnswerDelete = async (answerId: number | null) => {
    if (answerId !== null && question.id !== null) {
      await onDeleteAnswer(answerId)
    }
  }

  return (
    <Card>
      <CardHeader>
        {isEditing ? (
          <div className="flex gap-2">
            <Input
              value={editedQuestion}
              onChange={(e) => setEditedQuestion(e.target.value)}
              className="flex-grow"
            />
            <Button onClick={handleQuestionEdit}>Save</Button>
            <Button variant="outline" onClick={() => setIsEditing(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <div>
            <div className="mb-2 flex items-center justify-between">
              <CardTitle>{question.quiz}</CardTitle>

              {user?.id === question.contributorId && (
                <div>
                  <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                    <Pencil className="h-4 w-4" />
                  </Button>
                  <Button
                    variant="ghost"
                    size="icon"
                    onClick={() => question.id !== null && onDeleteQuestion(question.id)}
                  >
                    <Trash2 className="h-4 w-4" />
                  </Button>
                </div>
              )}
            </div>
            <div className="text-sm text-gray-500">
              <span>{question.contributor}</span>
              <span> • </span>
              <span>{question.timestamp && formatDate(question.timestamp)}</span>
            </div>
          </div>
        )}
      </CardHeader>
      <CardContent>
        <h3 className="mb-2 font-semibold">Answers:</h3>
        {question.answers && question.answers.filter((a) => a.approved !== false).length > 0 ? (
          <ul className="space-y-4">
            {question.answers
              .filter((a) => a.approved !== false)
              .map((answer, index, filteredAnswers) => (
                <li key={answer.id}>
                  <AnswerItem
                    answer={answer}
                    onEditAnswer={(newText) => handleAnswerEdit(answer.id, newText)}
                    onDeleteAnswer={() => handleAnswerDelete(answer.id)}
                  />
                  {index < filteredAnswers.length - 1 && <Separator className="my-2" />}
                </li>
              ))}
          </ul>
        ) : (
          <p className="text-gray-500">No answers yet.</p>
        )}
      </CardContent>

      <CardFooter>
        <form onSubmit={handleSubmit} className="w-full">
          <div className="flex gap-2">
            <div className="flex-grow">
              <Input
                type="text"
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Add an answer..."
              />

              <div className="h-4 text-sm text-green-500 transition-all">
                {showSubmitText ? 'Your answer has been submitted for review.' : ''}
              </div>
            </div>
            <Button type="submit">Answer</Button>
          </div>
        </form>
      </CardFooter>
    </Card>
  )
}
```

And the `AnswerItem` component:

```tsx {{ filename: 'src/components/AnswerItem.tsx', ins: [6, 15, 44, 53], del: [] }}
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Pencil, Trash2 } from 'lucide-react'
import { formatDate } from '@/lib/utils'
import { useUser } from '@clerk/nextjs'

type Props = {
  answer: Answer
  onEditAnswer: (newText: string) => void
  onDeleteAnswer: () => void
}

function AnswerItem({ answer, onEditAnswer, onDeleteAnswer }: Props) {
  const { user } = useUser()
  const [isEditing, setIsEditing] = useState(false)
  const [editedAnswer, setEditedAnswer] = useState(answer.ans)

  const handleEdit = () => {
    if (editedAnswer.trim() && editedAnswer !== answer.ans) {
      onEditAnswer(editedAnswer)
      setIsEditing(false)
    }
  }

  return (
    <div>
      {isEditing ? (
        <div className="flex w-full gap-2">
          <Input
            value={editedAnswer}
            onChange={(e) => setEditedAnswer(e.target.value)}
            className="flex-grow"
          />
          <Button onClick={handleEdit}>Save</Button>
          <Button variant="outline" onClick={() => setIsEditing(false)}>
            Cancel
          </Button>
        </div>
      ) : (
        <div className="space-y-2">
          <div className="flex items-start justify-between">
            <p>{answer.ans}</p>
            {user?.id === answer.contributorId && (
              <div>
                <Button variant="ghost" size="icon" onClick={() => setIsEditing(true)}>
                  <Pencil className="h-4 w-4" />
                </Button>
                <Button variant="ghost" size="icon" onClick={onDeleteAnswer}>
                  <Trash2 className="h-4 w-4" />
                </Button>
              </div>
            )}
          </div>
          <div className="text-sm text-gray-500">
            <span>{answer.contributor}</span>
            <span> • </span>
            <span>{answer.timestamp && formatDate(answer.timestamp)}</span>
          </div>
        </div>
      )}
    </div>
  )
}

export default AnswerItem
```

Next let's create a set of server actions used by the admin area to manage questions and answers. Notice in the following code we dont need to check the user's role, because we are using the Clerk middleware to protect this route.

Create the `src/app/admin/actions.ts` file and add the following content:

```tsx {{ filename: 'src/app/admin/actions.ts' }}
'use server'
import { db } from '@/db/index'
import { questions, answers } from '@/db/schema'
import { eq, desc } from 'drizzle-orm'

export const getAllQuestionsWithAnswers = async () => {
  const questionsData = await db.select().from(questions).orderBy(desc(questions.timestamp))
  const res: Question[] = questionsData.map((question) => ({
    id: question.id,
    quiz: question.quiz,
    approved: question.approved,
    contributor: question.contributor,
    contributorId: question.contributorId,
    timestamp: question.timestamp?.toISOString(),
  }))
  for (const question of res) {
    const answerData = await db
      .select()
      .from(answers)
      .where(eq(answers.questionId, question.id as number))
      .orderBy(desc(answers.timestamp))
    if (!answerData) continue
    question.answers = answerData.map((answer) => ({
      id: answer.id,
      ans: answer.ans,
      approved: answer.approved,
      contributor: answer.contributor,
      contributorId: answer.contributorId,
      questionId: answer.questionId,
      timestamp: answer.timestamp?.toISOString(),
    }))
  }
  return res
}

export const approveQuestion = async (id: number) => {
  try {
    await db.update(questions).set({ approved: true }).where(eq(questions.id, id))
  } catch (error) {
    console.error('Error approving question:', error)
    throw new Error('Failed to approve question')
  }
}

export const disapproveQuestion = async (id: number) => {
  try {
    await db.update(questions).set({ approved: false }).where(eq(questions.id, id))
  } catch (error) {
    console.error('Error disapproving question:', error)
    throw new Error('Failed to disapprove question')
  }
}

export const approveAnswer = async (id: number) => {
  try {
    await db.update(answers).set({ approved: true }).where(eq(answers.id, id))
  } catch (error) {
    console.error('Error approving answer:', error)
    throw new Error('Failed to approve answer')
  }
}

export const disapproveAnswer = async (id: number) => {
  try {
    await db.update(answers).set({ approved: false }).where(eq(answers.id, id))
  } catch (error) {
    console.error('Error disapproving answer:', error)
    throw new Error('Failed to disapprove answer')
  }
}
```

Now let's do the same thing to the `admin` page as the `qa` page. Update the `src/app/admin/page.tsx` file like so:

```tsx {{ filename: 'app/admin/page.tsx', ins: [[8, 14], [30, 53]], del: [[23, 28]] }}
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import Header from '@/components/Header'
import QuestionCard from '@/components/QuestionCard'
import {
  approveQuestion,
  disapproveQuestion,
  getAllQuestionsWithAnswers,
  approveAnswer,
  disapproveAnswer,
} from './actions'

export default function AdminPage() {
  const [questions, setQuestions] = useState<Question[]>([])

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

  // These placeholders will be populated later in this guide
  const fetchQuestions = async () => {}
  const onQuestionApproved = async (id: number) => {}
  const onQuestionDisapproved = async (id: number) => {}
  const onAnswerApproved = async (answerId: number) => {}
  const onAnswerDisapproved = async (answerId: number) => {}

  const fetchQuestions = async () => {
    const questions = await getAllQuestionsWithAnswers()
    setQuestions(questions)
  }

  const onQuestionApproved = async (id: number) => {
    await approveQuestion(id)
    fetchQuestions()
  }

  const onQuestionDisapproved = async (id: number) => {
    await disapproveQuestion(id)
    fetchQuestions()
  }

  const onAnswerApproved = async (answerId: number) => {
    await approveAnswer(answerId)
    fetchQuestions()
  }

  const onAnswerDisapproved = async (answerId: number) => {
    await disapproveAnswer(answerId)
    fetchQuestions()
  }

  return (
    <div className="flex min-h-screen flex-col">
      <Header />
      <main className="container mx-auto flex-grow p-4">
        <h1 className="mb-6 text-3xl font-bold">Admin Dashboard</h1>
        <div className="mb-4 flex justify-end">
          <Button>
            <Link href="/admin/set-user-roles">Set Roles</Link>
          </Button>
        </div>
        <div className="space-y-4">
          {questions.map((question) => (
            <QuestionCard
              key={question.id}
              question={question}
              onQuestionApproved={onQuestionApproved}
              onQuestionDisapproved={onQuestionDisapproved}
              onAnswerApproved={onAnswerApproved}
              onAnswerDisapproved={onAnswerDisapproved}
            />
          ))}
        </div>
      </main>
    </div>
  )
}
```

## Test it out!

After completing all the steps throughout this guide, you can now start up the application once more to test out all the features!

Here are a couple of things to try:

- Create a new question as a user of each role.
- Try approving and disapproving questions and answers.
- Try editing and deleting questions and answers.
- Explore the data in the Neon database.

## Conclusion

In this tutorial, you have learned how to integrate Clerk for authentication, configure RBAC using metadata, and enforce role-based restrictions to ensure users only access features appropriate to their roles. Also, you learned how to integrate Neon Postgres database with Drizzle ORM for seamless data management and how to conditionally render UI based on user roles.

By following this tutorial, developers can build secure applications by implementing Role-Based Access Control (RBAC) with Clerk.

Here is the [source code](https://github.com/bmorrisondev/qa-app) (remember to give it a star ⭐).

---

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

---

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

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

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

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

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

### The OAuth happy path

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

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

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

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

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

### The Open Redirect vulnerability

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

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

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

### The Open Response Type vulnerability

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

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

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

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

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

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

### The impact

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

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

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

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

## Mitigating the Open Response Type vulnerability

The Open Response Type vulnerability relies on two behaviors:

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

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

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

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

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

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

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

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

### Mitigation 2: Eliminate unexpected fragments

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

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

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

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

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

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

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

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

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

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

---

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

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

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

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

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

---

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

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

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

Let's get started.

## Core Differences between React and Next.js

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

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

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

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

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

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

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

### React Routing capabilities

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

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

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

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

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

## Example: How to Implement Authentication in React

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

### Overview of client-side authentication

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

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

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

### Considerations for client-side authentication

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

**Security Concerns**

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

**State Management**

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

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

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

### Example of setting up authentication in React

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

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

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

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

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

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

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

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

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

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

#### Step 2: Define the Login Component

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

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

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

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

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

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

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

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

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

interface UserData {
  username: string
}

export interface Credentials {
  username: string
  password: string
}

interface LoginData {
  token: string
  user: UserData
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Here is what the server code looks like:

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

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

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

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

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

#### Step 5: Running the Development Environment

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

First, let's install the required dependencies:

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

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

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

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

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

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

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

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

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

#### Step 1: Install NextAuth.js

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

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

#### Step 2: Configure NextAuth.js

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

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

*Setting up a new GitHub App*

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

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

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

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

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

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

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

And here is how to protect pages:

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

#### Step 4: Setup the Login page

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Simplifying Authentication with Clerk

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

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

### Clerk with React

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

### Clerk with Next.js

**Step 1: Install Clerk**

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

**Step 2: Setup Clerk Secret Keys**

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

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

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

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

Then configure the `authMiddleware` to protect routes:

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

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

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

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

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

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

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

---

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

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

## Building with Clerk Webhooks

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

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

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

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

## See it in Action

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

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

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

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

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

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

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

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

## **Onward and Upward**

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