# Clerk Blog — Guides — Page 3

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

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

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

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

## What is least privilege access?

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

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

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

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

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

## Implementing user-specific OAuth scopes with Clerk

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```tsx
'use client'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### Initiate a reauthorization by Clerk

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

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

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

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

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

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

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

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

### Add `publicMetadata` to the session claims

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

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

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

### Automatically handle reauthorization if additional scopes are present

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

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

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

const LOCAL_STORAGE_KEY = 'bookmate_isScopeCheckComplete'

interface Props {
  children: React.ReactNode
}

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

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

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

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

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

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

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

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

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

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

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

  if (!isSignedIn) {
    return children
  }

  if (!isLoaded) {
    return null
  }

  return <>{children}</>
}

export default ReauthProvider
```

The provider simply wraps the children in the root layout:

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

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

## Why is reauthorization required on every login?

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

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

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

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

## Conclusion

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

---

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

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

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

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

## Single sign-on and access tokens

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

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

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

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

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

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

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

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

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

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

## Clerk helps with token management and data access

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

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

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

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

## Demo: BookMate

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

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

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

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

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

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

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

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

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

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

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

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

// Code removed for brevity.

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

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

## Conclusion

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

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

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

---

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

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

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

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

## What is SAML?

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

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

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

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

## Automated enrollment with verified domains

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

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

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

## SAML with verified domains in action

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

Before following along, make sure you have the following:

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

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

### Configuring SAML

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### Using verified domains within organizations

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

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

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

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

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

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

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

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

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

## Conclusion

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

---

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

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

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

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

## What is Liveblocks?

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

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

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

## How room security works

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

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

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

## The Liveblocks authorization route

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

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

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

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

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

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

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

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

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

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

## Configure `LiveblocksProvider` to use the authorization route

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

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

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

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

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

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

## See it in action

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

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

## Conclusion

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

---

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

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

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

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

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

## Prerequisites

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

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

## Securing API Endpoints

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

For Express applications, Clerk offers two middleware options:

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

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

## Create the API with Express

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

```bash
npm init -y
```

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

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

Next, we need to install the following packages:

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

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

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

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

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

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

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

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

const port = process.env.PORT || 3000

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

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

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

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

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

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

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

```bash
node server.js
```

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

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

## Testing the route with Postman

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

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

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

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

This is the expected response:

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

## Testing authentication with React

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

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

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

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

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

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

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

### Set up Clerk

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

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

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

export default App
```

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

### Accessing the API via React

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

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

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

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

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

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

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

export default App
```

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

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

### Conclusion

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

---

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

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

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

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

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

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

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

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

## Project setup

### Clone the Clerk Next.js quickstart

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

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

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

### Set up a Clerk project

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

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

Run your project with the following command:

```sh
npm run dev
```

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

### Set up a Supabase project

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

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

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

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

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

## Create a table and enable RLS on it

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

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

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

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

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

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

## Create ID-based RLS policies

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

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

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

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

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

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

## Get your Supabase JWT secret key

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

Get your Supabase JWT secret key:

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

## Create a Supabase JWT template

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

To create a JWT template for Supabase:

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

## Install the Supabase client library

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

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

## Set up your environment variables

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

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

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

## Fetch Supabase data in your code

Let's get to coding!

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

### Client-side rendering (CSR)

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

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

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

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

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

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

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

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

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

  // This `useEffect` will wait for the User object to be loaded before requesting
  // the tasks for the signed in user
  useEffect(() => {
    if (!user) return

    async function loadTasks() {
      setLoading(true)
      const { data, error } = await client.from('tasks').select()
      if (!error) setTasks(data)
      setLoading(false)
    }

    loadTasks()
  }, [user])

  // Add a task into the "tasks" database
  async function createTask(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    await client.from('tasks').insert({
      name,
    })
    window.location.reload()
  }

  // Update a task when its completed
  async function onCheckClicked(taskId: number, isDone: boolean) {
    await client
      .from('tasks')
      .update({
        is_done: isDone,
      })
      .eq('id', taskId)
    window.location.reload()
  }

  // Delete a task from the database
  async function deleteTask(taskId: number) {
    await client.from('tasks').delete().eq('id', taskId)
    window.location.reload()
  }

  return (
    <div>
      <h1>Tasks</h1>

      {loading && <p>Loading...</p>}

      {!loading &&
        tasks.length > 0 &&
        tasks.map((task: any) => (
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
            <input
              type="checkbox"
              checked={task.is_done}
              onChange={(e) => onCheckClicked(task.id, e.target.checked)}
            />
            <p>{task.name}</p>
            <button onClick={() => deleteTask(task.id)}>Delete</button>
          </div>
        ))}

      {!loading && tasks.length === 0 && <p>No tasks found</p>}

      <form onSubmit={createTask}>
        <input
          autoFocus
          type="text"
          name="name"
          placeholder="Enter new task"
          onChange={(e) => setName(e.target.value)}
          value={name}
        />
        <button type="submit">Add</button>
      </form>
    </div>
  )
}
```

## Server-side rendering (SSR)

The following example demonstrates how to fetch data from Supabase in a server-side rendered page. It requires creating multiple files as you will use [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) to handle adding, deleting, and updating tasks.

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

It is stored in a separate file so that it can be reused in multiple places, such as in both your `page.tsx` and your Server Action file.

Create the `src/app/ssr/client.ts` file and paste the following code into it:

```ts {{ filename: 'src/app/ssr/client.ts' }}
import { auth } from '@clerk/nextjs/server'
import { createClient } from '@supabase/supabase-js'

export function createClerkSupabaseClientSsr() {
  // The `useAuth()` hook is used to access the `getToken()` method
  const { getToken } = auth()

  return createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_KEY!,
    {
      global: {
        // Get the custom Supabase token from Clerk
        fetch: async (url, options = {}) => {
          const clerkToken = await getToken({
            template: 'supabase',
          })

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

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

Create the `src/app/ssr/page.tsx` file and paste the following code into it:

```tsx {{ filename: 'src/app/ssr/page.tsx' }}
import AddTaskForm from './AddTaskForm'
import { createClerkSupabaseClientSsr } from './client'
import TaskRow from './TaskRow'

export default async function Home() {
  // Use the custom Supabase client you created
  const client = createClerkSupabaseClientSsr()

  // Query the 'tasks' table to render the list of tasks
  const { data, error } = await client.from('tasks').select()
  if (error) {
    throw error
  }
  const tasks = data

  return (
    <div>
      <h1>Tasks</h1>

      <div>
        {tasks?.map((task: any) => (
          <TaskRow key={task.id} task={task} />
        ))}
      </div>

      <AddTaskForm />
    </div>
  )
}
```

Create a `src/app/ssr/actions.ts` file which will include [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) for adding, deleting, and updating tasks. Paste the following code into that file:

```ts {{ filename: 'src/app/ssr/actions.ts' }}
'use server'

import { createClerkSupabaseClientSsr } from './client'

const client = createClerkSupabaseClientSsr()

export async function addTask(name: string) {
  try {
    const response = await client.from('tasks').insert({
      name,
    })
    console.log('Task successfully added!', response)
  } catch (error: any) {
    console.error('Error adding task:', error.message)
    throw new Error('Failed to add task')
  }
}

export async function deleteTask(taskId: number) {
  try {
    const response = await client.from('tasks').delete().eq('id', taskId)
    console.log('Task successfully deleted!', response)
  } catch (error: any) {
    console.error('Error deleting task:', error.message)
    throw new Error('Failed to delete task')
  }
}

export async function setTaskState(taskId: number, isDone: boolean) {
  try {
    const response = await await client
      .from('tasks')
      .update({
        is_done: isDone,
      })
      .eq('id', taskId)
    console.log('Task successfully updated!', response)
  } catch (error: any) {
    console.error('Error updating task:', error.message)
    throw new Error('Failed to update task')
  }
}
```

Your form for adding tasks will use the `addTask()` Server Action that you created in the previous file. The form is in a separate file than your `page.tsx` file because it must be a client component. Create the `src/app/ssr/AddTaskForm.tsx` file and paste the following code into it:

```ts {{ filename: 'src/app/ssr/AddTaskForm.tsx' }}
'use client'
import React, { useState } from 'react'
import { addTask } from './actions'
import { useRouter } from 'next/navigation'

function AddTaskForm() {
  const [taskName, setTaskName] = useState('')
  const router = useRouter()

  async function onSubmit() {
    await addTask(taskName)
    setTaskName('')
    router.refresh()
  }

  return (
    <form action={onSubmit}>
      <input
        autoFocus
        type="text"
        name="name"
        placeholder="Enter new task"
        onChange={(e) => setTaskName(e.target.value)}
        value={taskName}
      />
      <button type="submit">Add</button>
    </form>
  )
}
export default AddTaskForm
```

Create the `src/app/ssr/TaskRow.tsx` file which will display the tasks in your list. It's a separate file from your `page.tsx` because it uses the `useRouter()` hook, so it must be a client component. Paste the following code into that file:

```ts {{ filename: 'src/app/ssr/TaskRow.tsx' }}
// This must be a separate file because useRouter() can't be used in server components
'use client'

import { useRouter } from 'next/navigation'
import { deleteTask, setTaskState } from './actions'

export default function TaskRow({ task }: { task: any }) {
  const router = useRouter()

  async function onCheckClicked(taskId: number, isDone: boolean) {
    // Update a task when its completed
    await setTaskState(taskId, isDone)
    router.refresh()
  }

  async function onDeleteClicked(taskId: number) {
    // Delete a task from the database
    await deleteTask(taskId)
    router.refresh()
  }

  return (
    <div key={task.id}>
      <p>{task.name}</p>
      <input
        type="checkbox"
        checked={task.is_done}
        onChange={(e) => onCheckClicked(task.id, e.target.checked)}
      />
      <button onClick={() => onDeleteClicked(task.id)}>Delete</button>
    </div>
  )
}
```

### Test your integration

Now it's time to test your code.

Run your project with the following command:

```sh
npm run dev
```

Sign in and test viewing, creating, updating, and deleting tasks. Sign out and sign in as a different user, and repeat.

If you have the same tasks across multiple accounts, double-check that RLS is enabled, or that the RLS policies were properly created. Check the table in the Supabase dashboard. You should see all the tasks between both users but with differing values in the `user_id` column.

---

# Comparing Clerk Webhooks vs Backend API
URL: https://clerk.com/blog/webhooks-v-bapi.md
Date: 2024-08-29
Category: Guides
Description: Learn when to use Clerk Webhooks or the Backend API to efficiently access user data and avoid unnecessary complexity.

This post compares Clerk Webhooks and the Backend API, focusing on their roles in querying user data specifically.

Whether you need to query information about a specific unauthenticated user, a list of users, or synchronize Clerk user data with another system, this comparison will help you choose the best option for your circumstances.

> \[!IMPORTANT]
> This guide specifically addresses situations where querying data about unauthenticated users is necessary. For guidance on reading data about the currently authenticated user from your server or client, please refer to [A guide to reading authenticated user data from Clerk](/blog/read-user-data-guide).

## What is the Backend API?

The [Clerk Backend API](/docs/reference/backend-api) is designed to query or update information from your Clerk application, such as user data.

You can query users one at a time, or, if you need a list of users, it's possible to effectively batch the query to improve efficiency.

Backend API requests are [limited](/docs/backend-requests/resources/rate-limits) to 100 per 10 seconds for your Clerk application. While the Backend API is straightforward to use, you should be judicious so as not to exceed your request allowance, otherwise, your application might stop working properly.

### How does the Backend API work?

The easiest way to interface with the Backend API is by using a Clerk backend SDK. The most popular option is the [JavaScript Backend SDK](https://clerk.com/docs/references/backend/overview) although there are [others](https://clerk.com/docs/references/overview).

To retrieve a specific user, call [`getUser`](/docs/references/backend/user/get-user) with their identifier. This awaitable function returns a [Clerk `User` object](/docs/references/backend/overview) populated with all the information Clerk stores about the user.

```ts {{ title: 'getUser Example' }}
const userId = 'user_123'

const response = await clerkClient.users.getUser(userId)

console.log(response)

// _User {
//   id: 'user_123',
//   passwordEnabled: true,
//   totpEnabled: false,
//   backupCodeEnabled: false,
//   twoFactorEnabled: false,
//   banned: false,
//   locked: false,
//   createdAt: 1708103362688,
//   updatedAt: 1708103362701,
//   imageUrl: 'https://img.clerk.com/eyJ...',
//   hasImage: false,
//   primaryEmailAddressId: 'idn_123',
//   primaryPhoneNumberId: null,
//   primaryWeb3WalletId: null,
//   lastSignInAt: null,
//   externalId: null,
//   username: null,
//   firstName: 'Test',
//   lastName: 'User',
//   publicMetadata: {},
//   privateMetadata: {},
//   unsafeMetadata: {},
//   emailAddresses: [
//     _EmailAddress {
//       id: 'idn_123',
//       emailAddress: 'testclerk123@gmail.com',
//       verification: [_Verification],
//       linkedTo: []
//     }
//   ],
//   phoneNumbers: [],
//   web3Wallets: [],
//   externalAccounts: [],
//   lastActiveAt: null,
//   createOrganizationEnabled: true
// }
```

When you need to fetch a list of users by their IDs, [`getUserList`](/docs/references/backend/user/get-user-list) effectively batches `getUser` queries into one. This is not only simpler than sending and handling a sequence of requests, it's more efficient as well. Because this operation initiates only one API call under the hood, your backend request allowance goes further.

```ts {{ title: 'getUserList Example' }}
const userId = ['user_123', 'user_456']

const response = await clerkClient.users.getUserList({ userId })

console.log(response)
// {
//   data: [
//     _User {
//       id: 'user_123',
//       passwordEnabled: false,
//       totpEnabled: false,
//       backupCodeEnabled: false,
//       twoFactorEnabled: false,
//       banned: false,
//       locked: false,
//       createdAt: 1707561967007,
//       updatedAt: 1707561967095,
//       imageUrl: 'https://img.clerk.com/eyJ...',
//       hasImage: true,
//       primaryEmailAddressId: 'idn_123',
//       primaryPhoneNumberId: null,
//       primaryWeb3WalletId: null,
//       lastSignInAt: 1707561967014,
//       externalId: null,
//       username: null,
//       firstName: 'First',
//       lastName: 'Test',
//       publicMetadata: {},
//       privateMetadata: {},
//       unsafeMetadata: {},
//       emailAddresses: [Array],
//       phoneNumbers: [],
//       web3Wallets: [],
//       externalAccounts: [Array],
//       lastActiveAt: 1707523200000,
//       createOrganizationEnabled: true
//     },
//     _User {
//       id: 'user_456',
//       passwordEnabled: false,
//       totpEnabled: false,
//       backupCodeEnabled: false,
//       twoFactorEnabled: false,
//       banned: false,
//       locked: false,
//       createdAt: 1707539597250,
//       updatedAt: 1707539597331,
//       imageUrl: 'https://img.clerk.com/eyJ...',
//       hasImage: true,
//       primaryEmailAddressId: 'idn_456',
//       primaryPhoneNumberId: null,
//       primaryWeb3WalletId: null,
//       lastSignInAt: 1707539597260,
//       externalId: null,
//       username: null,
//       firstName: 'Second',
//       lastName: 'Test',
//       publicMetadata: {},
//       privateMetadata: {},
//       unsafeMetadata: {},
//       emailAddresses: [Array],
//       phoneNumbers: [],
//       web3Wallets: [],
//       externalAccounts: [Array],
//       lastActiveAt: 1707523200000,
//       createOrganizationEnabled: true
//     }
//   ],
//   totalCount: 2
// }
```

While the focus of this post is querying user data, the Backend API also supports manipulating user data with `createUser`, `updateUser`, and specific helpers like `banUser`. Additionally, the Backend API supports similar operations for organizations, sessions, and more.

> \[!NOTE]
> Explore [everything the Backend API has to offer](/docs/reference/backend-api) in the reference documentation.

## What are Clerk Webhooks?

A [Webhook](/docs/integrations/webhooks/overview) is a way for Clerk to send data to another system when specific events happen, such as when a user is created or updated.

They're most commonly used to register events with external systems, send analytics events, and synchronize databases with Clerk.

Think of Webhooks like a notification that automatically sends information to a URL you specify, allowing different systems to react to Clerk to events when they happen.

Webhooks are more complex than calling the Backend API. You need to [verify that the request came from Clerk](/docs/integrations/webhooks/overview#protect-your-webhooks-from-abuse), manage occasional duplicate and out-of-order events, plus handle the asynchronous nature of Webhooks, which can complicate building synchronous workflows such as a [custom onboarding flow](/blog/add-onboarding-flow-for-your-application-with-clerk). Despite these challenges, Webhooks do not enforce any rate limits. Clerk will send as many Webhooks events as your server can handle.

### How do Clerk Webhooks work?

To enable Webhook events, register your Webhook endpoints from the dashboard. Once configured, Clerk will push event data to these endpoints as events occur in your Clerk application.

Example events:

- `user.created`
- `user.updated`
- `user.deleted`

> \[!TIP]
> Configure your Webhook endpoints to receive only the necessary event types for your integration. Listening for unnecessary or all events can strain your server, which we strongly advise against.

Clerk uses svix to ensure Webhooks are delivered reliably with [retries and other mechanisms](/docs/integrations/webhooks/overview#how-clerk-handles-delivery-issues).

![Flowchart illustrating the process of handling webhooks with Clerk. On the left, the Clerk logo is connected to an event that triggers a webhook, such as user creation. This event is linked to a Clerk-powered application on the right side via the Internet/Local network. Below, the Svix logo is connected to a tunnel (e.g., ngrok, localtunnel), which then routes to the webhook route in your application. The flow shows how events from Clerk trigger webhooks that are routed through Svix and tunnels to reach the application.](./webhooks_diagram.webp)

> \[!TIP]
> [Learn more about Webhooks](/docs/integrations/webhooks/overview) and [how to debug them](/docs/integrations/webhooks/debug-your-webhooks) in the documentation.

## Comparing Webhooks vs Backend API

Below is a table summarizing the differences between Webhooks and the Backend API.

Use it to understand your options at a glance or reference the next time you return to this page.

|                           | Backend API                                         | Webhooks                                                                                                                  |
| ------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Purpose                   | Directly query and manipulate user data from Clerk. | Used to send events from Clerk to another system, allowing for automated and instantaneous communication between systems. |
| Model                     | Request/response (manual fetching or polling).      | Event-driven.                                                                                                             |
| Response handling         | Initiate and `await` API responses.                 | Must handle events asynchronously.                                                                                        |
| Implementation complexity | Requires API calls, typically simpler to implement. | Requires setting up reliable and secure endpoints to receive Webhooks.                                                    |
| Scalability               | Dependent on rate limit.                            | Can handle high volumes of events when needed.                                                                            |
| Reliability               | Highly available.                                   | Dependent on the quality of your Webhook endpoint implementation.                                                         |
| Example                   | Example functions:  `getUser` and `getUserList`.    | Example events:  `user.created`, `user.updated`, and `user.deleted`.                                                      |

Key differences:

- **Complexity** Using Webhooks to keep your database in sync with Clerk can be more complex than calling the Backend API due to their asynchronous nature.
- **Rate limits** The Backend API imposes rate limits, which can impede your application's functionality if exceeded. You can often avoid these limits by using [alternative methods to read authenticated user data](/blog/read-user-data-guide) and by batching queries with `getUserList` when appropriate. Unlike the Backend API, Webhooks don't have rate limits. For this reason, synchronizing your database with Clerk using Webhooks is a viable alternative to the Backend API if you are likely to surpass the request allowance.
- **Always up-to-date** When you query records from the Backend API, you query the freshest data from the source of truth. This is in contrast to reading from a database synchronized with Clerk using Webhooks, which might not have received the latest events yet.
- **Synchronous vs asynchronous** With the Backend API, you control when to initiate an API call and how to handle the response, making it a predictable method. By comparison, Webhooks provide an asynchronous mechanism to receive and react to events. They are best used for things like sending notifications or updating systems where it isn't critical that the data is immediately up-to-date. For example, an email provider for weekly newsletters or an analytics platform for daily event rollups.
- **Adding or updating resources** The Backend API is suitable for both querying and manipulating data, unlike Webhooks which can only react to events.

## Guidance

### When the Backend API is better

- **Straightforward access** The Backend API is the most straightforward tool to programmatically query the most up-to-date data from Clerk. It can support a variety of use cases, provided you don't encroach on the rate limits.
- **Synchronous events** The Backend API's request-response pattern is well-suited to operations that require synchronization or that must act in a serialized fashion, such as an onboarding flow.

### When Webhooks are better

- **Integrations** Webhooks are the preferred method to update external systems with data from Clerk. Use them to notify another system like an email platform or CMS when a new Clerk user is created or if they update their email.
  Or send events to an analytics platform like Google Analytics or [Posthog](/blog/how-to-use-clerk-with-posthog-identify-in-nextjs).
  You can even use Webhooks to [create new user notifications in Discord or Slack](https://x.com/r_marked/status/1816551015543447575)!
- **Synchronizing Clerk with your database** The Backend API limits can be restrictive for certain applications. For instance, a B2C social media application where you frequently need to render user information with their posts. In such cases, you would likely hit the Backend API limit quickly. Webhooks provide a means to synchronize your database with Clerk. You can then query your database to the sidestep rate limit.

---

# Automate Neon schema changes with Drizzle and GitHub Actions
URL: https://clerk.com/blog/automate-neon-schema-changes-with-drizzle-and-github-actions.md
Date: 2024-08-22
Category: Guides
Description: Learn about schema migrations and how they can be applied to a Neon database with Drizzle and GitHub Actions

While it’s relatively straightforward to deploy updated code to different environments, applying the same techniques to a relational database can be disastrous.

Application code is stateless, meaning you can rebuild the code to recreate the application at any time. In fact, when deploying updated code to platforms like Vercel, the tooling will simply build and ship the latest version of the application, overwriting the previous one.

However, databases are stateful since the value is in the data contained within them. Applying the same deployment methodology to a database (removing and replacing it with a new version) would be detrimental to your users or business.

In this article, you’ll learn what schema migrations are, how they can be used to safely make database changes, and how to automate those changes to a Neon database using Drizzle and GitHub Actions.

## What is a schema migration?

Schema migrations are a way to apply changes to the schema of a database in a controlled way.

Typically each schema migration is a SQL script that is applied to the database to update the schema to the latest version. The migration files can stored in version control so the state of the schema can be tracked over time.

Schema migrations are used for updating the database schema as changes are required, but can also be used to create new environments. To recreate the database up to a specific point in time, the migrations can applied in the same order they were generated.

### Schema migrations and deployments

When building an application that uses a relational database, you’ll often have a different database per environment. Each database is isolated from one another so modifying the schema in one database environment does not affect the others.

Let's say you have two environments, a dev environment for building new features, and a production environment that your users are actively using.

When a feature is complete, merging your code from the `dev` branch to `main` is relatively straightforward. Once the newest version of the application is built, the artifacts of that build are deployed into the production environment, replacing the previous version.

The latest schema migrations are also applied to the database, updating the schema to support the latest version of the application. If done properly, all of the data within the database will remain intact.

## How to generate and apply schema migrations with Drizzle

Now that you understand what schema migrations are and how they are used when deploying new versions of your database, let’s explore a practical example using Drizzle.

Drizzle is a type-safe ORM for applications built with TypeScript. The team also built Drizzle Kit for managing the schema of the database. Drizzle Kit can be used to analyze your TypeScript models, generate schema migrations from the models, and apply them to the database.

### The demo application

The remainder of this article uses a sample to-do app to demonstrate how to use schema migrations to apply database changes. The application uses a Postgres database hosted by Neon, with a relatively simple schema.

I’ll be showing the process of adding a single column to the `tasks` table (shown below) so that each task can have a description stored with it.

![A database table showing columns named "name", "is\_done", "owner\_id", "created\_on", and "created\_by\_id"](./table1.png)

To simulate multiple environments, the Neon database has a `main` branch that contains the production data and a `dev` branch that is an isolated environment used for adding and testing new features.

> \[!NOTE]
> All of the code shown in this article is available [on GitHub](https://github.com/bmorrisondev/team-todo-demo).

### Updating the development environment

I’ll start by updating the code on the `dev` branch to support a “description” field of the `tasks` model:

```ts {{ filename: 'src/db/schema.ts', ins: [8] }}
export const tasks = pgTable('tasks', {
  id: serial('id').primaryKey(),
  name: text('name'),
  is_done: boolean('is_done'),
  owner_id: text('owner_id'),
  created_in: timestamp('created_on'),
  created_by_id: text('created_by_id'),
  description: text('description'),
})
```

The application has the following `drizzle.config.ts` which Drizzle Kit uses to locate the schema files in the project, generate and store migrations, and connect to the database:

```ts {{ filename: 'drizzle.config.ts' }}
import type { Config } from 'drizzle-kit'

export default {
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL as string,
  },
  verbose: true,
  strict: true,
} satisfies Config
```

Next, I’ll run the following command which will generate a new schema migration file. I’m also setting the `DATABASE_URL` environment variable used by `drizzle.config.ts`:

```bash
export DATABASE_URL=postgresql://teamtodo_owner:mydbpass@ep-weathered-wildflower-a5okjpjr.us-east-2.aws.neon.tech/teamtodo?sslmode=require
drizzle-kit generate
```

A new file is automatically generated and placed in the `drizzle` folder with the necessary SQL to apply:

```sql {{ filename: 'drizzle/0001_loose_mojo.sql' }}
ALTER TABLE "tasks" ADD COLUMN "description" text;
```

Next, I’ll run the following command to update the database schema in Neon, adding the `description` column:

```bash
export DATABASE_URL=postgresql://teamtodo_owner:mydbpass@ep-weathered-wildflower-a5okjpjr.us-east-2.aws.neon.tech/teamtodo?sslmode=require
drizzle-kit migrate
```

Once the migration is applied, the new column is added to the database and is ready to test. The schema will now look like this:

![A database table showing columns named "name", "is\_done", "owner\_id", "created\_on", "created\_by\_id", and "description"](./table2.png)

### Updating the production environment

When I’m ready to move the code to production, I can apply the migrations to the `main` database branch by passing in that branch's connection string:

```bash
export DATABASE_URL=postgresql://teamtodo_owner:mydbpass@ep-frosty-tree-a54nb30r.us-east-2.aws.neon.tech/teamtodo?sslmode=require
drizzle-kit migrate
```

Once the migrations are applied to the `main` database branch, I can deploy the production version of my application. Platforms like Vercel (which I am using to host this application) will commonly monitor the `main` branch of a repository for changes and kick off the deploy process when a change is detected. Merging my code changes into `main` will trigger Vercel’s CI tools to deploy the newest version of the code.

I can deploy my application to Vercel by merging the changes into the `main` code branch and letting Vercel’s CI tools deploy the newest version of the code.

While this example makes a single change, multiple schema migrations can be generated and applied between deployments for more complex schema changes. The files will be applied in the order they were created, ensuring that the state of the production database matches the development environment.

### Automating migrations with GitHub Actions

Using Drizzle Kit to apply schema migrations helps to ensure that your schema is safely updated between deployments, but manually performing this operation introduces a point of failure in the process. If you forget to apply the migrations, and your schema doesn't match what the application expects, you could inadvertently take your service down.

GitHub Actions, a platform built into GitHub, provides a way for developers to define workflows that execute automatically when specific events (such as making changes to a repository branch) occur on GitHub. Let’s look at how GitHub Actions can automatically apply migrations when the code on the `main` branch changes.

First, I’ll need to securely store the connection string for my database in GitHub in a way that the GitHub Actions service can access it. This is done by adding a repository secret in **"Settings"** under **"Secrets and variables"**, then **"Actions"**.

![GitHub repository settings, adding the "DATABASE\_URL" secret.](./gha.png)

The following GitHub Actions workflow can be used to execute `drizzle-kit migrate` each time a change is performed on the `main` branch in GitHub:

```yaml
name: Apply schema migrations

# 👉 Only run this workflow when a change is made to the main branch
on:
  push:
    branches:
      - main

jobs:
  apply_migrations:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Install dependencies & tooling
        run: |
          npm install
          npm install -g drizzle-orm drizzle-kit pg
      - name: Apply migrations
        run: drizzle-kit migrate
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
```

Since Vercel triggers a deployment when changes are made on the `main` repository branch, the Actions workflow will trigger simultaneously with the deployment, ensuring that your code and schema versions always stay in sync.

## Conclusion

Properly applying changes to a database when new features are added to an application is important to retaining the data within the database, as well as maintaining the uptime of your application.

After reading this article, you now understand what schema migrations are and how they’re used in the development lifecycle of a database. You should also know how to use Drizzle to generate and apply migrations, and how to automate this process using GitHub Actions.

---

# A guide to reading authenticated user data from Clerk
URL: https://clerk.com/blog/read-user-data-guide.md
Date: 2024-08-15
Category: Guides
Description: Learn how to access data about the currently authenticated user with Clerk's APIs and session claims.

Reading data about the authenticated user is a fundamental part of any application.

Whether you need to access the user's ID, username, contact information, or profile data, Clerk provides various methods to accomplish this depending on your runtime environment and application needs.

Options are great! But they can also make it challenging to decide which approach to take. I wrote this guide to explain the different methods and their appropriate use cases. This way, you can choose the most suitable and efficient option for your circumstances.

> \[!IMPORTANT]
> This post is about accessing data about the currently-authenticated user. If you want to query data about unauthenticated users, you can use the Backend API or Webhooks. For guidance on which is best for your circumstances, please refer to [Clerk Webhooks vs BackendAPI](/blog/webhooks-v-bapi).

## Methods to read authenticated user data

Below is a table summarizing the different approaches to read  authenticated user data from Clerk.

Use it to understand your options at a glance or reference the next time you return to this page.

![Read User Data Guide setup guide](./table.png)

## Frontend API with `useUser()`

Clerk's [Frontend API](/docs/reference/frontend-api) enables authenticated users to access their own data and perform actions specific to their account from a browser or native environment.

To access the authenticated user, you could call the `/v1/me` endpoint. However, the convenient option in Next.js is to invoke [`useUser()`](/docs/references/react/use-user), which queries `/v1/me` under the hood.

```tsx {{ title: 'useUser()' }}
'use client'
import { useUser } from '@clerk/nextjs'

export default function Example() {
  const { isLoaded, isSignedIn, user } = useUser()

  if (!isLoaded || !isSignedIn) {
    return null
  }

  return <div>Hello, {user.firstName} welcome to Clerk</div>
}
```

Certain Frontend API requests are rate-limited but `/v1/me` is not. This endpoint has no defined limit, making it practically unlimited.

### Guidance

When to use Frontend API with `useUser()`:

- Call `useUser()` from a client component when you need to update the UI with information about the authenticated user.
- Best-suited for when you need to dynamically update the UI in response to a client event. `useUser()` returns a [`User`](/docs/references/javascript/user/user) with a `reload()` function that makes it even easier to render the most up-to-date user information.
- Also useful for loading user information on page load, though server-side rendering (SSR) may be preferred for this.

When to avoid:

- `useUser()` won't work from a server environment and is therefore inappropriate for server-side-rendering user information.
- Unsuitable for accessing user private metadata.
- Avoid querying and sending user data from the frontend to the backend. Fetch and verify the data directly on the backend to ensure its integrity.

## Backend API with `currentUser()`

Clerk's [Backend API](/docs/reference/backend-api) is designed to query user data from a server environment.

The [`currentUser()`](/docs/references/nextjs/current-user) helper is used to access information about the currently authenticated user from server components, server actions, and other server-side code like so:

```tsx {{ title: 'currentUser()' }}
import { auth, currentUser } from '@clerk/nextjs/server'

export default async function Page() {
  // Get the Backend API User object when you need access to the user's information
  const user = await currentUser()
  // Use `user` to render user details or create UI elements
}
```

> \[!IMPORTANT]
> `currentUser()` calls Clerk's Backend API behind the scenes. This counts towards your application's [Backend API request rate limit](/docs/backend-requests/resources/rate-limits#backend-api-requests) of 100 requests per 10 seconds.

### Guidance

When to use `currentUser()`:

- Call `currentUser()` from a Next.js server component, server action, API route handler, or other backend code where you require data about the authenticated user.
- Suitable for accessing [user private metadata](/docs/users/metadata#private-metadata).
- A good option when you need to query potentially large attributes such as `user.organizatons` from a backend environment. Dynamic attributes like these are usually too big for custom session claims (session claims are the subject of the next section).
- Suitable for accessing user data infrequently due to rate limits.

When to avoid:

- Do not exceed 100 requests per 10 seconds. This limit applies to *all* Backend API requests made by your application. Exceeding this limit will result in a `429 Too Many Requests` error and your application might not work properly.
- Avoid querying and storing user data in your database. If a user updates their information via a Clerk component like [`<UserProfile />`](https://clerk.com/docs/components/user/user-profile), your database won't be synchronized. Instead, store only the user ID and fetch the latest user data from Clerk as needed.

> \[!NOTE]
> If you *only* need the authenticated user ID, calling the Backend API is not efficient.
>
> Read the `userId` default session claim with `auth()` or `useAuth()` using the guidance in the [next section](#session-claims) instead.

## Session claims

Claims are pieces of information about the authenticated user. They're encoded in the [Clerk session token](/docs/backend-requests/resources/session-tokens) and digitally signed to ensure the information is authentic.

Claims are accessible from frontend or backend environments, and since they don't require an API roundtrip, rate limits do not apply.

The user ID is included in the token by [default](/docs/backend-requests/resources/session-tokens#default-session-claims). However, the user's username, contact information, and other attributes are not. To access this optional data, it's necessary to customize the session token with custom claims.

### Read user ID

Below are two code examples demonstrating how to access the authenticated user's ID on the client and on the server with the  [`useAuth()`](/docs/references/react/use-auth) and [`auth()`](/docs/references/nextjs/auth) helpers respectively:

```tsx {{ title: 'useAuth()' }}
'use client'
import { useAuth } from '@clerk/nextjs'

export default function Page() {
  const { isLoaded, userId, sessionId } = useAuth()

  // In case the user signs out while on the page.
  if (!isLoaded || !userId) {
    return null
  }

  return (
    <div>
      Hello, {userId} your current active session is {sessionId}
    </div>
  )
}
```

```tsx {{ title: 'auth()' }}
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  // Get the userId from auth() -- if null, the user is not signed in
  const { userId } = auth()

  if (userId) {
    // Use `userId` to store an entity in the database
  }
}
```

### Set and read custom session claims

To customize session claims for your application, open the Clerk dashboard then click **Sessions**.

Look for the "Customize session token section" and press **Edit**.

Define a JSON structure that includes dynamic placeholders called shortcodes (remember to **Save** after). Clerk will replace these shortcodes with the actual values at runtime:

```json {{ title: 'Claims' }}
{
  "firstName": "{{user.first_name}}",
  "lastName": "{{user.last_name}}",
  "email": "{{user.primary_email_address}}",
  "avatarUrl": "{{user.image_url}}",
  "publicMetadata": "{{user.public_metadata}}"
}
```

Read custom session claims from a backend environment with the same [`auth()`](/docs/references/nextjs/auth) helper and the returned `sessionClaims` property.

```tsx {{ title: 'auth()' }}
import { auth } from '@clerk/nextjs/server'

export default async function Page() {
  // Get the userId from auth() -- if null, the user is not signed in
  const { userId, sessionClaims } = auth()

  if (userId && sessionClaims) {
    const { firstName, lastName, email, avatarUrl, publicMetadata } = sessionClaims
  }
}
```

> \[!TIP]
> To get auto-complete and prevent TypeScript errors when working with custom session claims, [define a global type](/docs/backend-requests/making/custom-session-token#add-global-type-script-type-for-custom-session-claims).

Custom session claims are technically accessible from frontend environments, but this is rarely needed, so the Next.js SDK doesn't provide a dedicated helper.

### Guidance

When to use session claims:

- Custom session claims are best suited for scenarios where you need to frequently access certain user attributes from the backend, but the Backend API rate limits would normally be prohibitive.
- In either frontend or server environments, session claims are the most efficient way to access just the user ID. This eliminates the need for unnecessary API calls.

When to avoid:

- The *entire* session token must not exceed 4KB due to browser cookie size limits. Use good judgment when including custom claims such as `user.organizations`, which can cause the token to exceed this limit and break your app. Only store essential, predictably-sized user data in the token, and occasionally fetch larger claims through separate Backend API calls.
- Unsuitable for accessing user private metadata.
- Do not use custom session claims in a frontend environment. It requires less configuration and code to use the Frontend API with `useUser()`, as described at [the top](#frontend-api-with-use-user) of this guide.

---

# Role based access control with Clerk Organizations
URL: https://clerk.com/blog/role-based-access-control-with-clerk-orgs.md
Date: 2024-08-09
Category: Guides
Description: Learn what role based access control is and how to use it with Clerk Organizations to simplify permissions management.

Managing permissions in large SaaS applications can be a nightmare.

Providing team owners a way to grant functionality to users in a simplified way can be the difference between companies purchasing your software or going with a competitor. Clerk provides you with a way to build this functionality with minimal effort. By utilizing roles and permissions built into [Organizations](/docs/organizations/overview), you can implement role-based access control for your users.

> RBAC is essential for B2B applications. [Learn more about our B2B SaaS solution](/b2b-saas) for comprehensive enterprise features.

In this article, you'll learn how roles and permissions in Clerk can be used to allow functionality within an application for a user based solely on the role they are assigned.

## What is Role-Based Access Control?

Role-Based Access Control (RBAC) is a method of managing application security by granting permissions to systems based on the role assigned to the user.

When a user signs into an application, they provide their credentials to prove their identity. This process of the user proving *who they are* is known as Authentication in cybersecurity. Role-based access control is involved in the process of Authorization, which is identifying *what they can do*.

With RBAC, individual permissions can be linked to one or more roles. Roles are often correlated to the job function of the user, granting them access to everything they need to fulfill their duties while preventing them from having access to what they don't need.

As your system evolves, you can simply update the permissions assigned to a given role to grant those members access to new functionality. This approach to security makes security management easier compared to assigning permissions to individual users.

## Implement RBAC with organizations

The example used in this article is built on the concept of a team-based task management app built with Next.js, where each team is an organization in Clerk.

There will be three roles each with a different set of permissions included. Based on the role assigned to the user, their ability to perform operations within the app will vary. The following diagram demonstrates how three separate users with different roles will inherit their permissions:

- Charlie will have the Viewer role and can view the tasks, but not create or modify them.
- Bob has the Member and will be able to add and edit their own tasks, but not tasks created by another user.
- Alice will have the Manager role and will be able to manage all tasks in the organization.

![A diagram showing how permissions roll into roles and are then assigned to users.](./rbac-diagram.png)

The code referenced in this article is open-source and can be [accessed on GitHub](https://github.com/bmorrisondev/team-todo-demo/tree/article-3) in the `article-3` branch.

## Defining custom roles and permissions

When you enable organizations for a Clerk application, there are two default roles and a set of default permissions.

The Admin role contains all of the necessary permissions to manage the organization and its members, and the Member role can view the active organization, but not manage it. Developers can create custom roles and permissions that can be assigned by organization administrators as well, located in the Clerk dashboard in the **"Organization settings"** of the side nav under **"Roles"** and **"Permissions"** respectively.

In this example, there are three custom permissions created:

| Name         | Key          | Description                       |
| ------------ | ------------ | --------------------------------- |
| Manage tasks | tasks:manage | Allow users to edit all tasks.    |
| Edit tasks   | tasks:edit   | Allows users to edit their tasks. |
| View tasks   | tasks:view   | Allows users to view tasks.       |

There are also two custom roles created, and the Member role is updated to include the `View tasks` and `Edit tasks` permissions:

| Name     | Key     | Description                                                     | Permissions                                        |
| -------- | ------- | --------------------------------------------------------------- | -------------------------------------------------- |
| Manager  | manager | Users with this role can create tasks and edit all tasks.       | View tasks, Edit tasks, Manage tasks, Read Members |
| Member\* | member  | Users with this role can create tasks and edit their own tasks. | View tasks, Edit tasks, Read members               |
| Viewer   | viewer  | Users with this role can only view tasks.                       | View tasks, Read members                           |

- The Member role is a default role.

Now when a user is invited, administrators can set their role even before they accept the invitation.

![The Clerk user invite modal with brian@clerk.dev populated in the text area and a red arrow pointing to a dropdown showing the Admin role selected.](./invite-users.png)

## Adjusting functionality based on permissions

A role defines a set of permissions, but the permissions themselves should dictate what parts of the application users within that role have access to.

Clerk provides [a set of authorization helper functions](/blog/introducing-authorization) that can be used to check if the active user has a specific set of permissions and appropriately adjust the way the application behaves. The following example demonstrates how the `has()` function can be used with the name of the permission to determine if the user can create or edit tasks:

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

export function canCreateTasks() {
  const { sessionClaims, has } = auth()
  if (!isLicensed()) return false
  let canCreateTasks = false
  // 👉 If there is no org, it's the user's personal account
  if (!sessionClaims?.org_id) {
    canCreateTasks = true
  }
  // 👉 Check to make sure the user has the 'org:tasks:edit' permission
  if (sessionClaims?.org_id && has({ permission: 'org:tasks:edit' })) {
    canCreateTasks = true
  }
  return canCreateTasks
}

export function canEditTask(createdById: string) {
  if (!isLicensed()) return false
  const { userId, sessionClaims, has } = auth()
  let canEditTask = false
  // 👉 If there is no org, it's the user's personal account
  if (!sessionClaims?.org_id) {
    canEditTask = true
  } else {
    // 👉 If the user has the 'org:tasks:manage' permission, they can edit any task
    if (has({ permission: 'org:tasks:manage' })) {
      canEditTask = true
      // 👉 If the user has the 'org:tasks:edit' permission AND the user IDs match, they can edit this task
    } else if (has({ permission: 'org:tasks:edit' }) && createdById === userId) {
      canEditTask = true
    }
  }
  return canEditTask
}
```

These security functions are passed into the rendered components to determine if they should be disabled:

```tsx {{ filename: 'src/app/page.tsx' }}
<div className="flex flex-col">
  <AddTaskForm disabled={!canCreateTasks()} />
  <div className="flex flex-col gap-2 p-2">
    {tasks.map((task) => (
      <TaskRow key={task.id} task={task} disabled={!canEditTask(task.created_by_id)} />
    ))}
  </div>
</div>
```

The security functions can also be used to verify user's permissions in server actions. The following function is executed when the user wants to create a task. By leveraging the `canCreateTasks()` function, an erorr is thrown if the user attempts to do something they are not permitted to do:

```ts {{ filename: 'src/app/actions.ts' }}
import { canCreateTasks, getUserInfo } from './security'

export async function createTask(name: string) {
  if (!canCreateTasks()) {
    throw new Error('User not permitted to create tasks')
  }

  const { userId, ownerId } = getUserInfo()
  await sql`
    insert into tasks (name, owner_id, created_by_id) values (${name}, ${ownerId}, ${userId});
  `
}
```

## Working directly with roles and permissions

While Clerk offers a simple way to check the active user's permissions out of the box, there may be a situation where you want to check their roles or permissions manually.

By default, a user's organizational permissions can be accessed through the `sessionClaims` object of the `auth()` function:

```ts
const { sessionClaims } = auth()
```

This gives you the flexibility to leverage the roles and permissions as you see fit. The following sample shows the structure of the `sessionClaims` if a user has selected an organization:

```json
{
  "azp": "http://localhost:3005",
  "exp": 1721770193,
  "iat": 1721770133,
  "iss": "https://assuring-cod-50.clerk.accounts.dev",
  "jti": "daeb648e4c6dbfbdd2ce",
  "nbf": 1721770123,
  "org_id": "org_2ieWEfZl0M6ccS1Ap1XVRbEm9Kk",
  "org_metadata": {
    "isLicensed": true
  },
  "org_permissions": ["org:tasks:edit", "org:tasks:view", "org:tasks:manage"],
  "org_role": "org:manager",
  "org_slug": "d2-gamers",
  "sid": "sess_2jfFHBJQpqzwZbnwHXti0yxCz1G",
  "sub": "user_2iVvo8iFCJQJ1WCXeBk5T9lUTO5"
}
```

## Conclusion

With minimal effort, role-based access control is made accessible to applications of any size using organizations in Clerk. By checking the list of permissions a user has based on their assigned role, you can easily enable different areas of your application, or restrict functionality.

---

# Per-user B2B monetization with Stripe and Clerk Organizations
URL: https://clerk.com/blog/per-user-licensing-with-stripe-and-clerk-organizations.md
Date: 2024-08-02
Category: Guides
Description: Learn how to architect a B2B application for per-user licensing with Stripe and Clerk Organizations

Businesses tend to spend more money on software compared to individual consumers.

One of the most popular monetization models for B2B applications is the Per-User model, where a business purchases one “seat” for each user who will be using the application. Per-user pricing is a great way for application developers to generate income. The model is relatively straightforward, provides predictable pricing for finance departments, and allows for a steady stream of income that scales with the use of your application

In this article, you'll learn how Clerk Organizations and Stripe can be configured to implement per-user monetization into a web application.

> This guide focuses on implementation details. For a comprehensive overview of B2B SaaS architecture and features, [learn more about our B2B SaaS solution](/b2b-saas).

## Project Overview

This article will use an open-source application called Team Task that is preconfigured with the functionality described below. All of the critical parts of the code that enable per-user licensing will be thoroughly explained, however, you are welcome to dive right into the [code hosted in GitHub](https://github.com/bmorrisondev/team-todo-demo/tree/article-2).

Everything is built with self-service in mind, so users will be able to do the following without assistance from you:

- Create organizations and invite users
- Add and manage licenses using Stripe
- Assign and remove licenses from individual users

Using the pre-built Clerk components, users will be able to create organizations and invite users.

Once the organization is created, they will be prompted to purchase licenses via Stripe. Once purchased, administrators can toggle users to gain full use of the application.

Finally, administrators will also be able to easily manage their Stripe subscription with the click of a button.

## Creating organizations

Clerk Organizations allows you to easily add multi-tenancy into an application by letting users create organizations and invite users into them using the `OrganizationSwitcher` component.

When a user creates an organization, they'll immediately be asked if there are any users they want to invite into the organization by simply providing a list of email addresses. Behind the scenes, our system will also check to see if the Clerk application has any endpoints that are configured to receive a webhook when an organization is created.

Webhooks are a way for one service to inform another when an event occurs. The event, in this case, was that an organization was created. Team Task contains a route handler at `/api/clerk-hooks` that is configured to accept the following payload that Clerk will send when an organization is created:

```json
{
  "data": {
    "admin_delete_enabled": true,
    "created_at": 1721316613833,
    "created_by": "user_2iNu3heTeGj0U8G2gGFPWnVLbZm",
    "has_image": false,
    "id": "org_2jQQ2U3ykrhcoElPbh6ZVgUPKlV",
    "image_url": "https://img.clerk.com/eyJ0eXBlIjoiZGVmYXVsdCIsImlpZCI6Imluc18yaU50WjRDSGh2V1UwUW14bzYzZE81S3NNRjIiLCJyaWQiOiJvcmdfMmpRUTJVM3lrcmhjb0VsUGJoNlpWZ1VQS2xWIiwiaW5pdGlhbHMiOiJEIn0",
    "logo_url": null,
    "max_allowed_memberships": 5,
    "name": "Dev Ed",
    "object": "organization",
    "private_metadata": {},
    "public_metadata": {},
    "slug": "dev-ed",
    "updated_at": 1721316613833
  },
  "event_attributes": {
    "http_request": {
      "client_ip": "73.36.196.123",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
    }
  },
  "object": "event",
  "type": "organization.created"
}
```

To automate the process of creating Stripe customer records based on organizations in the application, we can accept the webhook message, create a Stripe customer, and create a record in a Neon table to associate the Clerk `org_id` to the Stripe `customer_id`.

```tsx {{ filename: 'src/app/api/clerk-hooks/route.ts' }}
const stripe = new Stripe(process.env.STRIPE_KEY as string)
const sql = neon(process.env.DATABASE_URL as string)

const handler = createWebhooksHandler({
  secret: process.env.CLERK_WEBHOOK_SECRET as string,
  onOrganizationCreated: async (org) => {
    // Create customer in Stripe
    const customer = await stripe.customers.create({
      name: org.name,
    })

    // Create record in neon
    await sql`insert into orgs (org_id, stripe_customer_id) values (${org.id}, ${customer.id})`
  },
})
```

This table will also track the number of licenses an organization has purchased, using a default value of `0` when the record is created.

![A sample of data from the orgs table in Neon](./neon-table.png)

### Process overview

1. A user starts the process by creating an organization in Clerk.
2. Clerk's backend will asynchronously send a message to a route handler informing the application that a new organization was created.
3. The application will create a customer in Stripe.
4. Finally, the application will insert a new row into a Neon database to associate the Clerk Organization with the Stripe Customer, along with a default license count of 0.

![The process diagram walking through the following steps: creating an organization in Clerk, a webhook being sent from Clerk to the application, the application creating a customer record in Stripe, the application storing the values in a Neon database.](./create-org-flow.png)

## Initial license purchase

Once the organization is created and users are invited, we'll redirect the current user to a page that lets them purchase licenses for the organization.

The `OrganizationSwitcher` uses the `afterCreateOrganizationUrl` prop to automatically forward the user to the `/licensing` page.

```tsx {{ filename: 'src/components/navbar.tsx' }}
<OrganizationSwitcher afterCreateOrganizationUrl={'/licensing'} />
```

The licensing page is used for both the initial license purchase as well as managing and allocating licesnes over time. When the page is loading, it queries the `license_count` value in the `orgs` table for that organization to determine how to render the page.

```tsx {{ filename: 'src/app/licensing/page.tsx' }}
<div className="mb-4 flex justify-center">
  {currentLicenseCount === 0 ? (
    <PurchaseLicensesCard />
  ) : (
    <ManageLicensesCard
      licensedUsersCount={currentlyLicensedUsers}
      purchasedLicensesCount={currentLicenseCount}
    />
  )}
</div>
```

The `PurchaseLicensesCard` component displays an input for the user to select how many licenses are required. Selecting the **"Purchase via Stripe"** button will use that value to create a Stripe Checkout Session using a server action.

Creating a Checkout Session requires the customer ID, a product ID that represents the per-user rate, purchase quantity, and redirect URLs. The session object returned from Stripe will contain the `url` that the user should be sent to for completing the transaction.

```tsx {{ filename: 'src/app/licensing/actions.ts' }}
export async function getCheckoutUrl(clerkOrgId: string, quantity: number) {
  const [row] = await sql`select stripe_customer_id from orgs where org_id=${clerkOrgId}`
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    customer: row.stripe_customer_id,
    line_items: [
      {
        price: 'price_1PajlBGVJ29rMAV1JmqqgEwa',
        quantity: quantity,
        adjustable_quantity: {
          enabled: true,
          minimum: 1,
        },
      },
    ],
    success_url: 'http://localhost:3005/licensing',
    cancel_url: 'http://localhost:3005/licensing',
  })
  return session.url
}
```

Since the `PurchaseLicensesCard` is a client component, we can redirect them using `window.location.href`:

```tsx {{ filename: 'src/app/licensing/PurchaseLicensesCard.tsx' }}
async function onPurchaseClicked() {
  setIsLoading(true)
  const url = await getCheckoutUrl(organization?.id as string, count)
  window.location.href = url as string
}
```

The user will then be prompted for payment info to complete the transaction.

![The Stripe checkout page.](./stripe-checkout.png)

After payment, they'll be redirected back to `/licensing`, where a list of users will be displayed to allocate licenses to.

Stripe offers webhooks for a wide array of events that occur on their end as well. The `customer.subscription.created` webhooks can be used to update the `license_count` value of an organization in the Neon database to match the value that was purchased:

```tsx {{ filename: 'src/app/api/stripe-hooks/route.ts' }}
export async function updateLicenseCount(stripeCustomerId: string, quantity: number) {
  const sql = neon(process.env.DATABASE_URL as string)
  await sql`update orgs set license_count=${quantity} where stripe_customer_id=${stripeCustomerId}`
}

export async function POST(request: NextRequest) {
  const sig = request.headers.get('stripe-signature')
  const body = await request.text()
  const event = stripe.webhooks.constructEvent(body, sig, endpointSecret)
  // Handle the event
  switch (event.type) {
    case 'customer.subscription.created':
      await updateLicenseCount(
        event.data.object.customer as string,
        // @ts-ignore
        event.data.object.quantity,
      )
      break
    default:
      console.log(`Unhandled event type ${event.type}`)
  }
  return new NextResponse(null, { status: 200 })
}
```

### Process overview

1. The user provides the number of licenses to purchase, and the application requests a Checkout Session URL from Stripe.
2. The user is sent to Stripe to complete the transaction.
3. Stripe will redirect the user back to the application URL while simultaneously sending an asynchronous webhook message to a route handler of the application.
4. The application will update the `license_count` value for that organization in the Neon database.

![A process diagram showing the following steps: the application requesting creating a checkout session from Stripe, the user being redirected for checkout, stripe redirecting back to the application on purchase as well as sending a webhook message to the application informing it of the number of licenses purchased, finally the application setting the value in the Neon database.](./purchase-licenses-flow.png)

## Licensing users

Now that licenses have been purchased, they need to be assigned to users.

As mentioned in the previous section, the `/licensing` page will automatically be updated to render a list of users who are members of an organization by querying the `license_count` value on load.

Since individual users can have access to multiple organizations within a single Clerk application, we use the concept of a “membership” to associate a user to an organization, modeling what is effectively a “many to many” relationship:

![A crowfoot diagram showing the many to many relationship between Clerk users and Clerk organizations by using Memberships as the joining entity.](./memberships-model.png)

Clerk offers various types of metadata to add arbitrary data to entities in Clerk, and memberships can also contain metadata independent of the organization or user. Toggling on the last column will flag the user as “licensed” in their membership metadata using the following server action:

```tsx {{ filename: 'src/app/licensing/actions.ts' }}
export async function toggleUserLicense(orgId: string, userId: string, status: boolean) {
  await clerkClient.organizations.updateOrganizationMembershipMetadata({
    organizationId: orgId,
    userId: userId,
    publicMetadata: {
      isLicensed: status,
    },
  })
}
```

When the licensing page loads, the Clerk Backend API is used to query the memberships of the organization, which includes the metadata used to flag a specific user as licensed in that organization. This both sets the toggle for the user row as well as to aggregate the total number of currently licensed users, which will also be used to determine how many licenses are available.

```tsx {{ filename: 'src/app/licensing/page.tsx' }}
const [row] =
  await sql`select license_count from orgs where org_id=${sessionClaims?.org_id as string}`
const currentLicenseCount = row.license_count
let currentlyLicensedUsers = 0

// Load users
let res = await clerkClient.organizations.getOrganizationMembershipList({
  organizationId: sessionClaims?.org_id as string,
})
const users: UserRowViewModel[] = []
res.data.forEach((el) => {
  let name = el.publicUserData?.firstName
    ? `${el.publicUserData?.firstName} ${el.publicUserData?.lastName}`
    : ''
  const isLicensed = (el.publicMetadata?.isLicensed as boolean) || false
  if (isLicensed) {
    currentlyLicensedUsers++
  }
  users.push({
    id: el.publicUserData?.userId as string,
    orgId: sessionClaims?.org_id as string,
    email: el.publicUserData?.identifier as string,
    name: name,
    isLicensed,
  })
})
```

If the `currentlyLicensedUsers` value is equal or greater than `currentLicenseCount` and the user  is not already licensed, the ability to enable licenses for a user can be disabled:

```tsx {{ filename: 'src/app/licensing/page.tsx' }}
<TableBody>
  {users?.map((u) => (
    <UserRow
      key={u.id}
      id={u.id}
      orgId={u.orgId}
      name={u.name ? u.name : u.email}
      isLicensed={u.isLicensed}
      emailAddress={u.email}
      disabled={!u.isLicensed && currentlyLicensedUsers >= currentLicenseCount}
    />
  ))}
</TableBody>
```

## Managing the subscription

Besides rendering a list of users, the `/licensing` page now also renders the `ManageLicensesCard` component to display the available of licenses along with a button to manage the current subscription using the Stripe Customer Portal.

![The application licensing page showing a "Manage Subscription" card with the number of available seats, number purchased, and a button to manage the subscription. A list of users with toggles to indicate license status is also shown.](./licensing-page.png)

The [Customer Portal](https://docs.stripe.com/customer-management) is a hosted solution from Stripe that allows users to manage their own subscriptions without developers having to build a custom user interface.

This feature is off by default, but can easily be enabled in the [Stripe Dashboard](https://dashboard.stripe.com/test/settings/billing/portal). Since licenses are managed via Stripe subscriptions, we only need to allow subscription management for our customers for this specific SKU.

![The settings page for the Stripe Customer Portal.](./stripe-customer-portal-settings.png)

As with the Checkout Session for the initial license purchase, a custom URL will be generated based on the Stripe customer ID:

```tsx {{ filename: 'src/app/licensing/actions.ts' }}
export async function getPortalUrl(clerkOrgId: string) {
  const stripeId = await getStripeCustomerIdFromOrgId(clerkOrgId)
  const session = await stripe.billingPortal.sessions.create({
    customer: stripeId,
    return_url: 'http://localhost:3005/licensing',
  })
  return session.url
}
```

After the URL is returned to the front end, the user will be redirected to the Customer Portal where they can adjust their licenses as needed:

![The Stripe Customer Portal.](./stripe-customer-portal.png)

The `customer.subscription.updated` event from Stripe will also be handled in the route handler to update the license count for a specific organization:

```tsx {{ filename: 'src/app/api/stripe-hooks/route.ts', prettier: false }}
case 'customer.subscription.updated':
  await updateLicenseCount(
    event.data.object.customer as string,
    // @ts-ignore
    event.data.object.quantity,
  )
  break
```

### Process overview

1. The application generates a Customer Portal URL from Stripe for the active customer.
2. The user can manage the number of licenses active in their subscription.
3. Upon updating the subscription, Stripe sends a webhook to the application informing it of the change.
4. The application updates the `license_count` in the database.

![A process diagram showing the following steps: the application requesting a Customer Portal URL from Stripe, redirecting the user to Stripe to update the licenses, Stripe sending a webhook to the application informing it of the updated licensing count, the application updating the license count in the Neon table.](./manage-licenses-flow.png)

## Accessing license status

Clerk makes it easy to access information about the currently logged-in user, and accessing membership metadata is no exception.

Recall that we stored the `isLicensed` flag within the membership metadata. To access these values, the `sessionClaims` object of the `auth()` function can be used:

```tsx {{ filename: 'src/app/page.tsx' }}
const { sessionClaims } = auth()

let isLicensed = false
if (sessionClaims?.org_metadata && sessionClaims?.org_metadata.isLicensed) {
  isLicensed = true
}
```

Then you can decide what functionality of the application can be restricted based on that flag, for example:

```tsx {{ filename: 'src/app/page.tsx', prettier: false }}
<AddTaskForm disabled={!isLicensed} />
<div className="flex flex-col gap-2 p-2">
  {tasks.map((task) => <TaskRow key={task.id} task={task} disabled={!isLicensed} />)}
</div>
```

## Summary

Per-user licensing is a great way to monetize an application. Stripe offers a great suite of tools that allows developers to process transactions and generate revenue from their work. By combining with power of Clerk Organizations with Stripe, you can build a seamless workflow for your users to independently create their own tenants, purchase and assign licenses for those tenants, and change the subscription at any time.

---

# Build a team-based task manager with Next.js, Neon, and Clerk
URL: https://clerk.com/blog/build-a-team-based-task-manager-with-organizations.md
Date: 2024-07-09
Category: Guides
Description: Use Clerk Organizations to build a task management app that isolates tasks to specific teams.

Building a multi-tenant application with robust permissions can be tricky.

Clerk Organizations were designed to simplify adding multi-tenant functionality to your application. By implementing Organizations, your users will be empowered to create isolated areas of your application, while also allowing users to have granular permissions based on their assigned roles, restricting or permitting functionality as needed.

> Organizations are a core feature of B2B applications. [Learn more about our B2B SaaS solution](/b2b-saas) for comprehensive multi-tenant architecture.

In this article, we'll take leverage the common example of a to-do app and implement Clerk Organizations to enable users to access task lists that are shared across teams.

## Project overview and setup

This guide uses an open-source starter project that you can clone to your computer to build on.

The project is a multi-tenant take on a simple concept; a task management app. Upon setting up the project, you'll have a functional to-do app where you can add, complete, and update tasks. Throughout this guide, you'll add the ability to create and switch between organizations, where each organization has an isolated list of tasks where multiple users can be invited into for collaboration.

To follow along, this guide assumes you have a general understanding of Next.js as well as [Node and NPM installed locally](https://nodejs.org/en/download/package-manager) on your workstation.

### Clone the project locally

To follow this guide, open your terminal and clone the starter repository using the following script. This will also switch you to the `article-1` branch which contains the proper starting point for this article:

```bash
git clone https://github.com/bmorrisondev/team-todo-demo.git
git checkout article-1
```

Now run the following script in your terminal to install the necessary dependencies.

```bash
npm install
```

### Set up a Clerk project

If you do not have a Clerk account, create one before proceeding, which will walk you through creating a project. If you already have an account, create a new project for this guide. Give the project a name and accept the default login providers: Email and Google.

You'll be presented with a Next.js quick start guide. Follow only step 2, which instructs you to create the `.env.local` file and populate it with the necessary environment variables. The remainder of the steps are already completed as part of the starter repo.

### Set up a Neon database

While any Postgres database should be usable, this guide leverages Neon. If you do not have an account, create one at neon.tech. If you do, create an empty database, copy the connection string from the “**Connection Details**” block, and add it to your `.env.local` file as `DATABASE_URL` like so:

```bash
DATABASE_URL=postgresql://teamtodo_owner:*********@ep-frosty-tree-a54nb30r.us-east-2.aws.neon.tech/teamtodo?sslmode=require
```

Next, access the SQL Editor from the left navigation and paste in the following database script to set up the schema:

```sql
create table tasks (
  id serial primary key,
  name text not null,
  description text,
  is_done boolean not null default false,
  owner_id text not null,
  created_on timestamp not null default now(),
  created_by_id text not null
);
```

### Access the project

Back on your computer, start the project with the following command:

```bash
npm run dev
```

By default, the application will be accessible at [`http://localhost:3000`](http://localhost:3000) however the port might be different if another process is using port 3000, so use what's shown in the terminal. Accessing the URL from your browser should prompt you to create an account using Clerk before rendering this:

![A to-do app logged in as a user with an input box, add button, and no tasks.](./team-task-start.png)

Feel free to test it out by adding a few tasks.

## Enable Clerk Organizations

Now that you understand the project and the current state it's in, let's start by setting up the Organizations feature in Clerk.

Log into the Clerk Dashboard and select **"Organization Settings"** from the left navigation. In the settings tab, click the toggle next to **"Enable organizations"** if it's not on already.

![The Clerk Dashboard with one arrow pointing towards "Organization Settings" in the left nav, and another pointing towards the toggle next to "Enable organizations".](./enable-orgs.png)

From now on, users within this Clerk application will be able to create organizations and invite other users to them. This setting is configurable on this same page. Make sure to leave the default checked for the remainder of this guide.

## Update the code

Now that Clerk Organizations are set up, we need to update the project to enable users to create organizations, switch between them, and create tasks specifically for that organization.

Start by adding the `OrganizationSwitcher` to the `Navbar` component:

```ts {{ filename: 'src/app/layout/Navbar.tsx', del: [3], ins: [4, 15] }}
import * as React from 'react'
import Link from 'next/link'
import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import { OrganizationSwitcher, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
import { metadata } from '@/app/layout'

function Navbar() {
  return (
    <nav className="flex items-center justify-between border-b border-slate-200 bg-slate-100 p-2">
      <div className="flex items-center gap-2">
        <div>{metadata.title as string}</div>
      </div>
      <SignedIn>
        <div className="flex items-center gap-2">
          <OrganizationSwitcher />
          <UserButton />
        </div>
      </SignedIn>
      <SignedOut>
        <Link href="/sign-in">Sign in</Link>
      </SignedOut>
    </nav>
  )
}

export default Navbar
```

The application should update to show “**Personal account**” next to your avatar in the upper right. This essentially indicates that the user does not have an organization selected that they are working in.

![The app with an arrow pointing towards the newly-added OrganizationSwitcher](./the-org-switcher.png)

This same menu gives you the ability to create an organization, however, the database queries will need to be modified to recognize that the user is in an organization, so let's get those updated now.

The `auth()` function, part of Clerk's Next.js SDK, returns token claims for the current user stored in `sessionClaims`. These claims can be used to determine if an organization is selected along with the permissions the user has set for that specific organization. By default the `org_id` value of the claims is not set unless the user has an organization selected, indicating they are in the “Personal account”.

The following is what `sessionClaims` looks like with an organization selected:

```tsx {{ prettier: false }}
{
  exp: 1719602340,
  iat: 1719602280,
  iss: 'https://assuring-cod-50.clerk.accounts.dev',
  jti: '3194d5c953057b24c256',
  nbf: 1719602270,
  org_id: 'org_2iR0dJJzzY3q9kLK0gsDVT08IP4',
  org_role: 'org:admin',
  org_slug: 'd2-gamers',
  sid: 'sess_2iWNGtu9GSFzttofPmhfB23FL9q',
  sub: 'user_2iNu3heTeGj0U8G2gGFPWnVLbZm',
}
```

Currently, the `getUserInfo` function in `src/app/actions.ts` uses the `auth()` function to return the user's ID from the claims, which is used as the `owner_id` in the database for a given task. The following snippet shows `getUserInfo` updated to conditionally return `org_id` if it is populated and how it is used in the database queries.

Note however that `userId` is still being used to maintain a record of who creates specific tasks, regardless if an organization is set or not.

```tsx {{ filename: 'src/app/actions.ts', ins: [18, [21, 23], 30, 34, 41, 45, 51, 55, 61, 65], del: [29, 33, 40, 44, 50, 54, 60, 64] }}
'use server'
import { auth } from '@clerk/nextjs/server'
import { neon } from '@neondatabase/serverless'

if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL is missing')
}
const sql = neon(process.env.DATABASE_URL)

function getUserInfo() {
  const { sessionClaims } = auth()
  if (!sessionClaims) {
    throw new Error('No session claims')
  }

  let userInfo = {
    userId: sessionClaims.sub,
    ownerId: sessionClaims.sub,
  }

  if (sessionClaims.org_id) {
    userInfo.ownerId = sessionClaims.org_id
  }

  return userInfo
}

export async function getTasks() {
  const { userId } = getUserInfo()
  const { ownerId } = getUserInfo()
  let res = await sql`
    select * from tasks
      where owner_id = ${userId};
      where owner_id = ${ownerId};
  `
  return res
}

export async function createTask(name: string) {
  const { userId } = getUserInfo()
  const { userId, ownerId } = getUserInfo()
  await sql`
    insert into tasks (name, owner_id, created_by_id)
      values (${name}, ${userId}, ${userId});
      values (${name}, ${ownerId}, ${userId});
  `
}

export async function setTaskState(taskId: number, isDone: boolean) {
  const { userId } = getUserInfo()
  const { ownerId } = getUserInfo()
  await sql`
    update tasks set is_done = ${isDone}
      where id = ${taskId} and owner_id = ${userId};
      where id = ${taskId} and owner_id = ${ownerId};
  `
}

export async function updateTask(taskId: number, name: string, description: string) {
  const { userId } = getUserInfo()
  const { ownerId } = getUserInfo()
  await sql`
    update tasks set name = ${name}, description = ${description}
      where id = ${taskId} and owner_id = ${userId};
      where id = ${taskId} and owner_id = ${ownerId};
  `
}
```

## Test Organization lists

To test the changes, use the Organization Switcher from the navigation bar and create a new organization.

The task list should automatically refresh into a blank list. Create a task to make sure it gets added to the list. Now switch back to the “Personal account” organization and notice how your previous list of tasks is rendered without the task you created while the organization was selected.

Now let's invite another user into the organization to demonstrate how the tasks in the organization are shared, but the “Personal account” tasks are isolated between users.

If you are in the “Personal account” still, use the switcher to select the organization you created. Once active, use the switcher again and click the gear icon next to the organization name. Then select Members from the left navigation in the modal, and finally click the “Invite” button. Invite another user via their email address.

Many email providers support [plus notation](https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html) where you can add a + to the end of your username along with an identifier to create a separate email address.

This can be used if you do not have multiple email addresses.

The user should receive an email with an invitation to the organization. Accept the invitation and create an account with the application. Upon completing the sign-up process, you should be dropped into the “Personal account” of the new user, which will have a blank list of tasks.

![A blank to do app logged in as a different account](./new-user-todo.png)

Now use the Organization Switcher to select your organization to see the tasks created by the previous user.

![The organization todo list as seen by the different user](./org-todo-list-as-new-user.png)

Adding additional tasks will show them for all users that have access to this organization.

## Conclusion

Clerk Organizations feature provides a way to easily add multi-tenancy into your application.

In this article, you learned how Organizations can be used to allow users to add and modify data across available organizations. By enabling Organizations and slightly tweaking the code based on the token claims, your applications can take advantage of isolated, collaborative environments just like the demo app that was built onto in this guide.

If you enjoyed this article, share it on X and let us know what you liked about it by tagging [@clerk](https://x.com/clerk)!

---

# Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements
URL: https://clerk.com/blog/building-a-hybrid-sign-up-and-subscribe-form-with-stripe.md
Date: 2024-06-18
Category: Guides
Description: Using custom flows, webhooks, and user metadata, learn how to build a single form that automatically subscribes new users.

I had a user reach out to me on X asking if there was any way to integrate a Stripe credit card entry field with Clerk's sign-up forms.

Can you actually create a sign up + card details input in a single page when using @clerk?

Using the OTP method.

Kostas is building a Chrome extension that uses AI to let users write responses to LinkedIn posts directly from their browser. To reduce the friction of users who want to sign up for the trial, he presented the following requirements:

1. It should be a single form that accepts an email address, tier selection, and credit card details.
2. The user should complete the sign-up using a one-time passcode sent to their email account.
3. Upon verifying their email, the user should automatically be signed up for a trial of the selected tier with no further interaction.

In this article, I'll walk through the process of building a completely custom sign-up form that matches the above requirements, starting with the end result.

## The final product

Before walking through how this solution was built, it's worth seeing it in action. The first phase of the signup process has the user entering the details outlined above.

![The hybrid sign-up and subscribe form.](./sign-up-form.png)

Upon completing this form, the user receives an email from Clerk with their sign-up code. The form in the previous screenshot will automatically update to accept a verification code.

![Email OTP verification form](./verification-form.png)

After the code is entered, the user is presented with a loading view, indicating that their account is being created in Clerk and the subscription is being registered in Stripe. Although the user experience appears seamless, the process happening behind the scenes is rather complex with a number of moving parts. Let's explore how this solution was built, starting with the front-end part.

![The user that has been registered and subscribed in Stripe](./stripe-subscription.png)

## Constructing the form

We'll start by exploring the components of both Clerk and Stripe that are used to build the user-facing part of this flow.

### Custom flows

Clerk has a great set of predesigned components that developers can drop directly into their application to provide a great sign-up and sign-in experience for their users.

In this scenario, however, the default components are not flexible enough to embed a product selection and credit card form, so we'll need to use [Custom flows](/docs/custom-flows/overview). Custom flows in Clerk allow you to build custom forms with your own logic to both register and sign in users, as well as customize the logic behind these actions to do whatever you need to for your application. Instead of using any of the components, we can instead build an HTML `<form>` with an `onSubmit` function to handle the submit process.

### Stripe Elements

[Stripe Elements](https://stripe.com/payments/elements) is a set of prebuilt components that can be used during the payment processing flow of your application.

One of these components is a credit card entry form that can generate a token for a given set of credit card details, allowing us to securely store a reference to the card and not the card details themselves. This token can be used later in the process to tie the card as a form of payment to the customer in Stripe. In order to use Elements in a Next.js application, the component that renders the form must be wrapped in the `<Elements>` component.

Because we're using a Custom flow, we can create a separate component that renders and handles the form logic and wrap it in `<Elements>` on the page, allowing us to combine the credit card entry form with our sign-in form.

### Unsafe metadata

Users in Clerk have a number of different [metadata](/docs/users/metadata#user-metadata) categories that are used for different purposes:

- public metadata - readable on the frontend, but writeable from the backend
- private metadata - accessible only from the backend
- unsafe metadata - readable and writeable from the front end, can also store pre-signup info about the user

Since unsafe metadata can be used to store information before the signup process is complete, we can take advantage of this to store information about the selected tier (a “product” in Stripe) and payment details provided in the custom form. When the user completes signup, the data stored locally in unsafe metadata will also be saved with the user on Clerks systems.

### Exploring the form code

After walking through all the moving parts required to solve this on the front end, let's take a look at the code.

To start, we have the `page.tsx` file which renders one of two forms based on if the signup attempt is verifying or not. If `verifying` is true, it means that the user has submitted the required details and the application is just waiting for them to add the OTP code they received via email. Take note that `SignUpForm` is wrapped in the Stripe `<Elements>` node, which is required to use Elements.

```tsx {{ filename: 'src/app/sign-up/[[...sign-up]]/page.tsx' }}
'use client'

import * as React from 'react'
import { useState } from 'react'
import SignUpForm from './SignUpForm'
import { loadStripe } from '@stripe/stripe-js'
import { Elements } from '@stripe/react-stripe-js'
import VerificationForm from './VerificationForm'

export default function Page() {
  const [verifying, setVerifying] = useState(false)
  const options = {
    appearance: {
      theme: 'stripe',
    },
  }
  const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string)

  // 👉 Render the verification form, meaning OTP email has been set
  if (verifying) {
    return <VerificationForm />
  }

  // 👉 Render the signup form by default
  return (
    <div className="mt-20 flex items-center justify-center">
      {/* @ts-ignore */}
      <Elements options={options} stripe={stripePromise}>
        <SignUpForm setVerifying={setVerifying} />
      </Elements>
    </div>
  )
}
```

Next let's explore the `SignUpForm.tsx` which is the form that accepts an email address, product selection, and credit card information. This component accepts a single prop of `setVerifying` which is only to signal to the page that the form has been submitted and the `VerificationForm` component can be shown instead.

When the form is submitted, three main things happen:

1. The card info is tokenized.
2. Clerk is notified that a signup is being attempted using the provided email address. This is where unsafe metadata is set as well.
3. The `setVerifying` prop is set to true, indicating to the parent that the `VerificationForm` component can now be rendered.

```tsx {{ filename: 'src/app/sign-up/[[...sign-up]]/SignUpForm.tsx' }}
'use client'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js'
import { useSignUp } from '@clerk/nextjs'
import { useState } from 'react'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'

type Props = {
  setVerifying: (val: boolean) => void
}

function SignUpForm({ setVerifying }: Props) {
  const { isLoaded, signUp } = useSignUp()
  const stripe = useStripe()
  const elements = useElements()
  const [priceId, setPriceId] = useState('')
  const [email, setEmail] = useState('')

  // 👉 Handles the sign-up process, including storing the card token and price id into the users metadata
  async function onSubmit() {
    if (!isLoaded && !signUp) return null

    try {
      if (!elements || !stripe) {
        return
      }

      let cardToken = ''
      const cardEl = elements?.getElement('card')
      if (cardEl) {
        const res = await stripe?.createToken(cardEl)
        cardToken = res?.token?.id || ''
      }

      await signUp.create({
        emailAddress: email,
        unsafeMetadata: {
          cardToken,
          priceId,
        },
      })

      // 👉 Start the verification - an email will be sent with an OTP code
      await signUp.prepareEmailAddressVerification()

      // 👉 Set verifying to true to display second form and capture the OTP code
      setVerifying(true)
    } catch (err) {
      // 👉 Something went wrong...
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <Card className="w-full sm:w-96">
        <CardHeader>
          <CardTitle>Create your account</CardTitle>
          <CardDescription>Welcome! Please fill in the details to get started.</CardDescription>
        </CardHeader>
        <CardContent className="grid gap-y-4">
          {/* // 👉  Email input */}
          <div>
            <Label htmlFor="emailAddress">Email address</Label>
            <Input
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              type="email"
              id="emailAddress"
              name="emailAddress"
              required
            />
          </div>

          {/* // 👉 Product selection radio group */}
          <div>
            <Label>Select tier</Label>
            <RadioGroup
              defaultValue="option-one"
              className="mt-2"
              value={priceId}
              onValueChange={(e) => setPriceId(e)}
            >
              <div className="flex items-center space-x-2">
                <RadioGroupItem value="price_1PG1OcF35z7flJq7p803vcEP" id="option-one" />
                <Label htmlFor="option-one">Pro</Label>
              </div>
              <div className="flex items-center space-x-2">
                <RadioGroupItem value="price_1PG1UwF35z7flJq7vRUrnOiv" id="option-two" />
                <Label htmlFor="option-two">Enterprise</Label>
              </div>
            </RadioGroup>
          </div>

          {/* // 👉 Use Stripe Elements to render the card capture form */}
          <Label>Payment details</Label>
          <div className="rounded border p-2">
            <CardElement />
          </div>
        </CardContent>

        <CardFooter>
          <div className="grid w-full gap-y-4">
            <Button type="submit" disabled={!isLoaded}>
              Sign up for trial
            </Button>
            <Button variant="link" size="sm" asChild>
              <Link href="/sign-in">Already have an account? Sign in</Link>
            </Button>
          </div>
        </CardFooter>
      </Card>
    </form>
  )
}

export default SignUpForm
```

Finally, we have the `VerificationForm.tsx` component, which simply accepts the code that was sent to the user's email address. The submit handler for this form sends the code to Clerk where it is checked to be valid. If valid, the user account will be created and the user will be redirected to `/after-sign-up` .

```tsx {{ filename: 'src/app/sign-up/[[...sign-up]]/VerificationForm.tsx' }}
import * as React from 'react'
import { useSignUp } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import { Button } from '@/components/ui/button'
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useState } from 'react'

function VerificationForm() {
  const { isLoaded, signUp, setActive } = useSignUp()
  const [code, setCode] = useState('')
  const router = useRouter()

  // 👉 Handles the verification process once the user has entered the validation code from email
  async function handleVerification(e: React.FormEvent) {
    e.preventDefault()
    if (!isLoaded && !signUp) return null

    try {
      // 👉 Use the code provided by the user and attempt verification
      const signInAttempt = await signUp.attemptEmailAddressVerification({
        code,
      })

      // 👉 If verification was completed, set the session to active
      // and redirect the user
      if (signInAttempt.status === 'complete') {
        await setActive({ session: signInAttempt.createdSessionId })
        router.push('/after-sign-up')
      } else {
        // 👉 If the status is not complete. User may need to complete further steps.
      }
    } catch (err) {
      // 👉 Something went wrong...
    }
  }

  return (
    <div className="mt-20 flex items-center justify-center">
      <form onSubmit={handleVerification}>
        <Card className="w-full sm:w-96">
          <CardHeader>
            <CardTitle>Create your account</CardTitle>
            <CardDescription>Welcome! Please fill in the details to get started.</CardDescription>
          </CardHeader>
          <CardContent className="grid gap-y-4">
            <div>
              <Label htmlFor="code">Enter your verification code</Label>
              <Input
                value={code}
                onChange={(e) => setCode(e.target.value)}
                id="code"
                name="code"
                required
              />
            </div>
          </CardContent>
          <CardFooter>
            <div className="grid w-full gap-y-4">
              <Button type="submit" disabled={!isLoaded}>
                Verify
              </Button>
            </div>
          </CardFooter>
        </Card>
      </form>
    </div>
  )
}

export default VerificationForm
```

## Registering the subscription in Stripe

Now that we've covered everything the user sees, let's break down what happens behind the scenes to make sure the user is successfully registered for the trial of their chosen tier.

### Clerk webhooks

We'll need a reliable way to signal that a user has been created and something needs to be done about it, and that's where webhooks come in.

Webhooks are HTTP requests that are automatically dispatched to an API endpoint of your choosing when an event happens in Clerk. One of these can be triggered when a user is created, using the `user.created` event. The dispatched request also contains various details about the user that was created, including the unsafe metadata. By configuring a webhook handler in our application to accept the request, we can read in the selected product and payment info, and create the subscription using the Stripe SDK.

![The Webhooks section of the Clerk dashboard](./dashboard-webhooks.png)

Using the `@brianmmdev/clerk-webhooks-handler` utility library, we can define functions that automatically validate the webhook signature and allow you to easily handle the payload, including pulling out the unsafe metadata that was set during the signup process.

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
import { createWebhooksHandler } from '@brianmmdev/clerk-webhooks-handler'
import { Stripe } from 'stripe'
import { clerkClient } from '@clerk/nextjs/server'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string)

const handler = createWebhooksHandler({
  onUserCreated: async (user) => {
    // 👉 Parse the unsafe_metadata from the user payload
    const { cardToken, priceId } = user.unsafe_metadata
    if (!cardToken || !priceId) {
      return
    }

    // 👉 Stripe operations will go here...
  },
})

export const POST = handler.POST
```

### Creating the Stripe entities

Creating a subscription in Stripe requires three separate entities:

- Customer
- Payment method
- Subscription

Since the card info was tokenized and stored in the user's unsafe metadata along with the selected product, we can take advantage of the info sent to our application to create these three entities and tie them to each other. The first thing is to create a payment method based on the tokenized card info:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
const pm = await stripe.paymentMethods.create({
  type: 'card',
  card: {
    token: cardToken as string,
  },
})
```

Next, we can use the captured email address to create a customer in Stripe and tie the payment method to them:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
const customer = await stripe.customers.create({
  email: user?.email_addresses[0].email_address,
  payment_method: pm.id,
})
```

Finally, we can create the subscription entity, attach it to the customer, set the payment method, AND set a trial period:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
const subscription = await stripe.subscriptions.create({
  customer: customer.id,
  default_payment_method: pm.id,
  trial_period_days: 14,
  items: [
    {
      price: priceId as string,
    },
  ],
})
```

### Syncing subscription state

Although the frontend and backend flows occur separately, we need a way to signal to the front end that processing has been completed on the backend. To do this, we can use metadata again (public metadata in this case) to set data from the Stripe operations to indicate that the process has been completed:

```tsx {{ filename: 'src/app/api/clerkhooks/route.ts' }}
await clerkClient.users.updateUser(user.id, {
  publicMetadata: {
    stripeCustomerId: customer.id,
    stripeSubscriptionId: subscription.id,
  },
})
```

On the front end, our redirect page actually just renders a loading indicator but also polls the user's info from Clerk to redirect them once that data is available. The following is the code that makes up the page for `/after-sign-up`, which is where the user was redirected after the OTP code was entered.

```tsx {{ filename: 'src/app/after-sign-up/page.tsx' }}
'use client'

import { Icons } from '@/components/ui/icons'
import { useUser } from '@clerk/nextjs'
import { useRouter } from 'next/navigation'
import React, { useEffect } from 'react'

async function sleep(ms: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

function AfterSignUp() {
  const router = useRouter()
  const { user } = useUser()

  // 👉 Poll the user data until a stripeSubscriptionId is available
  useEffect(() => {
    async function init() {
      while (!user?.publicMetadata?.stripeSubscriptionId) {
        await sleep(2000)
        await user?.reload()
      }
      // 👉 Once available, redirect to /dashboard
      router.push('/dashboard')
    }
    init()
  }, [])

  return (
    <div className="mt-20 flex items-center justify-center">
      <Icons.spinner className="size-8 animate-spin" />
    </div>
  )
}

export default AfterSignUp
```

## Putting it all together

As you can see there are quite a lot of moving parts that allow for this simple form to do so much. To put everything into context, let's look at the entire flow step by step:

![An actor diagram that explains how the workflow operates](./flow.jpg)

1. When the user submits the form, the card details are sent to Stripe to tokenize the card. That token, and the selected product, are stored as `unsafeMetadata`.
2. The app will signal to Clerk that a user is trying to sign up.
3. Clerk sends the user an OTP to their email.
4. The user enters the code into the application.
5. The app signals to Clerk that the user completed the signup and the account should be created.
6. The `user.created` webhook is triggered and the payload is sent to an API route in the application.
7. The webhook handler uses the Stripe SDK to create a payment method, customer, and subscription.
8. Once done, the user record is updated from Next and the user is allowed to proceed

## Conclusion

Custom flows in Clerk open a world of opportunities, allowing you to create your own forms to handle sign-up and sign-in. By taking advantage of webhooks and using the various types of metadata, you can also build in complex and advanced automation, while creating a seamless experience for your users.

If you enjoyed this, share it on X and let us know by tagging [@clerk](https://x.com/clerk)!