Hooks and types
Store-backed hooks and the vendor-free types every app codes against.
Import from @workspace/auth. Every hook reads the auth store, so it works the same under Clerk and under the simulated provider, and it never imports the vendor. The types alone are also exposed by @workspace/auth/types.
useAuthPort
Returns a stable AuthPort. The object only changes when the user id, the loading flag, the signed-in flag or the impersonation changes.
import { useAuthPort } from '@workspace/auth';
function DevOnly({ children }: { children: ReactNode }) {
const auth = useAuthPort();
return isDevUser(auth.getUserId()) ? children : null;
}The app hands the same object to TanStack Router's context so route beforeLoad guards can call auth.isAuthenticated() and redirect to the sign-in page.
AuthPort
Prop
Type
getUserId() is the effective identity: the impersonated subject while an impersonation runs, else the signed-in user. getActorId() is who really holds the session. Read the former for data, the latter for anything that must name the human in front of the screen. See Impersonation.
IAuthService and useAuthService are deprecated aliases of AuthPort and useAuthPort.
useAuthSession
Returns the AuthSession or null. It only updates when the session id changes, not on every token refresh.
import { useAuthSession } from '@workspace/auth';
const session = useAuthSession();
const getToken = useMemo(
() => () => session?.getToken() ?? Promise.resolve(null),
[session],
);AuthSession
Prop
Type
getToken() returns the bearer the API verifies with verifyToken. Under the simulated provider it resolves to a simulated-token:<userId> string.
useAuthProfile
Returns what a surface may show of the signed-in user, or null when nobody is signed in: the avatar menu of a public page reads it, signupSource included to send the user to the space they signed up on.
import { useAuthProfile } from '@workspace/auth';
const profile = useAuthProfile();Prop
Type
useImpersonation
Returns an ImpersonationState, never null. When nothing is active every field but isImpersonating is null.
import { useImpersonation } from '@workspace/auth';
const { isImpersonating, subjectId, actorLabel, startedAt } = useImpersonation();Prop
Type
useImpersonationActions
Returns { start(token, subjectId), stop() } bound to the store. start throws when nobody is signed in. These are store-level only: the HTTP calls that mint the token or log the stop live in the app.
import { useImpersonationActions } from '@workspace/auth';
const actions = useImpersonationActions();
actions.start(handoff.token, handoff.subjectId);
actions.stop();useAvatarUpdate
Uploads or removes the signed-in user's avatar through whichever provider populated the store.
import { useAvatarUpdate } from '@workspace/auth';
const { uploadAvatar, removeAvatar, isPending, error } = useAvatarUpdate();
await uploadAvatar(file); // validates, then { imageUrl }
await removeAvatar(); // { imageUrl } of the provider defaultBoth calls reject with an AvatarUpdateError, also exposed through error.
Avatar validation
validateAvatarFile(file) throws before any upload when the MIME type is not in ALLOWED_AVATAR_MIME_TYPES (image/png, image/jpeg, image/webp) or the size exceeds MAX_AVATAR_SIZE_BYTES (5 MiB). uploadAvatar calls it for you; call it yourself to reject a file on selection.
AvatarUpdateError extends AppError from @workspace/training with isExpected = true and one of these codes:
| Code | Raised when |
|---|---|
AVATAR_INVALID_TYPE | MIME type not allowed |
AVATAR_FILE_TOO_LARGE | File above the size cap |
AVATAR_UPLOAD_FAILED | The provider refused the upload |
AVATAR_PERSIST_FAILED | The removal failed |
UNAUTHORIZED | No signed-in user |
AuthComponents
The surface a host mounts. Both createClerkAuth and createSimulatedAuth return it.
Prop
Type
Provider suspends its children until the store reports isLoaded, rendering pending meanwhile. Mount it once, above everything that calls a hook from this package.
Testing a consumer
Components that only need an AuthPort can take a hand-written one, no provider required:
import type { AuthPort } from '@workspace/auth';
const anonymous: AuthPort = {
getUserId: () => null,
getActorId: () => null,
getImpersonationToken: () => null,
isLoaded: () => true,
isAuthenticated: () => false,
signOut: async () => {},
};Components that call the hooks need the store loaded: render them inside a simulated provider.