@wristband/react-client-auth
Version:
A lightweight React SDK that pairs with your backend server auth to initialize and sync frontend sessions via secure session cookies.
585 lines (575 loc) • 23.8 kB
TypeScript
import * as react_jsx_runtime from 'react/jsx-runtime';
import { PropsWithChildren } from 'react';
/**
* Represents an error encountered in the Wristband SDK. This error is thrown by various SDK operations
* including session retrieval, token acquisition, and other authentication-related failures. It provides
* a specific error code for easier handling and debugging, along with the original error if available.
*
* @example Session error handling
* ```typescript
* const { authError } = useWristbandAuth();
*
* if (authError) {
* switch (authError.code) {
* case WristbandErrorCode.INVALID_SESSION_RESPONSE:
* console.error('Session configuration error:', authError.message);
* break;
* case WristbandErrorCode.SESSION_FETCH_FAILED:
* console.error('Session network error:', authError.message);
* break;
* default:
* console.error('Unexpected error')
* }
* }
* ```
*
* @example Token error handling
* ```typescript
* try {
* const token = await getToken();
* } catch (error) {
* if (error instanceof WristbandError) {
* switch (error.code) {
* case WristbandErrorCode.UNAUTHENTICATED:
* console.error('User not authenticated');
* break;
* case WristbandErrorCode.INVALID_TOKEN_RESPONSE:
* console.error('Token endpoint configuration error:', error.message);
* break;
* case WristbandErrorCode.TOKEN_FETCH_FAILED:
* console.error('Token network error:', error.message);
* break;
* }
* }
* }
* ```
*/
declare class WristbandError extends Error {
readonly code: WristbandErrorCode;
readonly originalError?: unknown | undefined;
/**
* @param code - The specific {@link WristbandErrorCode} indicating the failure reason.
* @param message - A human-readable error message.
* @param originalError - (Optional) The original error thrown during the request, if available.
*/
constructor(code: WristbandErrorCode, message: string, originalError?: unknown | undefined);
}
/**
* Authentication status enum representing the possible states of user authentication.
*
* This enum is used throughout the authentication flow to represent the current state
* of the authentication process and helps components determine what UI to display.
*/
declare enum AuthStatus {
/**
* The authentication state is currently being determined. This is the initial state during session validation.
*/
LOADING = "LOADING",
/**
* The user is successfully authenticated with a valid session. Protected resources can be accessed in this state.
*/
AUTHENTICATED = "AUTHENTICATED",
/**
* The user is not authenticated or the session is invalid. Access to protected resources should be denied in this
* state.
*/
UNAUTHENTICATED = "UNAUTHENTICATED"
}
/**
* Context interface providing authentication state and session data throughout the application.
*
* This context is provided by the WristbandAuthProvider and can be accessed using the
* useWristbandAuth() and useWristbandSession() hooks.
*
* @template TSessionMetadata - Type for custom session metadata, defaulting to unknown.
*/
interface IWristbandAuthContext<TSessionMetadata = unknown> {
/**
* {@link WristbandError} object containing error details when authentication fails,
* or `null` when no error has occurred.
*/
authError: WristbandError | null;
/**
* Current authentication status representing the state of the authentication process.
* Use this for showing appropriate UI based on auth state (loading indicators, login forms, etc.)
*/
authStatus: AuthStatus;
/**
* This function clears all client-side auth state including authentication status, user data,
* session metadata, and cached tokens. Use this when you need to completely reset the
* SDK state, typically for testing, error recovery, or when implementing custom logout flows.
*
* NOTE: This only clears React state and does not invalidate server-side sessions or
* redirect the user. For standard logout, redirect to your logout URL instead.
*
* @example
* ```typescript
* const { clearAuthData } = useWristbandAuth();
*
* const handleCriticalError = () => {
* clearAuthData(); // Reset all state
* redirectToLogin('/api/auth/login');
* };
* ```
*/
clearAuthData: () => void;
/**
* Clears the cached access token and forces the next getToken() call to fetch a fresh token,
* assuming that the user still has an authenticated session.
*
* NOTE: This only clears the client-side token cache. If the user's session remains
* active, then the getToken() will continue to work by fetching new tokens from the
* configured tokenUrl endpoint.
*
* @example
* ```typescript
* const { getToken, clearToken } = useWristbandToken();
*
* const forceTokenFetch = async () => {
* clearToken(); // Clear cached token
* const freshToken = await getToken(); // Fetches new token
* };
* ```
*/
clearToken: () => void;
/**
* Retrieves a valid access token for making authenticated API calls to resource servers. Returns a
* cached token if available and not expired; otherwise fetches a fresh token from the configured
* "tokenUrl" endpoint. Your server's Token Endpoint should automatically handle token expiration and
* refresh using the user's session cookie.
*
* If the token endpoint returns a 401 (unauthorized), the cached token state will be
* automatically cleared and the user may be redirected to login depending on configuration.
*
* @returns Promise that resolves to a valid access token string
* @throws Error if tokenUrl is not configured, user is not authenticated, or token retrieval fails
*
* @example
* ```typescript
* const { getToken } = useWristbandToken();
*
* const callAPI = async () => {
* try {
* const token = await getToken();
* const response = await fetch('/api/protected', {
* headers: { 'Authorization': `Bearer ${token}` }
* });
* } catch (error) {
* console.error('Token retrieval failed:', error);
* }
* };
* ```
*/
getToken: () => Promise<string>;
/**
* Boolean flag indicating if the user is authenticated.
* Use this for conditional rendering of authenticated content.
*/
isAuthenticated: boolean;
/**
* Boolean flag indicating if the authentication state is still being determined.
* Use this to show loading indicators during initial session validation.
*/
isLoading: boolean;
/**
* Custom metadata associated with the authenticated session. The type is defined by the TSessionMetadata
* generic parameter. Available only when authentication is successful.
*/
metadata: TSessionMetadata;
/**
* Identifier for the tenant associated with the authenticated session.
* Available only when authentication is successful.
*/
tenantId: string;
/**
* Function to update the session metadata. This allows components to modify session data without replacing
* the entire object.
*
* @param newMetadata - Partial metadata object containing properties to update
*/
updateMetadata: (newMetadata: Partial<TSessionMetadata>) => void;
/**
* Unique identifier for the authenticated user.
* Available only when authentication is successful.
*/
userId: string;
}
interface IWristbandAuthProviderProps<TSessionMetadata = unknown> extends PropsWithChildren<{
/**
* Name of the CSRF cookie that the server sets. This enables CSRF protection for requests made to your
* backend server.
* @default 'CSRF-TOKEN'
*/
csrfCookieName?: string;
/**
* Name of the CSRF header that will be sent with authenticated requests. This should match the header name
* your server expects for CSRF validation.
* @default 'X-CSRF-TOKEN'
*/
csrfHeaderName?: string;
/**
* When true, unauthenticated users will remain on the current page instead of being redirected to your backend
* server's Login Endpoint. This is useful for public pages that have both authenticated and
* unauthenticated states.
* @default false
*/
disableRedirectOnUnauthenticated?: boolean;
/**
* This URL should point to your backend server's Login Endpoint that handles the authentication flow with Wristband.
* @required
*/
loginUrl: string;
/**
* Callback that executes after a successful session response but before authentication state updates.
*
* This hook is designed for operations that must complete before the component tree re-renders
* due to authentication state changes. Common use cases include:
*
* - Caching user data with state management solutions (React Query, Redux, etc.)
* - Preloading critical resources needed immediately after authentication
* - Initializing analytics or monitoring services with user identity
* - Setting up user-specific configurations
*
* If this callback returns a Promise, the authentication state update will be
* delayed until the Promise resolves, ensuring all async operations complete first.
*
* NOTE: For simply transforming the metadata stored in context, use transformSessionMetadata() instead.
*
* @example
* onSessionSuccess={async (sessionResponse) => {
* // Cache user data in React Query
* queryClient.setQueryData(['user-profile'], sessionResponse.metadata.profile);
*
* // Prefetch critical user permissions
* await queryClient.prefetchQuery(
* ['user-permissions'],
* () => fetchPermissions(sessionResponse.userId)
* );
* }}
*
* @param sessionResponse The complete session data returned from the session endpoint
* @returns void or a Promise that resolves when all operations are complete
*/
onSessionSuccess?: (sessionResponse: SessionResponse) => Promise<void> | void;
/**
* This URL should point to your backend server's Session Endpoint, which returns an authenticated user's userId,
* tenantId, and any optional metadata.
* @required
*/
sessionUrl: string;
/**
* Optional URL endpoint for retrieving access tokens from your application's backend server.
* When provided, enables getToken() functionality in the useWristbandAuth() hook for making
* direct API calls to resource servers with Bearer tokens. If not provided, applications should
* use session-based authentication for all API calls.
*
* The token endpoint should:
* - Ensure the user has a valid session cookie
* - Handle token refresh automatically using the session state, if necessary.
* - Return a JSON response with { accessToken: string, expiresAt: number }
*
* @example "/api/auth/token"
*/
tokenUrl?: string;
/**
* Function to transform raw metadata from the session response before storing it in context.
*
* Use this to format, type, or filter the metadata that your components will access
* via the useWristbandSession() hook. This is particularly useful for:
*
* - Converting data types (e.g., string dates to Date objects)
* - Adding computed properties
* - Filtering out unnecessary properties
* - Ensuring type safety for the metadata
*
* @example
* transformSessionMetadata={(rawMetadata: unknown): MySessionData => ({
* name: rawMetadata.displayName,
* email: rawMetadata.email,
* hasOwnerRole: rawMetadata.roles.some(role => isAdminRole(role.name)
* })}
*
* @param rawSessionMetadata The unprocessed metadata object from the session response
* @returns A transformed metadata object of type TSessionMetadata
*/
transformSessionMetadata?: (rawSessionMetadata: unknown) => TSessionMetadata;
}> {
}
/**
* Response structure returned by the session endpoint.
*
* This interface represents the expected data structure your backend server should return from its Session Endpoint.
* It contains the essential user information needed to establish an authenticated session in your application.
*
* @interface SessionResponse
* @property {unknown} metadata - Additional user data or session information. The type is 'unknown' to allow flexible schema that can be transformed by your application as needed.
* @property {string} tenantId - The identifier for the tenant the user belongs to.
* @required
* @property {string} userId - The unique identifier for the authenticated user.
* @required
*
* @example
* {
* "userId": "user123456",
* "tenantId": "tenant123456",
* "metadata": {
* "displayName": "Jane Doe",
* "email": "jane@example.com",
* "roles": ["admin", "user"]
* }
* }
*/
interface SessionResponse {
metadata: unknown;
tenantId: string;
userId: string;
}
/**
* Error codes for failures in the Wristband SDK.
*/
declare enum WristbandErrorCode {
/** An invalid login URL value was provided to the SDK. */
INVALID_LOGIN_URL = "INVALID_LOGIN_URL",
/** An invalid logout URL value was provided to the SDK (primarily for `redirectToLogout()`). */
INVALID_LOGOUT_URL = "INVALID_LOGOUT_URL",
/** The session endpoint response is missing required fields. */
INVALID_SESSION_RESPONSE = "INVALID_SESSION_RESPONSE",
/** An invalid session URL value was provided to the SDK. */
INVALID_SESSION_URL = "INVALID_SESSION_URL",
/** The token endpoint response is missing required fields. */
INVALID_TOKEN_RESPONSE = "INVALID_TOKEN_RESPONSE",
/** An invalid token URL value was provided to the SDK (only occurs if using `getToken()`). */
INVALID_TOKEN_URL = "INVALID_TOKEN_URL",
/** The session endpoint returned an error other than 401. */
SESSION_FETCH_FAILED = "SESSION_FETCH_FAILED",
/** The token endpoint returned an error other than 401. */
TOKEN_FETCH_FAILED = "TOKEN_FETCH_FAILED",
/** The user is not authenticated and cannot request a session or token. */
UNAUTHENTICATED = "UNAUTHENTICATED"
}
/**
* WristbandAuthProvider establishes an authenticated session with your backend server
* by making a request to your session endpoint. It manages authentication state and
* provides session data to all child components through React Context.
*
* This component should be placed near the root of your application to make authentication
* state and session data available throughout your component tree.
*
* @example Basic usage
* ```jsx
* function App() {
* return (
* <WristbandAuthProvider
* loginUrl="/api/auth/login"
* sessionUrl="/api/auth/session"
* >
* <YourAppComponents />
* </WristbandAuthProvider>
* );
* }
* ```
*
* * @example Using access tokens directly in React
* ```jsx
* function App() {
* return (
* <WristbandAuthProvider
* loginUrl="/api/auth/login"
* sessionUrl="/api/auth/session"
* tokenUrl="/api/auth/token"
* >
* <YourAppComponents />
* </WristbandAuthProvider>
* );
* }
* ```
*
* @example With custom session metadata handling
* ```jsx
* function App() {
* const queryClient = useQueryClient();
*
* return (
* <WristbandAuthProvider
* loginUrl="/api/auth/login"
* sessionUrl="/api/auth/session"
* transformSessionMetadata={(rawMetadata) => ({
* name: rawMetadata.displayName,
* email: rawMetadata.email,
* role: rawMetadata.userRole
* })}
* onSessionSuccess={(sessionData) => {
* // Cache additional data in React Query
* queryClient.setQueryData(['user-permissions'], sessionData.permissions);
* }}
* >
* <YourAppComponents />
* </WristbandAuthProvider>
* );
* }
* ```
*
* Once rendered, child components can access authentication state using the hooks:
* - useWristbandAuth() - For authentication status (isAuthenticated, isLoading, authStatus, clearAuthData)
* - useWristbandSession() - For session data (userId, tenantId, metadata)
* - useWristbandToken() - For client-side token management, if applicable (getToken, clearToken)
*
* @template TSessionMetaData - Type for the transformed session metadata, if applicable.
*/
declare function WristbandAuthProvider<TSessionMetaData = unknown>({ children, csrfCookieName, csrfHeaderName, disableRedirectOnUnauthenticated, loginUrl, onSessionSuccess, sessionUrl, tokenUrl, transformSessionMetadata, }: IWristbandAuthProviderProps<TSessionMetaData>): react_jsx_runtime.JSX.Element;
declare function useWristbandAuth(): Pick<IWristbandAuthContext, 'isAuthenticated' | 'isLoading' | 'authStatus' | 'authError' | 'clearAuthData'>;
declare function useWristbandSession<TSessionData = unknown>(): {
metadata: TSessionData;
tenantId: string;
userId: string;
updateMetadata: (newMetadata: Partial<TSessionData>) => void;
};
declare function useWristbandToken(): Pick<IWristbandAuthContext, 'getToken' | 'clearToken'>;
/**
* Configuration options for customizing the login redirect behavior.
*/
interface LoginRedirectConfig {
/**
* Optional hint to pre-fill the Tenant Login Page form with a specific username or email.
*
* When provided, this value will be sent as the "login_hint" query parameter, which can be used by your
* authentication endpoint to pre-populate the login form, improving the user experience by reducing manual input.
*
* @example "user@example.com"
*/
loginHint?: string;
/**
* Optional URL to redirect back to after successful authentication.
*
* This URL will be sent as the "return_url" query parameter to your Login Endpoint. After successful login, your
* auth flow should redirect users back to this URL. If not provided, the current page URL will be used as the
* return destination.
*
* @example "https://app.example.com/dashboard"
*/
returnUrl?: string;
/**
* Optional tenant domain name for directing users to the correct Tenant Login Page.
*
* When provided, this value is sent as the "tenant_domain" query parameter to your backend server's Login Ednpoint,
* enabling automatic tenant routing during login.
*
* This parameter is primarily useful when:
* - All tenants in your application share a common app URL (i.e. no subdomains, no custom domains)
* - You want to avoid tenant discovery during login
*
* @example "acme-corp" // Directs to Acme Corporation's Tenant Login Page
*/
tenantDomain?: string;
/**
* Optional tenant custom domain for directing users to the correct Tenant Login Page.
*
* When provided, this value is sent as the "tenant_custom_domain" query parameter to your
* backend server's Login Endpoint, allowing routing to tenant custom domains.
*
* This parameter is useful when:
* - Tenants have their own custom domains
* - You want to avoid tenant discovery during login
*
* @example "auth.acme.com" // Directs to Acme's tenant custom domain
*/
tenantCustomDomain?: string;
}
/**
* Configuration options for redirecting to your server's logout endpoint.
* @interface LogoutRedirectConfig
*/
interface LogoutRedirectConfig {
/**
* Optional tenant domain name for directing users to the correct Tenant Login Page.
*
* When provided, this value is sent as the "tenant_domain" query parameter to your backend server's Logout Ednpoint,
* enabling automatic tenant routing during logout.
*
* This parameter is primarily useful when:
* - All tenants in your application share a common app URL (i.e. no subdomains, no custom domains)
* - You want to avoid tenant discovery during login
*
* @example "acme-corp" // Directs to Acme Corporation's Tenant Login Page
*/
tenantDomain?: string;
/**
* Optional tenant custom domain for directing users to the correct Tenant Login Page.
*
* When provided, this value is sent as the "tenant_custom_domain" query parameter to your
* backend server's Login Endpoint, allowing routing to tenant custom domains.
*
* This parameter is useful when:
* - Tenants have their own custom domains
* - You want to avoid tenant discovery during login
*
* @example "auth.acme.com" // Directs to Acme's tenant custom domain
*/
tenantCustomDomain?: string;
}
/**
* Redirects the user to your backend server's Login Endpoint with optional configuration parameters (browser only).
*
* This function initiates a redirect to the specified login URL and appends relevant query parameters
* based on the provided configuration. The redirect will preserve the current page URL as the return
* destination by default, allowing users to resume where they left off after authentication.
*
* @param {string} loginUrl - The login endpoint URL that handles authentication
* @param {LoginRedirectConfig} config - Optional configuration for customizing the login experience
* @returns {Promise<void>} A promise that resolves when the redirect is triggered
* @throws {WristbandError} If loginUrl is undefined, null, or empty.
*
* @example
* // Basic redirect to login endpoint
* await redirectToLogin('/api/auth/login');
*
* @example
* // Redirect with pre-filled email address
* await redirectToLogin('/api/auth/login', {
* loginHint: 'user@example.com'
* });
*
* @example
* // Redirect with custom return destination and tenant domain
* await redirectToLogin('/api/auth/login', {
* returnUrl: 'https://app.example.com/dashboard',
* tenantDomain: 'acme-corp'
* });
*
* @example
* // Redirect with custom return destination and tenant domain
* await redirectToLogin('/api/auth/login', {
* returnUrl: 'https://app.example.com/dashboard',
* tenantCustomDomain: 'auth.acme.com'
* });
*/
declare function redirectToLogin(loginUrl: string, config?: LoginRedirectConfig): void;
/**
* Redirects the user to your backend server's Logout Endpoint with optional configuration (browser only).
*
* This function navigates the user to the specified logout URL and can append additional parameters as needed.
*
* @param {string} logoutUrl - The URL of your server's Logout Endpoint
* @param {LogoutRedirectConfig} config - Optional configuration for the logout redirect
* @returns {Promise<void>} A promise that resolves when the redirect is triggered
* @throws {WristbandError} If logoutUrl is undefined, null, or empty.
*
* @example
* // Basic redirect to logout endpoint
* await redirectToLogout('/api/auth/logout');
*
* @example
* // Redirect with tenant domain parameter
* await redirectToLogout('/api/auth/logout', {
* tenantDomain: 'acme-corp'
* });
*
* @example
* // Redirect with tenant domain parameter
* await redirectToLogout('/api/auth/logout', {
* tenantCustomDomain: 'auth.acme.com'
* });
*/
declare function redirectToLogout(logoutUrl: string, config?: LogoutRedirectConfig): void;
export { AuthStatus, WristbandAuthProvider, WristbandError, WristbandErrorCode, redirectToLogin, redirectToLogout, useWristbandAuth, useWristbandSession, useWristbandToken };
export type { LoginRedirectConfig, LogoutRedirectConfig, SessionResponse };