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:
| Member | Description |
|---|---|
Provider, SignIn, SignUp | The 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
| Clerk | Simulated | |
|---|---|---|
| Session token | Clerk JWT | simulated-token:<userId> |
| Impersonation persistence | sessionStorage, survives a reload | Memory only |
userLabel for the banner | Name, else e-mail | null |
| Avatar update | Clerk profile image | Not available, useAvatarUpdate rejects with UNAUTHORIZED |
| Password hooks | Available | Not 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.