Bridge Training
Auth

Simulated auth

A vendor-free provider the host drives, for dev scenarios and component tests.

Import from @workspace/auth/simulated. The simulated provider loads the same store the hooks read, without any Clerk code. Who is signed in, and when they sign out, is decided by the host.

createSimulatedAuth

import { createSimulatedAuth } from '@workspace/auth/simulated';

const auth = createSimulatedAuth({
  initial: { kind: 'signed-in', userId: account.id },
  screens: { SignIn: ActorPickerSignIn, SignUp: ActorPickerSignUp },
});

Prop

Type

The store is loaded as soon as the handle exists, so signIn and signOut work before, during and after the Provider is mounted. isLoaded is true from the start: the Provider's gate settles immediately, unless loading keeps it pending until the first signIn or signOut. profile is what the store shows of whoever signs in (useAuthProfile), and onSignOut receives the redirect a port sign-out asks for.

The returned SimulatedAuth is an AuthComponents plus a handle:

MemberDescription
Provider, SignIn, SignUpThe shared surface; the screens are the ones you passed
signIn(userId)Signs that user in with a simulated-session:<userId> session
signOut()Back to anonymous, impersonation cleared
getState()Current SimulatedAuthState
subscribe(listener)Called on every change of identity or impersonated subject

The dev scenario runner uses subscribe to switch the mocked API's actor and to leave the scenario when the user signs out.

SimulatedAuthState

type SimulatedAuthState =
  | { kind: 'anonymous' }
  | {
      kind: 'signed-in';
      userId: string;
      impersonating?: { subjectId: string; token?: string };
    };

impersonating starts the session already impersonating a subject. Without token, the store holds a simulated-impersonation:<subjectId> token; pass one when the mocked API must recognise it.

useSimulatedAuth

For the host-owned sign-in and sign-up screens. Throws outside a simulated Provider.

import { useSimulatedAuth } from '@workspace/auth/simulated';

function ActorPickerSignIn() {
  const { state, signIn, signOut } = useSimulatedAuth();
  return <ActorPicker onPick={signIn} />;
}

state is the SimulatedAuthState without the impersonation detail.

Differences from Clerk

ClerkSimulated
Session tokenClerk JWTsimulated-token:<userId>
Impersonation persistencesessionStorage, survives a reloadMemory only
userLabel for the bannerName, else e-mailnull
Avatar updateClerk profile imageNot available, useAvatarUpdate rejects with UNAUTHORIZED
Password hooksAvailableNot available

In tests

Render the component inside the Provider, and drive identity from the handle:

const auth = createSimulatedAuth({
  initial: { kind: 'anonymous' },
  screens: { SignIn: () => null, SignUp: () => null },
});

render(
  <auth.Provider>
    <ComponentUnderTest />
  </auth.Provider>,
);

act(() => auth.signIn('user_1'));

Because the store is module-level, call auth.signOut() in teardown so the next test starts anonymous.