Clerk
This guide will walk you through integrating Clerk authentication with your SpacetimeDB React application. You will configure a Clerk application, obtain a JWT from Clerk, and pass it to your SpacetimeDB connection as the authentication token.
Prerequisites
We assume you have the following prerequisites in place:
- A working SpacetimeDB project. Follow our React Quickstart Guide if you need help setting this up.
- A Clerk account.
Getting started
Install the Clerk React SDK into your React application.
- NPM
- Yarn
- PNPM
- Bun
npm add @clerk/clerk-reactyarn add @clerk/clerk-reactpnpm add @clerk/clerk-reactbun add @clerk/clerk-react- Head to the Clerk Dashboard and create (or select) an application.
- In your app settings, locate your Publishable key and save it somewhere handy (you'll need it in
main.tsx). - Ensure your local development URL is allowed in Clerk (for example
http://localhost:5173if you are using Vite). - You will also need a JWT issued by Clerk to send to SpacetimeDB. Clerk can mint JWTs via session tokens; in the next steps we will retrieve a token from the active session in the browser.
Create a component that:
- Ensures the user is signed in (and redirects them to Clerk's sign-in UI when they are not).
- Retrieves a session token (JWT) from Clerk once authenticated.
- Exposes that token to your app via React context, similar to the
AutoLoginpattern in the Auth0 tutorial.
Create a file named ClerkTokenProvider.tsx.
import React, {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { useAuth, RedirectToSignIn } from '@clerk/clerk-react';
const TokenContext = createContext<string | undefined>(undefined);
export function useClerkToken() {
const token = useContext(TokenContext);
if (!token) {
throw new Error('useClerkToken must be used within a ClerkTokenProvider');
}
return token;
}
/**
* ClerkTokenProvider:
* - If signed out: renders Clerk's redirect component.
* - If signed in: loads a Clerk session token (JWT) and provides it via context.
*
* Note:
* - getToken() returns a token suitable for sending to your backend. If you have
* configured a specific JWT template in Clerk, pass its name via
* getToken({ template: "<YOUR_TEMPLATE_NAME>" }).
*/
export function ClerkTokenProvider({
children,
}: {
children: React.ReactNode;
}) {
const { isLoaded, isSignedIn, getToken } = useAuth();
const [token, setToken] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
async function run() {
if (!isLoaded) return;
// IMPORTANT: if signed out, clear any cached token
if (!isSignedIn) {
if (!cancelled) setToken(null);
return;
}
try {
// If you use a Clerk JWT template, use:
// const t = await getToken({ template: "<YOUR_TEMPLATE_NAME>" });
const t = await getToken();
if (!t) {
throw new Error('Clerk returned no session token.');
}
if (!cancelled) setToken(t);
} catch (e) {
if (!cancelled) setError(e as Error);
}
}
run();
return () => {
cancelled = true;
};
}, [isLoaded, isSignedIn, getToken]);
const value = useMemo<string | undefined>(() => token ?? undefined, [token]);
if (error) {
return (
<div>
<p>Authentication error</p>
<pre>{error.message}</pre>
</div>
);
}
if (!isLoaded) {
return <p>Loading...</p>;
}
if (!isSignedIn) {
// Sends the user to Clerk sign-in. After sign-in, they return to the app.
return <RedirectToSignIn />;
}
if (!token) {
return <p>Loading...</p>;
}
return (
<TokenContext.Provider value={value}>{children}</TokenContext.Provider>
);
}Wrap your app with ClerkProvider so Clerk can manage authentication state.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { ClerkProvider } from '@clerk/clerk-react';
import App from './App.tsx';
import { ClerkTokenProvider } from './ClerkTokenProvider.tsx';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<ClerkProvider publishableKey="<YOUR_CLERK_PUBLISHABLE_KEY>">
<ClerkTokenProvider>
<App />
</ClerkTokenProvider>
</ClerkProvider>
</StrictMode>
);Update your App.tsx file to:
- Read the Clerk token via
useClerkToken. - Pass it to the
DbConnectionbuilder using.withToken(...).
This mirrors the Auth0 flow: SpacetimeDB receives a bearer token (JWT) and can validate it server-side.
import { useMemo } from 'react';
import { Identity } from 'spacetimedb';
import { SpacetimeDBProvider } from 'spacetimedb/react';
import { DbConnection, ErrorContext } from './module_bindings';
import { useClerkToken } from './ClerkTokenProvider';
const onConnect = (_conn: DbConnection, identity: Identity) => {
console.log(
'Connected to SpacetimeDB with identity:',
identity.toHexString()
);
};
const onDisconnect = () => {
console.log('Disconnected from SpacetimeDB');
};
const onConnectError = (_ctx: ErrorContext, err: Error) => {
console.log('Error connecting to SpacetimeDB:', err);
};
export default function App() {
const token = useClerkToken();
const connectionBuilder = useMemo(() => {
return DbConnection.builder()
.withUri('<YOUR SPACETIMEDB URL>')
.withModuleName('<YOUR SPACETIMEDB MODULE NAME>')
.withToken(token)
.onConnect(onConnect)
.onDisconnect(onDisconnect)
.onConnectError(onConnectError);
}, [token]);
return (
<SpacetimeDBProvider connectionBuilder={connectionBuilder}>
<div>
<h1>SpacetimeDB React App</h1>
<p>
You can now use SpacetimeDB in your app with Clerk authentication!
</p>
</div>
</SpacetimeDBProvider>
);
}If you want quick UI controls, Clerk provides ready-made components such as UserButton.
import { UserButton } from '@clerk/clerk-react';
export function Header() {
return (
<header
style={{ display: 'flex', justifyContent: 'flex-end', padding: 12 }}
>
<UserButton />
</header>
);
}You are now set up to use Clerk authentication in your React application. When users access your app, they will be redirected to Clerk for sign-in, a session token (JWT) will be retrieved in the browser, and that token will be used to authenticate your SpacetimeDB connection.
If you are using Clerk JWT templates (recommended for controlling claims/audience/issuer), update the token retrieval line to:
await getToken({ template: '<YOUR_TEMPLATE_NAME>' });
and ensure your SpacetimeDB authentication layer validates the corresponding issuer and signing keys.