Frontend SDK Integration
Learn how Wristband frontend SDKs work with your backend server's auth endpoints.
Frontend SDKs run in the browser and never communicate with Wristband directly. Instead, they call your backend server's auth endpoints:
- Session Endpoint: Loads the user's auth state.
- Token Endpoint: Retrieves access tokens, if your frontend uses them directly.
- Login and Logout Endpoints: Where the SDK sends users to log in and log out.
Since the session cookie is HttpOnly, the frontend can't read the session itself, so the Session Endpoint is how it learns who the user is.
See Auth Endpoints for more on these endpoints.
Setting Up the Frontend SDK
The frontend SDK is configured once at the root of your application with the URLs of your backend server's auth endpoints. It then makes the user's auth state available throughout your application.
For example, using the React Client Auth SDK:
import { WristbandAuthProvider } from '@wristband/react-client-auth';
function App() {
return (
<WristbandAuthProvider
loginUrl="https://customer01.yourapp.io/auth/login"
sessionUrl="https://customer01.yourapp.io/auth/session"
>
<YourAppComponents />
</WristbandAuthProvider>
);
}The frontend SDK accepts the following configurations:
| Frontend SDK Configuration | Required | Default | Description |
|---|---|---|---|
loginUrl | Yes | N/A | The URL of your backend server's Login Endpoint. |
sessionUrl | Yes | N/A | The URL of your backend server's Session Endpoint. |
tokenUrl | No | N/A | The URL of your backend server's Token Endpoint. Only needed if your frontend uses access tokens directly. |
csrfCookieName | No | CSRF-TOKEN | The name of the CSRF cookie your backend server sets. |
csrfHeaderName | No | X-CSRF-TOKEN | The name of the header the SDK sends the CSRF token in. |
disableRedirectOnUnauthenticated | No | false | Whether the SDK stays on the current page instead of redirecting to your Login Endpoint when the user isn't authenticated. |
onSessionSuccess | No | N/A | A function that runs after the session loads successfully, before the auth state updates. |
transformSessionMetadata | No | N/A | A function that transforms the metadata from the Session Endpoint response before it's stored. |
The SDK makes the following available throughout your application:
- Auth state: Whether the auth state is still loading, whether the user is authenticated, and any error from loading the session, plus functions to revalidate the session and clear the auth state.
- Session data: The user's
userId,tenantId, andmetadata, plus a function to update the metadata in the browser. - Access tokens: Functions to get and clear the cached access token.
How you access them follows your framework's conventions. For example, the React Client Auth SDK provides all of the above through the useWristbandAuth(), useWristbandSession(), and useWristbandToken() hooks. Refer to each frontend SDK's README file for the exact naming and convention.
Loading the Auth State
Session Endpoint Requirements
For the frontend SDK to load the auth state, your Session Endpoint must:
- Return the user's identity: Respond with a JSON body that includes non-empty
userIdandtenantIdvalues. Themetadataobject is optional. - Return a 401 when the user isn't authenticated: The frontend SDK treats a 401 as unauthenticated, and any other error as a failed request.
- Be protected by the auth middleware: The middleware validates the session, refreshes expired access tokens, and keeps the session rolling on each call.
- Never be cached: The response contains the user's session data, so a browser or shared cache must never store it.
For example, a Session Endpoint using the Express Auth SDK:
app.get('/auth/session', requireWristbandAuth, (req, res) => {
const sessionResponse = req.session.getSessionResponse({ foo: 'bar' });
res.status(200).json(sessionResponse);
});The response looks like this:
{
"tenantId": "tenant_abc123",
"userId": "user_xyz789",
"metadata": {
"foo": "bar"
}
}Calling the Session Endpoint
When your application first loads, the SDK calls your Session Endpoint to find out whether the user is authenticated. For example:
GET https://customer01.yourapp.io/auth/sessionUntil the request completes, the auth state data is considered to be in a loading state. The SDK then handles the response as follows:
- Success: The SDK stores the
userId,tenantId, and anymetadata, and the user is authenticated. - 401 Unauthorized: The user isn't authenticated.
- Any other error: Other client errors (4xx) fail immediately. Server errors (5xx) and network failures are retried, up to three attempts in total, waiting 100 milliseconds between attempts.
Cookies and CSRF TokensThe request includes the browser's cookies, so the session cookie reaches your backend server. If the CSRF cookie is present, the SDK also sends the CSRF token in the request headers.
Auth State
The auth state is made up of the following values:
| Value | Description |
|---|---|
isLoading | true while the Session Endpoint request is in progress. |
isAuthenticated | Whether the user is authenticated. Only meaningful once isLoading is false. |
authStatus | Both values combined: LOADING, AUTHENTICATED, or UNAUTHENTICATED. |
For example, using the React Client Auth SDK:
import { useWristbandAuth } from '@wristband/react-client-auth';
function App() {
const { authStatus } = useWristbandAuth();
if (authStatus === 'LOADING') {
return <div>Loading...</div>;
}
return authStatus === 'AUTHENTICATED' ? <Dashboard /> : <PublicHome />;
}The same check using isLoading and isAuthenticated:
import { useWristbandAuth } from '@wristband/react-client-auth';
function App() {
const { isAuthenticated, isLoading } = useWristbandAuth();
if (isLoading) {
return <div>Loading...</div>;
}
return isAuthenticated ? <Dashboard /> : <PublicHome />;
}In this example, the PublicHome branch only renders when disableRedirectOnUnauthenticated is true. Otherwise, the SDK redirects unauthenticated users to your Login Endpoint.
Session Success Callback
The onSessionSuccess configuration runs a function after the Session Endpoint responds successfully, but before the auth state updates. If the function returns a Promise, the auth state doesn't update until it resolves. This is useful for work that depends on the user's identity, such as prefetching data or initializing analytics.
For example, using the React Client Auth SDK:
<WristbandAuthProvider
loginUrl="https://customer01.yourapp.io/auth/login"
sessionUrl="https://customer01.yourapp.io/auth/session"
onSessionSuccess={({ tenantId, userId }) => {
initializeAnalytics({ tenantId, userId });
}}
>
<YourAppComponents />
</WristbandAuthProvider>When the User Isn't Authenticated
If the session can't be loaded, the SDK redirects the user to your Login Endpoint by default, along with the current page set as the return URL, so that the user lands back where they started after logging in. For example:
GET https://customer01.yourapp.io/auth/login?return_url=https%3A%2F%2Fcustomer01.yourapp.io%2FsettingsWhile the SDK redirects the user, the auth state stays loading until the browser leaves the page. Your frontend never renders a logged-out state.
For pages that serve both authenticated and unauthenticated users, set disableRedirectOnUnauthenticated to true. The SDK then keeps the user on the current page, marks them as unauthenticated, and exposes an error describing why the session couldn't be loaded.
For example, using the React Client Auth SDK:
<WristbandAuthProvider
loginUrl="https://customer01.yourapp.io/auth/login"
sessionUrl="https://customer01.yourapp.io/auth/session"
disableRedirectOnUnauthenticated={true}
>
<YourAppComponents />
</WristbandAuthProvider>const { authError, isAuthenticated } = useWristbandAuth();
if (!isAuthenticated) {
console.debug(authError?.code);
}Errors
Errors are reported as a WristbandError with the following fields:
| Field | Type | Description |
|---|---|---|
code | string | One of the error codes listed below. |
message | string | A description of what went wrong. |
originalError | unknown | The underlying error, such as the failed HTTP response, if there was one. |
The frontend SDK reports errors with the following error codes. Errors loading the auth state are only exposed when disableRedirectOnUnauthenticated is true, since the SDK otherwise redirects to your Login Endpoint. Errors retrieving an access token are thrown when your application requests a token.
| Error Code | Description |
|---|---|
UNAUTHENTICATED | The Session Endpoint or Token Endpoint returned a 401. |
SESSION_FETCH_FAILED | The Session Endpoint returned an error other than a 401, or failed after all retries. |
INVALID_SESSION_RESPONSE | The Session Endpoint response is missing userId or tenantId. |
TOKEN_FETCH_FAILED | The Token Endpoint returned an error other than a 401, or failed after all retries. |
INVALID_TOKEN_RESPONSE | The Token Endpoint response is missing accessToken or expiresAt. |
INVALID_LOGIN_URL, INVALID_LOGOUT_URL, INVALID_SESSION_URL, INVALID_TOKEN_URL | A configured endpoint URL is missing or invalid. |
Session Metadata
The metadata in the Session Endpoint response is whatever your backend server passes to its session response helper, such as the user's name, email, or roles. The frontend SDK makes it available alongside userId and tenantId.
For example, using the React Client Auth SDK:
const { metadata, tenantId, userId } = useWristbandSession();The frontend SDK can update its copy of the metadata to refresh the UI, but those changes only live in the browser. They aren't saved to the backend session and are lost when the page reloads.
For example, using the React Client Auth SDK:
const { metadata, updateMetadata } = useWristbandSession();
// Read a value from the metadata.
const currentTheme = metadata.theme;
// Update the metadata in the browser only.
updateMetadata({ theme: currentTheme === 'dark' ? 'light' : 'dark' });To change session data permanently, update the session on your backend server.
Typed Metadata
In TypeScript, pass a type for the metadata to both the provider and useWristbandSession(), so metadata and updateMetadata() are type-checked.
For example, using the React Client Auth SDK:
interface UserMetadata {
displayName: string;
email: string;
}
<WristbandAuthProvider<UserMetadata>
loginUrl="https://customer01.yourapp.io/auth/login"
sessionUrl="https://customer01.yourapp.io/auth/session"
>
<YourAppComponents />
</WristbandAuthProvider>const { metadata } = useWristbandSession<UserMetadata>();Transforming Metadata
The transformSessionMetadata configuration reshapes the raw metadata from the Session Endpoint before it's stored, such as converting field names to the format your frontend uses. It runs once each time the session loads.
For example, using the React Client Auth SDK:
<WristbandAuthProvider<UserMetadata>
loginUrl="https://customer01.yourapp.io/auth/login"
sessionUrl="https://customer01.yourapp.io/auth/session"
transformSessionMetadata={(rawMetadata: any): UserMetadata => ({
displayName: rawMetadata.user_name,
email: rawMetadata.email_address,
})}
>
<YourAppComponents />
</WristbandAuthProvider>Revalidating the Session
The SDK only loads the session once when your application first loads. If the user's auth state changes without a full page reload, such as after completing login in a popup window, your application can tell the SDK to call the Session Endpoint again and update the auth state.
For example, using the React Client Auth SDK:
const { validateSession } = useWristbandAuth();
// After a popup login completes:
await validateSession();Protecting Frontend Routes and Components
Your frontend uses the auth state to hide protected routes from users who aren't logged in, and to hold off work that needs the user's identity until the session has loaded.
Frontend Checks Don't Protect Your DataFrontend checks only control what the UI shows. Anyone can call your backend APIs directly, so every protected API must still be protected by the auth middleware on your backend server.
See Protecting Routes and APIs for how the auth middleware protects your backend APIs.
Guarding Routes
A route guard wraps the routes that require a logged-in user. It shows a loading state until the session loads, and sends unauthenticated users to your Login Endpoint with the current page as the return URL.
For example, using the React Client Auth SDK:
import { useEffect } from 'react';
import { redirectToLogin, useWristbandAuth } from '@wristband/react-client-auth';
function AuthGuard({ children }: { children: React.ReactNode }) {
const { authStatus } = useWristbandAuth();
useEffect(() => {
if (authStatus === 'UNAUTHENTICATED') {
redirectToLogin('https://customer01.yourapp.io/auth/login', {
returnUrl: window.location.href,
});
}
}, [authStatus]);
if (authStatus !== 'AUTHENTICATED') {
return <div>Loading...</div>;
}
return children;
}Wrap the component of each protected route in the guard. Routes without the guard are only public when disableRedirectOnUnauthenticated is true. Otherwise, the SDK redirects unauthenticated users on every page.
For example, using React Router:
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<PublicHome />} />
<Route
path="/dashboard"
element={
<AuthGuard>
<Dashboard />
</AuthGuard>
}
/>
</Routes>
</BrowserRouter>
);
}A guard like this works with either disableRedirectOnUnauthenticated setting. When it's false, the SDK redirects before the guard sees an unauthenticated state. When it's true, the guard performs the redirect itself.
Waiting for the Session Before Loading Data
Work that needs the user's identity or access token should wait until the user is authenticated. Until the session loads, userId and tenantId are empty, and requesting an access token throws an error.
For example, using the React Client Auth SDK:
import { useEffect } from 'react';
import { useWristbandAuth, useWristbandToken } from '@wristband/react-client-auth';
function Orders() {
const { isAuthenticated } = useWristbandAuth();
const { getToken } = useWristbandToken();
useEffect(() => {
if (!isAuthenticated) {
return;
}
async function loadOrders() {
const accessToken = await getToken();
const response = await fetch('https://api.yourapp.io/orders', {
headers: { Authorization: `Bearer ${accessToken}` },
});
// ...
}
loadOrders();
}, [isAuthenticated, getToken]);
// ...
}Using Access Tokens in the Frontend
Prefer Sessions Over Access Tokens in the BrowserAccess tokens that JavaScript can read in the browser can be stolen through cross-site scripting (XSS) attacks, while the HttpOnly session cookie can't be read by JavaScript at all. Whenever possible, have your frontend call your backend server's APIs with the session cookie, and only expose access tokens to the frontend when an API can't be reached through your backend server.
The Token Endpoint exists for applications that need to call APIs directly from the browser with the user's access token, such as a separate resource server, without routing every request through your backend server.
Token Endpoint Requirements
For the frontend SDK to retrieve access tokens, your Token Endpoint must:
- Return the access token and its expiration: Respond with a JSON body that includes a non-empty
accessTokenand anexpiresAtvalue in milliseconds since the Unix epoch. The frontend SDK usesexpiresAtto decide when to call the Token Endpoint again. - Return a 401 when the user isn't authenticated: The frontend SDK treats a 401 as unauthenticated and clears its cached token.
- Be protected by the auth middleware: The middleware refreshes the access token if it has expired, so the Token Endpoint always returns a valid one.
- Never be cached: The response contains the user's access token, so a browser or shared cache must never store it.
For example, a Token Endpoint using the Express Auth SDK:
app.get('/auth/token', requireWristbandAuth, (req, res) => {
const tokenResponse = req.session.getTokenResponse();
res.status(200).json(tokenResponse);
});The response looks like this:
{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": 1735689600000
}Calling the Token Endpoint
When your frontend needs an access token, the SDK calls your Token Endpoint. For example:
GET https://customer01.yourapp.io/auth/tokenThe SDK caches the access token in memory, and reuses it until it's within 30 seconds of its expiration time. After that, the next request for a token calls the Token Endpoint again. If several parts of your application request a token at the same time, they share a single request. The SDK only keeps the token in memory, never in browser storage. After a page reload, the next request for a token calls the Token Endpoint again.
Your frontend sends the access token in the Authorization header of its API requests.
For example, using the React Client Auth SDK:
const { getToken } = useWristbandToken();
const accessToken = await getToken();
const response = await fetch('https://api.yourapp.io/orders', {
headers: { Authorization: `Bearer ${accessToken}` },
});The API receiving the request must validate the access token as a JWT.
See the JWT Strategy section of Protecting Routes and APIs and JWT Validation for how to validate access tokens.
Keeping Access Tokens Valid
The frontend SDK never refreshes access tokens itself, nor does the refresh token ever enter the browser. When the cached token is about to expire, it calls your Token Endpoint again, and your backend server's auth middleware refreshes the token if needed.
If your Token Endpoint isn't protected by the auth middleware, then before returning the access token, your Token Endpoint must validate the session and use the SDK's refresh function to refresh the access token if it has expired.
See Token Refresh for more on how the backend server refreshes access tokens.
Redirecting to Login and Logout
Your frontend sends users to your Login Endpoint when they need to log in, such as when they click a sign-in button or when an API returns a 401. It sends users to your Logout Endpoint when they log out or their session expires.
The SDK provides convenience functions for both that build the URL with any query parameters you provide. Pass these query parameters as options rather than adding them to the URL yourself. The SDK throws an error if the URL already contains any of them.
Redirecting to Login
For example, using the React Client Auth SDK:
import { redirectToLogin } from '@wristband/react-client-auth';
// Send the user to your Login Endpoint, returning them to the current page after.
redirectToLogin('https://customer01.yourapp.io/auth/login', {
returnUrl: window.location.href,
});The SDK sends the user to your Login Endpoint with the options as query parameters. For example:
GET https://customer01.yourapp.io/auth/login?return_url=https%3A%2F%2Fcustomer01.yourapp.io%2Fsettings| Option | Query Parameter |
|---|---|
idpHint | idp_hint |
loginHint | login_hint |
returnUrl | return_url |
tenantCustomDomain | tenant_custom_domain |
tenantName | tenant_name |
See the Login Endpoint Query Parameters section of Login for what each query parameter does.
Redirecting to Logout
For example, using the React Client Auth SDK:
import { redirectToLogout } from '@wristband/react-client-auth';
// Send the user to your Logout Endpoint.
redirectToLogout('https://yourapp.io/auth/logout', {
tenantName: 'customer01',
});The SDK sends the user to your Logout Endpoint with the options as query parameters. For example:
GET https://yourapp.io/auth/logout?tenant_name=customer01| Option | Query Parameter |
|---|---|
tenantCustomDomain | tenant_custom_domain |
tenantName | tenant_name |
See the Logout Endpoint Query Parameters section of Logout for what each query parameter does.
Handling Unauthenticated API Responses
When your frontend calls a protected API on your backend server and the user's session is no longer valid, such as after it expires or a token refresh fails, the auth middleware in most SDKs returns a 401 Unauthorized response. If CSRF validation fails, it returns a 403 Forbidden response instead. Your frontend is responsible for handling these responses, typically by sending the user to your Login Endpoint.
For example, using the React Client Auth SDK:
const response = await fetch('/api/orders', { credentials: 'include' });
if (response.status === 401) {
redirectToLogin('https://customer01.yourapp.io/auth/login', {
returnUrl: window.location.href,
});
}If your application needs to reset the frontend SDK's auth state without redirecting, such as before starting a new login in a custom flow, the SDK can clear all of its auth state, session data, and cached tokens.
For example, using the React Client Auth SDK:
const { clearAuthData } = useWristbandAuth();
clearAuthData();This only clears state in the browser. It doesn't destroy the backend session or log the user out. It's up to your application whether to also clear the session cookie through your backend server.
CSRF Tokens
When CSRF protection is enabled on your backend server, the SDK automatically sends the CSRF token on its own requests to your Session and Token Endpoints. It reads the token from the CSRF cookie set by your backend server and sends it in the CSRF header.
For your own requests to protected APIs, your frontend must send the CSRF token the same way. For example, using the React Client Auth SDK:
import { getCsrfToken } from '@wristband/react-client-auth';
const response = await fetch('/api/orders', {
method: 'POST',
credentials: 'include',
headers: { 'X-CSRF-TOKEN': getCsrfToken() ?? '' },
});See CSRF Protection for more on how CSRF tokens are generated and validated.
Updated about 4 hours ago