Clerk
Overview
Overview
Clerk authenticates users, manages session tokens, and provides user management functionality that can be used in combination with the authorization logic available in Supabase through PostgreSQL Row Level Security (RLS) policies.
Documentation
This guide explains how to connect your Supabase database with Clerk.
Clerk is a user management platform that provides beautifully designed, drop-in UI components to quickly add authentication capabilities to your application. Clerk supports numerous sign-in strategies such as social providers, email links, and passkeys, as well as a suite of B2B SaaS tools and APIs to build your own authentication flows.
The Clerk integration uses the authorization logic available in Supabase through PostgreSQL Row Level Security (RLS) policies.
This guide assumes you have a Supabase account and database project already set up.
If you don't have a Clerk account, you can create an account for free.
How the integration works
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 and inserted.
- Use the Clerk Supabase integration helper in your code to authenticate with Supabase and execute queries.
1: Create a function to check the incoming user ID
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.
-
In the sidebar of your Supabase dashboard, navigate to Database > Functions.
-
Select Create a new function.
-
In the Add a new function sheet, make the following changes:
- Set Name of function to
requesting_user_id
. - Set Return type to
text
. - Toggle Show advanced settings on.
- Set Language to
sql
. - Populate the Definition with the following sql:
_10SELECT NULLIF(_10current_setting('request.jwt.claims', true)::json->>'sub',_10''_10)::text;- Select Confirm.
- Set Name of function to
2: Create a user_id
column
💡 The following steps will need to be performed on any tables you wish to secure.
Next, you’ll create a user_id
column in the table you wish to secure. This column will be used in the RLS policies to only return or modify records scoped to the user's account and it will use the requesting_user_id()
function you just created as its default value.
- Navigate to the sidebar on the left and select Table Editor.
- Select the table you wish to secure.
- In the table, select the + column to add a new column.
- Set the Name to user_id.
- Set Type to text.
- Set Default Value to
requesting_user_id()
. - Select Save to create the column.
3: Enable RLS on your table and create the policies
To enable RLS on your table:
- In the top bar above the table, select RLS disabled and then Enable RLS for this table.
- In the modal that appears, select Enable RLS.
- Select the Add RLS policy button (which has replaced RLS disabled).
Create two policies: one to enforce that the data returned has a user_id
value that matches the requestor, and another to automatically insert records with the ID of the requestor.
- Select Create policy to create the
SELECT
policy:-
Provide a name for the policy.
-
For Policy Command, select SELECT.
-
For Target roles, select authenticated.
-
Replace the "-- Provide a SQL expression for the using statement" with the following:
_10requesting_user_id() = user_id -
Select Save policy.
-
- Select Create policy to create the
INSERT
policy:-
Provide a name for the policy.
-
For Policy Command, select INSERT.
-
For Target roles, select authenticated.
-
Replace the "-- Provide a SQL expression for the with check statement" with the following:
_10requesting_user_id() = user_id -
Select Save policy.
-
4: Get your Supabase JWT secret key
To give users access to your data, Supabase's API requires an authentication token. Your Clerk project can generate these authentication tokens, but it needs your Supabase project's JWT secret key first.
To find the JWT secret key:
- In the sidebar of your Supabase dashboard, navigate to Project Settings > API.
- Under the JWT Settings section, save the value in the JWT Secret field somewhere secure. This value will be used in the next step.
5: Create a Supabase JWT template
Clerk's JWT templates allow you to generate a new valid Supabase authentication token for each signed-in user. These tokens allow authenticated users to access your data with Supabase's API.
To create a Clerk JWT template for Supabase:
- Navigate to the Clerk Dashboard.
- In the navigation sidebar, select JWT Templates.
- Select the New template button, then select Supabase from the list of options.
- 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 the Supabase docs. - Under Signing key, add the value of your Supabase JWT secret key from the previous step.
- You can leave all other fields at their default settings or customize them to your needs. See the Clerk JWT template guide to learn more about these settings.
- Select Save from the notification bubble to complete setup.
- The value of the Name field will be required when using the template in your code. For this tutorial, name it
6: Configure your application
The next step is to configure your client. Supabase provides an official JavaScript/TypeScript client library and there are libraries in other languages built by the community.
This guide will use a Next.js project with the JS client as an example, but the mechanism of setting the authentication token should be similar to other libraries and frameworks.
Set up Clerk
To configure Clerk in your Next.js application, follow the Next.js Quickstart available in the Clerk docs. The linked guide will walk you through the basics of configuring Clerk by adding sign-up and sign-in functionality to your application.
Configure the Supabase client
Next, the Supabase client used to query and modify data in your Supabase database needs to be modified to use the Clerk token as part of the request headers. This can be done by customizing the fetch
that is used by Supabase like so:
_40import { useSession, useUser } from '@clerk/nextjs'_40import { createClient } from '@supabase/supabase-js'_40_40export default function Home() {_40 // The `useSession()` hook will be used to get the Clerk `session` object_40 const { session } = useSession()_40 _40 // Create a custom supabase client that injects the Clerk Supabase token into the request headers_40 function createClerkSupabaseClient() {_40 return createClient(_40 process.env.NEXT_PUBLIC_SUPABASE_URL!,_40 process.env.NEXT_PUBLIC_SUPABASE_KEY!,_40 {_40 global: {_40 // Get the custom Supabase token from Clerk_40 fetch: async (url, options = {}) => {_40 // The Clerk `session` object has the getToken() method _40 const clerkToken = await session?.getToken({_40 // Pass the name of the JWT template you created in the Clerk Dashboard_40 // For this tutorial, you named it 'supabase'_40 template: 'supabase',_40 })_40 _40 // Insert the Clerk Supabase token into the headers_40 const headers = new Headers(options?.headers)_40 headers.set('Authorization', `Bearer ${clerkToken}`)_40 _40 // Call the default fetch_40 return fetch(url, {_40 ...options,_40 headers,_40 })_40 },_40 },_40 },_40 )_40 }_40 _40 //... The rest of the code is removed for brevity_40}
Then the client can be created and used throughout the application:
_10const client = createClerkSupabaseClient()
If you have previously followed the Supabase Next.js guide, you’d replace any use of the createClient
function with the one above.
Example
The following example demonstrates how this technique is used in a to-do application that queries data from and inserts data into a tasks
table, which will be secured with the RLS policies created in previous steps:
_93'use client'_93import { useEffect, useState } from 'react'_93import { useSession, useUser } from '@clerk/nextjs'_93import { createClient } from '@supabase/supabase-js'_93_93export default function Home() {_93 const [tasks, setTasks] = useState<any[]>([])_93 const [loading, setLoading] = useState(true)_93 const [name, setName] = useState('')_93 // The `useUser()` hook will be used to ensure that Clerk has loaded data about the logged in user_93 const { user } = useUser()_93 // The `useSession()` hook will be used to get the Clerk `session` object_93 const { session } = useSession()_93_93 // Create a custom supabase client that injects the Clerk Supabase token into the request headers_93 function createClerkSupabaseClient() {_93 return createClient(_93 process.env.NEXT_PUBLIC_SUPABASE_URL!,_93 process.env.NEXT_PUBLIC_SUPABASE_KEY!,_93 {_93 global: {_93 // Get the custom Supabase token from Clerk_93 fetch: async (url, options = {}) => {_93 const clerkToken = await session?.getToken({_93 template: 'supabase',_93 })_93_93 // Insert the Clerk Supabase token into the headers_93 const headers = new Headers(options?.headers)_93 headers.set('Authorization', `Bearer ${clerkToken}`)_93_93 // Call the default fetch_93 return fetch(url, {_93 ...options,_93 headers,_93 })_93 },_93 },_93 },_93 )_93 }_93_93 // Create a `client` object for accessing Supabase data using the Clerk token_93 const client = createClerkSupabaseClient()_93_93 // This `useEffect` will wait for the `user` object to be loaded before requesting_93 // the tasks for the logged in user_93 useEffect(() => {_93 if (!user) return_93_93 async function loadTasks() {_93 setLoading(true)_93 const { data, error } = await client.from('tasks').select()_93 if (!error) setTasks(data)_93 setLoading(false)_93 }_93_93 loadTasks()_93 }, [user])_93_93 async function createTask(e: React.FormEvent<HTMLFormElement>) {_93 e.preventDefault()_93 // Insert task into the "tasks" database_93 await client.from('tasks').insert({_93 name,_93 })_93 window.location.reload()_93 }_93_93 return (_93 <div>_93 <h1>Tasks</h1>_93_93 {loading && <p>Loading...</p>}_93_93 {!loading && tasks.length > 0 && tasks.map((task: any) => <p>{task.name}</p>)}_93_93 {!loading && tasks.length === 0 && <p>No tasks found</p>}_93_93 <form onSubmit={createTask}>_93 <input_93 autoFocus_93 type="text"_93 name="name"_93 placeholder="Enter new task"_93 onChange={(e) => setName(e.target.value)}_93 value={name}_93 />_93 <button type="submit">Add</button>_93 </form>_93 </div>_93 )_93}