Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions docs/docs/00200-core-concepts/00500-authentication/00300-Clerk.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import React, {
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useAuth, RedirectToSignIn } from '@clerk/clerk-react';
Expand All @@ -95,12 +96,17 @@ export function useClerkToken() {
/**
* ClerkTokenProvider:
* - If signed out: renders Clerk's redirect component.
* - If signed in: loads a Clerk session token (JWT) and provides it via context.
* - If signed in: loads the initial 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>" }).
* - The token is intentionally captured once per signed-in session. Clerk may
* refresh its session token in the background, but replacing this context
* value would rebuild the SpacetimeDB connection. Keep the existing
* connection open until the user signs out or you intentionally reconnect.
*/
export function ClerkTokenProvider({
children,
Expand All @@ -111,6 +117,7 @@ export function ClerkTokenProvider({

const [token, setToken] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
const tokenRef = useRef<string | null>(null);

useEffect(() => {
let cancelled = false;
Expand All @@ -120,7 +127,15 @@ export function ClerkTokenProvider({

// IMPORTANT: if signed out, clear any cached token
if (!isSignedIn) {
if (!cancelled) setToken(null);
if (!cancelled) {
tokenRef.current = null;
setToken(null);
}
return;
}

// Keep the token stable for the lifetime of this signed-in connection.
if (tokenRef.current) {
return;
}

Expand All @@ -133,7 +148,10 @@ export function ClerkTokenProvider({
throw new Error('Clerk returned no session token.');
}

if (!cancelled) setToken(t);
if (!cancelled) {
tokenRef.current = t;
setToken(t);
}
} catch (e) {
if (!cancelled) setError(e as Error);
}
Expand Down Expand Up @@ -215,6 +233,7 @@ Update your `App.tsx` file to:
2. Pass it to the `DbConnection` builder using `.withToken(...)`.

This mirrors the Auth0 flow: SpacetimeDB receives a bearer token (JWT) and can validate it server-side.
The token returned by `useClerkToken` stays stable while the user remains signed in, so Clerk's background token refreshes do not rebuild the `SpacetimeDBProvider` connection.
</StepText>
<StepCode>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import React, {
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useAuth, RedirectToSignIn } from '@clerk/clerk-react';
Expand All @@ -95,12 +96,17 @@ export function useClerkToken() {
/**
* ClerkTokenProvider:
* - If signed out: renders Clerk's redirect component.
* - If signed in: loads a Clerk session token (JWT) and provides it via context.
* - If signed in: loads the initial 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>" }).
* - The token is intentionally captured once per signed-in session. Clerk may
* refresh its session token in the background, but replacing this context
* value would rebuild the SpacetimeDB connection. Keep the existing
* connection open until the user signs out or you intentionally reconnect.
*/
export function ClerkTokenProvider({
children,
Expand All @@ -111,6 +117,7 @@ export function ClerkTokenProvider({

const [token, setToken] = useState<string | null>(null);
const [error, setError] = useState<Error | null>(null);
const tokenRef = useRef<string | null>(null);

useEffect(() => {
let cancelled = false;
Expand All @@ -120,7 +127,15 @@ export function ClerkTokenProvider({

// IMPORTANT: if signed out, clear any cached token
if (!isSignedIn) {
if (!cancelled) setToken(null);
if (!cancelled) {
tokenRef.current = null;
setToken(null);
}
return;
}

// Keep the token stable for the lifetime of this signed-in connection.
if (tokenRef.current) {
return;
}

Expand All @@ -133,7 +148,10 @@ export function ClerkTokenProvider({
throw new Error('Clerk returned no session token.');
}

if (!cancelled) setToken(t);
if (!cancelled) {
tokenRef.current = t;
setToken(t);
}
} catch (e) {
if (!cancelled) setError(e as Error);
}
Expand Down Expand Up @@ -215,6 +233,7 @@ Update your `App.tsx` file to:
2. Pass it to the `DbConnection` builder using `.withToken(...)`.

This mirrors the Auth0 flow: SpacetimeDB receives a bearer token (JWT) and can validate it server-side.
The token returned by `useClerkToken` stays stable while the user remains signed in, so Clerk's background token refreshes do not rebuild the `SpacetimeDBProvider` connection.
</StepText>
<StepCode>

Expand Down
Loading