# Auth — `client.auth` `AuthService` (source: `src/auth/AuthService.ts`). Player authentication: device ID login (primary for game clients), Google / Apple sign-in, custom webhook login, identity linking/unlinking, logout, and auth state observation. ## Methods ```ts loginWithDevice(options?: LoginWithDeviceOptions): Promise loginWithGoogle(options: LoginWithGoogleOptions): Promise loginWithApple(options: LoginWithAppleOptions): Promise loginWithCustom(options: LoginWithCustomOptions): Promise linkWithGoogle(idToken: string): Promise linkWithApple(idToken: string): Promise linkWithCustom(options: LinkWithCustomOptions): Promise unlinkIdentity(provider: string): Promise logout(): void get isAuthenticated(): boolean onAuthStateChange(listener: AuthStateListener): () => void ``` ### `LoginWithDeviceOptions` ```ts { region?: string; // default 'global' language?: string; // default 'en' nickname?: string; // omitted from the request when not set } ``` The device ID is auto-generated on first call and persisted in localStorage (`src/device/DeviceId.ts`). The request also carries the client's `projectKey`. ### `LoginWithGoogleOptions` / `LoginWithAppleOptions` ```ts { idToken: string; // required — Google ID token / Apple identity token region?: string; // default 'global' language?: string; // default 'en' nickname?: string; // omitted from the request when not set } ``` The client obtains the `idToken` from the provider's own sign-in flow (Google Identity Services, Sign in with Apple) and forwards it to `POST /sdk/v1/authorization/google` or `/sdk/v1/authorization/apple`. ### `LoginWithCustomOptions` ```ts { customData: Record; // required — forwarded to the project's custom auth webhook provider?: string; // custom provider name; omitted = the default provider region?: string; // default 'global' language?: string; // default 'en' nickname?: string; } ``` ### Login responses ```ts interface LoginViaDeviceResponse { accessToken?: string; refreshToken?: string } interface LoginViaGoogleResponse { accessToken?: string; refreshToken?: string } interface LoginViaAppleResponse { accessToken?: string; refreshToken?: string } interface LoginViaCustomResponse { accessToken?: string; refreshToken?: string } ``` On success both tokens are saved to the client's `TokenStore`, player and catalog are warmed, the revision poll starts, and the state flips to `'signed-in'`. ## Identity linking `linkWithGoogle` / `linkWithApple` / `linkWithCustom` attach an additional identity to the currently signed-in player; `unlinkIdentity` detaches one. All four require an active session — the access token is sent automatically (transport level). They return nothing (HTTP 204). ```ts await client.auth.linkWithGoogle(idToken); await client.auth.linkWithApple(idToken); await client.auth.linkWithCustom({ customData, provider }); // provider optional await client.auth.unlinkIdentity('custom:my-backend'); ``` ### `LinkWithCustomOptions` ```ts { customData: Record; // required provider?: string; // custom provider name; omitted = the default provider } ``` `unlinkIdentity(provider)` takes the full provider string: `device`, `google`, `apple`, `custom`, or `custom:`. Linked identities appear on the player profile: `client.player.value.identities: PlayerProfileIdentity[]` with `{ provider: string; subject: string; createdAt: string }`. ## Environments A project has two environments, `staging` and `prod`. The SDK key you pass as `RudderClientOptions.projectKey` belongs to one of them, so the environment is resolved at login and carried inside the access and refresh tokens; nothing in the client API takes an environment argument, and a player created in one environment is invisible in the other. Content released only to `staging` is empty for a `prod` key and vice versa. ## Auth state ```ts type AuthState = 'signed-in' | 'signed-out'; type AuthStateListener = (state: AuthState) => void; ``` - `isAuthenticated` is `true` while an access token is present in the token store. - `onAuthStateChange` fires the listener **immediately** with the current state and returns an unsubscribe function. - `logout()` clears tokens, stops the revision poll, drops cached domain data, and emits `'signed-out'`. ## Token refresh (automatic, transport level) Source: `src/transport/request.ts`. - Every request injects `Authorization: Bearer ` when a token exists. - On 401 the transport does a single-flight refresh against `POST /sdk/v1/authorization/refresh` (concurrent 401s share one refresh) and retries the original request once. - If refresh fails, tokens are cleared, `onAuthStateChange` listeners get `'signed-out'`, and the request throws `RudderAuthError`. ## TokenStore ```ts interface TokenStore { getAccessToken(): string | null; getRefreshToken(): string | null; saveTokens(accessToken: string, refreshToken: string): void; clear(): void; } ``` Factories (exported from the package root): - `createDefaultTokenStore()` — localStorage, with a silent in-memory fallback where localStorage is unavailable (SSR, private mode). This is the default when `tokenStore` is omitted from `RudderClientOptions`. - `createLocalStorageTokenStore()` — keys `rudder_access_token` / `rudder_refresh_token`. Provide a custom `TokenStore` via `RudderClientOptions.tokenStore` for other backends (sessionStorage, cookies). ## Errors - `createClient`: missing `projectKey` → `RudderError`, `code: 'sdk/invalid-options'`. - Login failure → `RudderHttpError` (e.g. unknown project key) or `RudderNetworkError`. - Any later request with an expired session → `RudderAuthError` (after the refresh attempt above fails).