@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
JavaScript
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 };