In this guide, weâll walk through integrating Clerk with Supabase step-by-step. We start with a basic, no-auth Next.js app and gradually add authentication and secure, user-specific data storage using Clerk for auth and Supabase for data. Youâll end up with a diagram editor where each user can save and retrieve their own diagrams.
What Youâll Learn
- Set up Clerk authentication in a Next.js app.
- Connect Clerk and Supabase (using the updated 2025 integration).
- Configure Row-Level Security (RLS) for user-specific access.
- See it all come together in a diagram editor where authenticated users save and load their own charts.
We begin with a simple âno-authâ editor (users can draw and preview Mermaid diagrams, but nothing is saved). Then we layer on Clerk and Supabase. Clerk provides sign-in flows, session tokens, and user IDs. Supabase holds the diagrams and enforces per-user data access via RLS.
Finally, weâll introduce SuperTokens as an alternative in Part II, showing how it can offer more control (especially for session management) while still working with Supabase. Clerk is great out of the box if you want a quick, managed solution, but SuperTokens is worth a look if you need advanced extensibility or self-hosting.
How Clerk and Supabase Work Together
In this integration, Clerk and Supabase take on two distinct roles:
-
Clerk handles authentication and user identity
It provides everything needed to securely sign users in like login flows, session management, and user IDs. Each user gets a unique userId (thesub
claim in Clerkâs JWT) that stays the same across sessions. -
Supabase handles data storage and access control
We use Supabase to store content (in our example, each userâs saved Mermaid chart) and to enforce fine-grained access rules. Supabaseâs Row-Level Security (RLS) policies make sure users only see and modify their own data, even though everyoneâs data lives in the same database.
How the Integration Flow Works
- User signs in with Clerk
Clerk verifies their credentials and returns a JWT (JSON Web Token) containing the userâs unique ID (sub
claim). - Your app sends the JWT to Supabase
You configure the Supabase client to include Clerkâs token on each request. Supabase sees the token and uses it to identify the user. - Supabase enforces RLS policies
For example, if we have a charts table with a user_id column, RLS policies can be written like:-- Only allow a user to see rows where user_id matches their ID from Clerk policy "Users can access their own charts" on charts for all using (auth.uid() = user_id);
RLS ensures each user sees only their own data, even in a shared database.
This works because
- Authentication is fully managed by Clerk out of the box.
- User data is stored in Supabase and automatically tied to individual user identities.
- Row-Level Security (RLS) policies enforce strict access control, preventing users from viewing or altering data that isnât theirs.
Clerk and Supabase Integration: Project Overview
đ View the full demo on GitHub - this Next.js example shows Clerk + Supabase in action.
App Features:
- MermaidJS Editor. A live editor to write and render Mermaid diagrams.
- Clerk Authentication. Sign-up/sign-in UI components and session handling.
- Supabase Storage. Save and retrieve diagrams in a secure database.
- Row-Level Security. Supabase policies ensure each userâs data is private.
Initial State: The app starts as a simple, stateless editor. Users can draw and preview diagrams, but nothing is saved â as soon as the page reloads, the content is lost.
With Clerk + Supabase: After adding Clerk, users must sign in. Then, with Supabase connected, each user can save their diagram, and it will reappear the next time they log in. Each saved chart is tagged with that userâs Clerk user ID, and RLS guarantees no user can see anotherâs charts. This turns the tool into a full multi-user app with persistent, private data.
What Youâll Build
Weâll enhance the basic editor by adding:
- Clerk Authentication: Add Clerk to the Next.js app. Users can sign up and sign in (or sign out). After login, they see personalized content.
- Supabase Integration: Configure Supabase so that each signed-in user can save their diagrams to the database. Supabase will use the Clerk user ID (from the JWT) to scope the data. RLS policies enforce that users only access their own data.
By the end, youâll have a working diagram editor where each user signs in, sees their own saved charts, and nothing else.
Setting Up Clerk
Letâs start by integrating Clerk for authentication. Clerk provides a powerful auth-as-a-service with pre-built React components, so most of this is straightforward setup.
1. Sign Up for Clerk
Go to Clerkâs website and create an account. After signing up, log in to the Clerk dashboard, where you can manage your applications and auth settings.
2. Create a New Clerk Application
In your Clerk dashboard:
- Click Create Application and give it a name (e.g. Mermaid Visualizer).
- Choose your auth methods. (For simplicity, weâll enable only the email authentication method in this demo.)
- Clerk will then guide you to install its SDK and configure your app.
3. Install the Clerk SDK
In your project directory, install Clerkâs Next.js SDK:
# If you're using npm
npm install @clerk/nextjs
# If you're using yarn
yarn add @clerk/nextjs
# If you're using pnpm
pnpm add @clerk/nextjs
This SDK gives you React components (like SignInButton
) and server helpers for middleware.
4. Set your Clerk API keys
Add these keys to your .env
file, or create the file if it doesnât exist.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your-clerk-key
CLERK_SECRET_KEY=your-clerk-secret
5. Update middleware.ts
Update your middleware file, or create one at the root of your project, or the src/ directory if youâre using a src/ directory structure.
The clerkMiddleware
helper enables authentication and is where youâll configure your protected routes.
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};
6. Add ClerkProvider and the SignUp/SignIn Buttons
To enable Clerk throughout your app, youâll use the ClerkProvider in your layout.tsx file.
Hereâs what to do:
- Import the necessary components from
@clerk/nextjs
. - Wrap your app with
ClerkProvider
to make Clerkâs features available globally. - Use
SignedOut
to wrap theSignInButton
andSignUpButton
. These will only appear when the user is signed out. - Use
SignedIn
to wrap both theUserButton
and your appâs content. These will only show when the user is signed in.
import { type Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
// đ Clerk components for authentication flowsimport { ClerkProvider, // Wraps your app to provide authentication context SignInButton, // Renders a sign-in button that opens Clerk's sign-in modal SignUpButton, // Renders a sign-up button that opens Clerk's sign-up modal SignedIn, // Conditionally renders children *only* if the user is signed in SignedOut, // Conditionally renders children *only* if the user is signed out UserButton, // Displays the user's avatar with a dropdown for account management} from '@clerk/nextjs'
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
// â
ClerkProvider wraps the entire app, enabling access to auth state and Clerk components
<ClerkProvider> <html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`} >
<header className="p-5">
{/* đ Show these only when the user is signed out */} <SignedOut> <SignInButton /> {/* Opens a modal for users to sign in */} <SignUpButton /> {/* Opens a modal for users to create an account */} </SignedOut>
{/* đ Show these only when the user is signed in */} <SignedIn> <UserButton /> {/* Shows user profile options like sign out */} {children} {/* Show the app content only to signed-in users */} </SignedIn>
</header>
</body>
</html>
</ClerkProvider> );
}
đ And thatâs it! Youâve now set up Clerk for user authentication in your app. Users can sign in, sign up, and see personalized content based on their auth state.
7. Run Your Project and Get Your First User
Start your dev server:
# If you're using npm
npm run dev
# If you're using yarn
yarn dev
# If you're using pnpm
pnpm dev
Once itâs running, head to http://localhost:3000
in your browser. You should see your app with sign-in and sign-up options. Go ahead and registerâthis will create your first authenticated user.
Next up, weâll connect Supabase and set it up to store user-specific data like saved Mermaid charts.
Setting Up Supabase with Clerk
To integrate Supabase with your Next.js project and secure user-specific data using Clerk authentication, follow these steps:
1. Create a Supabase Project
- Visit supabase.com and sign up for a free account.
- Once inside the dashboard, click âNew Projectâ.
- Choose a Project Name (e.g. Mermaid Visualizer), select the closest region for your users, and set a strong password (you can use the âGenerate Passwordâ feature).
- Save this password in your password manager.
2. Install Supabase Client
Install the Supabase JavaScript client in your project:
npm install @supabase/supabase-js
3. Connect Clerk to Supabase (New 2025 Flow)
As of April 2025, Supabase has updated its integration method with Clerk from JWT templates to native third-party support, which simplifies setup and improves security.
- Configure Clerk for Supabase: In your Clerk dashboard, navigate to API Keys and enable Supabase compatibility.
- Add Clerk as a Third-Party Auth Provider in Supabase: In Supabase, go to Authentication > Providers > External OAuth and select Clerk. Youâll need to input the Issuer URL and JWKS endpoint from Clerk (Clerkâs docs or connect page will show you what values to use).
4. Set Up Row-Level Security (RLS) with Clerk
By default, Supabase uses its own auth.uid()
method, which doesnât work when using Clerk. To fix that, weâll extract the authenticated userâs ID from the Clerk-provided JWT.
Create a Helper Function for Clerk IDs
In Supabaseâs SQL Editor, paste the following:
CREATE OR REPLACE FUNCTION requesting_user_id()
RETURNS TEXT AS $$
SELECT NULLIF(
current_setting('request.jwt.claims', true)::json->>'sub',
''
)::text;
$$ LANGUAGE SQL STABLE;
This function safely retrieves the authenticated Clerk user ID from the incoming JWT token.
Create the charts
Table
Run the following SQL to create a table and enable RLS with Clerk support:
create table if not exists charts (
id UUID default gen_random_uuid() primary key,
content text not null,
user_id text not null,
created_at timestamptz default now()
);
Add RLS Policies (the Right Way for Clerk)
alter table charts enable row level security;
create policy "Users can insert their own charts"
on charts
for insert
with check (
user_id = current_setting('request.jwt.claims', true)::json->>'sub'
);
create policy "Users can view their own charts"
on charts
for select
using (
user_id = current_setting('request.jwt.claims', true)::json->>'sub'
);
Common Pitfall: auth.uid()
Returns NULL
If you previously used this:
USING (auth.uid()::text = user_id)
âŚit wonât work with Clerk. Why?
auth.uid()
is only available if youâre using Supabase Auth, which weâre not.- Clerk user IDs are strings like
"user_2w2a6PJC4T4BfXDsg72AQsLNEyU"
, which are not UUIDs. - This mismatch causes errors like:
invalid input syntax for type uuid: "user_..."
The Fix: Use this pattern instead:
current_setting('request.jwt.claims', true)::json->>'sub'
This safely extracts the sub field (i.e. the Clerk user ID) from the JWT, allowing you to scope data per user, securely and correctly.
Integrating Clerk and Supabase in Next.js
Clerk and Supabase work together seamlessly to manage authentication and user-specific data in your Next.js app. Hereâs how the integration functions:
- Authentication with Clerk: When a user logs in, Clerk authenticates them and generates a unique user ID and session token.
- Data Storage with Supabase: That user ID is used to store and retrieve data (e.g. Mermaid charts) in the Supabase database, scoped to the logged-in user.
Letâs break down how to implement this integration in code:
Middleware for Clerk Authentication
The middleware.ts
file ensures that all routes requiring authentication are protected by Clerk. It intercepts requests and verifies the session before allowing access:
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js|png|svg)).*)',
'/(api|trpc)(.*)',
],
};
Supabase Provider with Clerk Session Token
In order to securely access Supabase on behalf of the authenticated user, we initialize a Supabase client using the session token provided by Clerk:
import { createClient } from "@supabase/supabase-js";
import { useSession } from "@clerk/nextjs";
export default function SupabaseProvider({ children }: { children: React.ReactNode }) {
const { session } = useSession();
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {
accessToken: () => session?.getToken()
}
);
return <Context.Provider value={{ supabase }}>{children}</Context.Provider>;
}
Code Walkthrough
You can explore the full codebase in the GitHub repository.
Saving Charts
The handleSave
function stores a Mermaid chart in your Supabase database, associating it with the currently logged-in user:
const handleSave = async () => {
if (!supabase || !user) return;
const { data, error } = await supabase
.from('charts')
.insert({ content: code, user_id: user.id });
if (error) console.error('Error saving chart:', error);
};
Fetching Charts
The fetchCharts
function retrieves all charts for the authenticated user, ordered by creation time:
const fetchCharts = async () => {
const { data, error } = await supabase
.from('charts')
.select('content')
.order('created_at', { ascending: false });
if (data) setSavedCharts(data.map((item) => item.content));
};
Challenges and Solutions
- Session Management:
Challenge: Ensure Clerkâs session is available before initializing Supabase.
Solution: Use useEffect to wait for the session before creating the Supabase client. - Error Handling:
Challenge: Errors when saving or fetching data can disrupt the UX.
Solution: Implement error checks and show clear feedback to the user.
What About Session Security? (The Hidden Risk No One Talks About)
So far, Clerk + Supabase covers authentication (who the user is) and data access rules. But one aspect often overlooked is session security. Common risks include:
- Stolen Tokens: If an attacker somehow obtains a userâs refresh token or access token (via XSS, device theft, etc.), they can impersonate that user until the token expires.
- Replay Attacks: Without proper handling, someone could reuse an old valid refresh token to continue a session indefinitely.
- Session Hijacking: If your system only allows a single session per user, an attacker who logs in on one device can knock out other sessions, and vice versa.
Clerk and Supabase handle basic sessions well (Clerk rotates refresh tokens, etc.), but these advanced threats can still be concerns. This is where SuperTokens shines. Itâs designed with advanced session security features.
How SuperTokens Fixes This Problem
SuperTokens puts session security at the forefront. Compared to Clerk or vanilla Supabase Auth, SuperTokens offers:
- Self-hosted & fully customizable: You can self-host SuperTokens (or use the cloud hosting option). Because itâs open source, you have full control. This means you arenât locked into a vendor.
- Advanced session management: Out of the box, SuperTokens uses short-lived access tokens and rotating refresh tokens. Each time a refresh token is used, a new one is issued and the old one is invalidated. This one-time-use refresh scheme means a stolen refresh token is useless after first use.
- Token theft detection & rotation: SuperTokens can detect if a refresh token is being reused (an anomaly indicating theft) and revoke all sessions for that user immediately.
- Granular session revocation: You can revoke individual device sessions. For instance, SuperTokens provides backend functions like
Session.revokeSession(sessionHandle)
orSession.revokeAllSessionsForUser(userId)
, letting you target a specific session or wipe all of a userâs sessions.
These features directly mitigate the risks above:
- Stolen token â short expiry & rotation: Even if an access token is stolen, it will expire quickly. If a refresh token is stolen, SuperTokens will notice its reuse and invalidate it, limiting damage to minutes.
- Replay attacks â token rotation: Reusing a refresh token triggers detection. The userâs session can be revoked immediately, so replayed tokens get caught.
- Hijacking â per-device logout: Users can log out of one device without logging out everywhere, since sessions are separate. An attacker in one session can be locked out while others stay intact.
By design, SuperTokens âbelongs in the backendâ of your auth flow. You can still use Clerkâs or your own front end if you wish, but swap in SuperTokens on the server to handle sessions. In short, SuperTokens fixes session management in a way many providers donât.
How SuperTokens Handles Session Security (In Practice)
Hereâs a quick comparison of how these threats are handled:
Threat | Risk Without Protection | SuperTokens Defense |
---|---|---|
Token Theft | Attacker reuses a stolen refresh token indefinitely. | Short-lived access tokens + rotating refresh tokens. If a refresh token is stolen and reused, SuperTokens detects the anomaly and revokes it. |
Session Hijacking | Logging out all devices if a user is compromised on one. | Per-session tokens: SuperTokens lets you revoke a single session handle (device) or all sessions for a user, avoiding a blanket logout. |
Replay Attacks | A stolen refresh token could be saved and replayed. | Refresh tokens are one-time-use. Reuse triggers detection; old tokens are invalidated. |
Learn more about how SuperTokens handles these threats.
Clerk vs. Supabase vs. SuperTokens
Feature | Clerk đ§° | Supabase đď¸ | SuperTokens đĄď¸ |
---|---|---|---|
Pre-Built UI Components | â Yes | â No | â Yes (optional) |
Custom Auth Flows | â ď¸ Limited | â ď¸ Limited | â Full flexibility |
Session Management | â ď¸ Basic | â ď¸ Basic | â Advanced |
Self-Hosting | â No | â Yes (via CLI) | â Yes |
Token Theft Detection | â No | â No | â Yes |
Fine-Grained Session Revocation | â No | â No | â Yes |
- Pre-Built UI: Clerk and SuperTokens both offer ready-made login/signup UI components. Supabase itself does not include UI (itâs just a backend service). For example, SuperTokensâ docs explicitly mention using their pre-built UI components (or you can choose a custom UI).
- Custom Flows: Clerk allows some configuration, but SuperTokens lets you override everything. You can inject your own logic into any part of the auth flow.
- Session Security: Clerk and Supabase provide basic session handling, but SuperTokens is designed for security. As discussed above, it offers rotating tokens, theft detection, and per-session revocation.
- Self-Hosting: Supabase is open-source so you can self-host it via the Supabase CLI or Cloud.
In short, SuperTokens stands out for its flexibility and security. It gives devs full control over auth logic and sessions without sacrificing convenience (it also has pre-built UIs and a simple integration).
Custom Auth Flows with SuperTokens
SuperTokens lets you override every piece of the logicâperfect for apps with unique flows or compliance requirements.
Example: Add custom sign-in behavior:
override: {
functions: (original) => ({
...original,
signInUp: async (input) => {
// Custom logic here
}
})
}
You can also build your own UI, or start with theirs and customize as needed.
Should You Use Clerk, SuperTokens, or Both?
Thereâs no one-size-fits-all answer, but here are some guidelines:
- Choose Clerk if you want a fully managed auth solution with beautiful, drop-in UI components and minimal setup. Clerk covers most common use cases out of the box.
- Choose SuperTokens if you need ultimate control over auth and sessions. For example, if you must self-host, enforce custom token lifetimes, or build a very custom workflow, SuperTokens gives you that flexibility.
- Use Both in a hybrid approach. You might love Clerkâs React components and user experience, but still want SuperTokensâ session security. You can start with Clerk handling UI and signup, and then integrate SuperTokens behind the scenes for session issuance (or vice versa). SuperTokens even provides migration guides from other systems, so you can gradually swap parts out.
Because SuperTokens is open-source, you can try it risk-free alongside Clerk. For example, you could use Clerk for login/signup UI, but configure Supabase (or your backend) to accept SuperTokensâ tokens for session validation, leveraging SuperTokensâ defenses.
Migrating from Clerk to SuperTokens
If you want to move from Clerk to SuperTokens, you donât need to rip everything out. You can migrate gradually:
- Start by having SuperTokens manage the session (tokens) while still using Clerkâs front end.
- Swap out Clerkâs session cookie with SuperTokensâ cookies under the hood.
- Replace one signup/signin flow at a time with SuperTokens recipes.
SuperTokens provides guides on migrating from other auth systems, making the process smoother.
Clerk + Supabase Is a Great Start, But SuperTokens Takes You Further
To summarize, the Clerk + Supabase integration we built gives you:
- A complete app with secure user sign-in.
- Personalized data storage (each user only sees their charts).
- Row-Level Security enforcing data isolation.
This is elegant and suitable for many projects. Clerk handles auth beautifully, and Supabase handles data. But if your project has higher security needs or unusual requirements, SuperTokens can take you further. With SuperTokens, you can finely tune session lifetimes, detect stolen tokens, revoke individual sessions, and even self-host everything.
In Part II of this series, weâll swap Clerk out for SuperTokens in our app and show how to achieve similar functionality (login UI, user-specific data, RLS) with maximum control over sessions.
Regardless of which tools you choose, you now have a solid blueprint: Clerk (or another provider) for authentication, Supabase for data storage, and well-configured RLS to tie it all together. By adding SuperTokens into the mix, you gain an extra layer of security and flexibility.