import type { SessionStorage, SessionIdStorageStrategy, data, SessionData } from 'react-router'; import type { AuthenticationResponse, FeatureFlagsRuntimeClient, OauthTokens, User } from '@workos-inc/node'; import * as v from 'valibot'; export type DataWithResponseInit = ReturnType>; export type UnwrapData = T extends DataWithResponseInit ? U : T; export type HandleAuthOptions = { returnPathname?: string; onSuccess?: (data: AuthLoaderSuccessData) => void | Promise; } & ( | { storage?: never; cookie?: SessionIdStorageStrategy['cookie']; } | { storage: SessionStorage; cookie: SessionIdStorageStrategy['cookie']; } ); export interface AuthLoaderSuccessData { accessToken: string; impersonator: Impersonator | null; oauthTokens: OauthTokens | null; refreshToken: string; user: User; organizationId: string | null; authenticationMethod?: AuthenticationResponse['authenticationMethod']; state?: string; } export interface RefreshErrorOptions { error: unknown; request: Request; sessionData: SessionData; /** * Whether the refresh failed for a transient reason (network error, timeout, * 429, or 5xx) rather than a terminal one (the refresh token is dead). When * `true`, the sealed session is preserved (not destroyed) so a later request * can refresh successfully once the condition clears. */ isTransient: boolean; } export interface RefreshSuccessOptions { accessToken: string; user: User; impersonator: Impersonator | null; organizationId: string | null; } export interface FeatureFlagsErrorOptions { error: unknown; request: Request; user: User; organizationId: string | null; tokenFeatureFlags: string[]; } export interface AuthKitFeatureFlagsOptions { runtimeClient: FeatureFlagsRuntimeClient; waitUntilReady?: boolean | { timeoutMs?: number }; } export interface Impersonator { email: string; reason: string | null; } export interface Session { accessToken: string; refreshToken: string; user: User; impersonator?: Impersonator; headers: Record; } export interface AccessToken { sid: string; org_id?: string; role?: string; roles?: string[]; permissions?: string[]; entitlements?: string[]; feature_flags?: string[]; } export interface UserInfo { user: User; sessionId: string; organizationId?: string; role?: string; roles?: string[]; permissions?: string[]; entitlements?: string[]; featureFlags?: string[]; impersonator?: Impersonator; accessToken: string; } export interface NoUserInfo { user: null; sessionId?: undefined; organizationId?: undefined; role?: undefined; roles?: undefined; permissions?: undefined; entitlements?: undefined; featureFlags?: undefined; impersonator?: undefined; accessToken?: undefined; } export interface GetAuthURLOptions { screenHint?: 'sign-up' | 'sign-in'; returnPathname?: string; organizationId?: string; redirectUri?: string; loginHint?: string; prompt?: 'consent'; /** * Custom state value echoed back to `onSuccess` after a successful callback. * The library always generates its own internal OAuth `state` parameter so * that PKCE and CSRF protection cannot be bypassed — this value is sealed * alongside it for round-trip delivery only. */ state?: string; /** * The incoming `Request`, if available. When provided the PKCE cookie's * `Secure` attribute is derived from the live request protocol rather than * the configured `redirectUri` — necessary so local dev on `http://` with * a `https://` redirect URI still sets a cookie the browser will keep. */ request?: Request; } /** * Result of building an authorization URL. The caller must attach `headers` * to whatever redirect response they return so the short-lived PKCE / * CSRF-binding cookie is set on the browser before WorkOS redirects back. * * The concrete `{ 'Set-Cookie': string }` shape is still assignable to * `HeadersInit` (via `Record`), so callers can spread it * directly into a `new Headers({ ...headers, 'Cache-Control': 'no-store' })` * or pass it straight to `redirect(url, { headers })`. * * @example * const { url, headers } = await getSignInUrl('/dashboard', request); * return redirect(url, { headers }); */ export interface GetAuthURLResult { url: string; headers: { 'Set-Cookie': string }; } /** * Sealed value round-tripped through WorkOS as the OAuth `state` URL * parameter. It carries no secret: the PKCE `codeVerifier` deliberately lives * only in the HttpOnly cookie (see `PKCECookieSchema`), never in the URL, so a * leaked callback URL cannot complete the code exchange on its own. `nonce` * binds the state to the verifier cookie set on the initiating browser. */ export const StateSchema = v.object({ nonce: v.string(), customState: v.optional(v.string()), returnPathname: v.optional(v.string()), }); export type State = v.InferOutput; /** * Sealed payload stored only in the HttpOnly PKCE cookie. Holds the PKCE * `codeVerifier` — the secret that binds the authorization code to the browser * that started the flow — plus the `nonce` that ties it back to the URL state. */ export const PKCECookieSchema = v.object({ nonce: v.string(), codeVerifier: v.string(), }); export type PKCECookiePayload = v.InferOutput; export type AuthKitLoaderOptions = { ensureSignedIn?: boolean; debug?: boolean; featureFlags?: AuthKitFeatureFlagsOptions; onFeatureFlagsError?: (options: FeatureFlagsErrorOptions) => void | Promise; onSessionRefreshError?: (options: RefreshErrorOptions) => void | Response | Promise; onSessionRefreshSuccess?: (options: RefreshSuccessOptions) => void | Promise; } & ( | { storage?: never; cookie?: SessionIdStorageStrategy['cookie']; } | { storage: SessionStorage; cookie: SessionIdStorageStrategy['cookie']; } ); export interface AuthorizedData { user: User; sessionId: string; organizationId: string | null; role: string | null; roles: string[] | null; permissions: string[]; entitlements: string[]; featureFlags: string[]; impersonator: Impersonator | null; } export interface UnauthorizedData { user: null; sessionId: null; organizationId: null; role: null; roles: null; permissions: null; entitlements: null; featureFlags: null; impersonator: null; } /** * Configuration shared by both client modes. */ interface AuthKitBaseConfig { /** * The WorkOS Client ID * Equivalent to the WORKOS_CLIENT_ID environment variable */ clientId: string; /** * The redirect URI for the authentication callback * Equivalent to the WORKOS_REDIRECT_URI environment variable */ redirectUri: string; /** * The password used to encrypt the session cookie * Equivalent to the WORKOS_COOKIE_PASSWORD environment variable * Must be at least 32 characters long */ cookiePassword: string; /** * The hostname of the API to use * Equivalent to the WORKOS_API_HOSTNAME environment variable */ apiHostname?: string; /** * Whether to use HTTPS for API requests * Equivalent to the WORKOS_API_HTTPS environment variable */ apiHttps: boolean; /** * The port to use for the API * Equivalent to the WORKOS_API_PORT environment variable */ apiPort?: number; /** * The expected `iss` claim of WorkOS access tokens, or a list of accepted issuers * Equivalent to the WORKOS_ISSUER environment variable (comma-separated for a list) * Defaults to the configured API origin, e.g. `https://api.workos.com` */ issuer?: string | string[]; /** * The maximum age of the session cookie in seconds * Equivalent to the WORKOS_COOKIE_MAX_AGE environment variable */ cookieMaxAge: number; /** * The name of the session cookie * Equivalent to the WORKOS_COOKIE_NAME environment variable * Defaults to "wos-session" */ cookieName: string; } /** * Confidential client: the server holds a WorkOS API key. * * The key is sent as the client secret on code exchange and refresh (alongside * PKCE), and unlocks features that call WorkOS management APIs: feature flags * (`getFeatureFlagsRuntimeClient`) and direct `getWorkOS()` calls such as * `organizations.*` or `userManagement.getUser`. */ export interface AuthKitConfidentialConfig extends AuthKitBaseConfig { /** * The WorkOS API Key * Equivalent to the WORKOS_API_KEY environment variable */ apiKey: string; } /** * Public client (keyless): the server holds no WorkOS API key. * * Sign-in, callback, session refresh, organization switching and sign-out all * work; PKCE protects the code exchange and the refresh token is bound to the * client ID. Feature flags and WorkOS management APIs are unavailable. */ export interface AuthKitPublicConfig extends AuthKitBaseConfig { apiKey?: never; } /** * AuthKit Configuration Options * * Discriminated on the presence of `apiKey`: {@link AuthKitConfidentialConfig} * when set, {@link AuthKitPublicConfig} when not. Configuration is resolved at * runtime from environment variables and `configure()`, so `getConfig('apiKey')` * is typed `string | undefined`. */ export type AuthKitConfig = AuthKitConfidentialConfig | AuthKitPublicConfig;