UNPKG

@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.

130 lines (127 loc) 5.38 kB
import { jsx } from 'react/jsx-runtime'; import { useState, useCallback, useEffect } from 'react'; import { WristbandAuthContext } from './wristband-auth-context.js'; import { AuthStatus } from '../types/auth-provider-types.js'; import { isUnauthorizedError } from '../utils/auth-utils.js'; import apiClient from '../api/api-client.js'; import { resolveAuthProviderLoginUrl, validateAuthProviderLogoutUrl, validateAuthProviderSessionUrl } from '../utils/auth-provider-utils.js'; /** * 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" * logoutUrl="/api/auth/logout" * sessionUrl="/api/auth/session" * > * <YourAppComponents /> * </WristbandAuthProvider> * ); * } * ``` * * @example With custom session metadata handling * ```jsx * function App() { * const queryClient = useQueryClient(); * * return ( * <WristbandAuthProvider * loginUrl="/api/auth/login" * logoutUrl="/api/auth/logout" * 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) * - useWristbandSession() - For session data (userId, tenantId, metadata) * * @template TSessionMetaData - Type for the transformed session metadata, if applicable. */ function WristbandAuthProvider({ children, csrfCookieName = 'CSRF-TOKEN', csrfHeaderName = 'X-CSRF-TOKEN', disableRedirectOnUnauthenticated = false, loginUrl, logoutUrl, onSessionSuccess, sessionUrl, transformSessionMetadata, }) { const resolvedLoginUrl = resolveAuthProviderLoginUrl(loginUrl); validateAuthProviderLogoutUrl(logoutUrl); validateAuthProviderSessionUrl(sessionUrl); const [isAuthenticated, setIsAuthenticated] = useState(false); const [isLoading, setIsLoading] = useState(true); const [userId, setUserId] = useState(''); const [tenantId, setTenantId] = useState(''); const [metadata, setMetadata] = useState({}); const authStatus = isLoading ? AuthStatus.LOADING : isAuthenticated ? AuthStatus.AUTHENTICATED : AuthStatus.UNAUTHENTICATED; const updateMetadata = useCallback((newMetadata) => { setMetadata((prevData) => ({ ...prevData, ...newMetadata })); }, []); // Bootstrap the application with the authenticated user's session data. useEffect(() => { const fetchSession = async () => { try { // The session API will let React know if the user has a previously authenticated session. // If so, it will initialize session data. const response = await apiClient.get(sessionUrl, { csrfCookieName, csrfHeaderName }); const { userId, tenantId, metadata: rawMetadata } = response.data; // Execute side effects callback before updating state if provided if (onSessionSuccess) { await Promise.resolve(onSessionSuccess(response.data)); } // Apply transformation if provided if (rawMetadata) { setMetadata(transformSessionMetadata ? transformSessionMetadata(rawMetadata) : rawMetadata); } // Update remaining context state last setTenantId(tenantId || ''); setUserId(userId || ''); setIsAuthenticated(true); setIsLoading(false); } catch (error) { console.log(error); if (disableRedirectOnUnauthenticated) { setIsAuthenticated(false); setIsLoading(false); } else { // Don't call logout on 401 to preserve the current page for when the user returns after re-authentication. window.location.href = isUnauthorizedError(error) ? resolvedLoginUrl : logoutUrl; } } }; fetchSession(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (jsx(WristbandAuthContext.Provider, { value: { authStatus, isAuthenticated, isLoading, metadata, tenantId, updateMetadata, userId, }, children: children })); } export { WristbandAuthProvider };