Bridge Training
Auth

Clerk

Binding Clerk to a Vite app behind the shared auth surface.

Import from @workspace/auth/clerk. This entry pulls Clerk's React SDK, so the app loads it lazily and only when no other auth was injected by the shell.

createClerkAuth

Returns an AuthComponents bound to a key, a language and the app's routes.

import { createClerkAuth } from '@workspace/auth/clerk';

const auth = createClerkAuth({
  publishableKey: getEnv().auth.publishableKey,
  useLanguage: useAppLanguage,
  signInUrl: '/signin',
  signUpUrl: '/signup',
  afterAuthUrl: '/',
});

Prop

Type

useLanguage is a reactive hook: the Clerk screens re-localize when the app language changes. The package maps every language code of @workspace/i18n to a Clerk localization and falls back to English for an unknown code.

What the Provider does

ClerkProvider            Clerk's own context, with the resolved localization
  AuthSyncProvider       Reads Clerk hooks, writes the store synchronously during render
    Suspense(pending)
      AuthGate           use(authReady) suspends until the store reports isLoaded
        children

The sync happens during render rather than in an effect so a page reload never flashes the sign-in redirect: by the time children render, the store already knows whether a user is signed in.

Screens

auth.SignIn and auth.SignUp wrap Clerk's components with the configured redirect and cross-links. Mount them on the routes you passed as signInUrl and signUpUrl.

Public surfaces

Import from @workspace/auth/deferred. A public page (the player) never shows a sign-in screen and must not pay for Clerk when nobody is signed in. The entry imports no Clerk code:

import { DeferredAuthProvider, waitForToken } from '@workspace/auth/deferred';

<DeferredAuthProvider publishableKey={signalled ? publishableKey : null}>
  <App />
</DeferredAuthProvider>;

const token = await waitForToken(TOKEN_WAIT_MS);

Prop

Type

  • With a key, it fetches the Clerk provider on demand and mounts it ungated (AuthProvider gate={false}) beside the children: they render at once and are never suspended nor remounted when Clerk arrives. The store, and every hook reading it, follows Clerk once loaded.
  • With null, nothing is fetched and the store settles anonymous right away.
  • A provider that fails to load or throws settles the store anonymous; the children keep running.
  • waitForToken(timeoutMs) is for non-React callers (an HTTP client): the signed-in user's session token, or null when nobody is signed in, the token fails, or nothing came within the bound.

Whether to pass a key is the host's call; hasSignalledSession(cookie) (@workspace/auth/session-signal) reads Clerk's session cookie without loading any auth script.

Configuration

Import from @workspace/auth/config. The package takes a plain publishableKey, validated as a pk_ string. Apps map their own env to it:

import { authConfigSchema, parseAuthConfig } from '@workspace/auth/config';

const envSchema = z.object({
  VITE_CLERK_PUBLISHABLE_KEY: authConfigSchema.shape.publishableKey,
});

const auth = parseAuthConfig({ publishableKey: env.VITE_CLERK_PUBLISHABLE_KEY });

Password hooks

These three hooks talk to Clerk directly and only work under createClerkAuth.

useHasPassword

true when the signed-in user has a password. SSO-only accounts return false; use it to hide the change-password form.

usePasswordChange

Changes the password of the signed-in user. Clerk may ask the user to re-verify, which opens its own modal; a cancelled re-verification resets the status to idle.

import { usePasswordChange } from '@workspace/auth/clerk';

const { changePassword, status, error, isLoading, reset } = usePasswordChange();

await changePassword(
  { newPassword, signOutOfOtherSessions: true },
  { onSuccess: () => toast.success(t('saved')), onError: (message) => toast.error(message) },
);

status is a ChangePasswordStatus: idle, changing, success or error.

usePasswordReset

Two-step reset by e-mail code for a signed-out user. usePasswordResetCurrentUser is the same flow pre-filled with the signed-in user's e-mail.

import { usePasswordReset } from '@workspace/auth/clerk';

const { sendCode, resetPassword, status, error, isLoading } = usePasswordReset();

await sendCode(email);                 // status: code_sent
await resetPassword(code, password);   // status: success, session activated

status is a PasswordResetStatus: idle, sending, code_sent, resetting, success or error. Accounts with a second factor are refused with an error message.

Styles

Clerk components carry Tailwind classes from inside the package. Add the package to the app's Tailwind sources:

@import '@workspace/auth/styles.css';