Managing Authentication in the Frontend

Learn how to manage frontend authentication state, protect authenticated routes and components, and handle unauthorized API responses in backend server integrations.

Overview

In a backend server integration, your backend is responsible for authenticating users with Wristband, managing application sessions, and securely storing access and refresh tokens.

However, your frontend application also needs to know whether a user is authenticated so it can display the appropriate content and handle authentication failures.

The frontend is responsible for three primary tasks:

  • Retrieving Authentication State: Calling your backend's Session Endpoint to determine whether the user is authenticated and retrieve relevant session information.
  • Protecting Routes and Components: Using authentication state to control access to authenticated pages and conditionally render UI components.
  • Handling Unauthorized Responses: Detecting when a user's application session is no longer valid and redirecting them to login when reauthentication is required.

Simplify Frontend Authentication with Wristband's React SDK

Wristband provides the React Client Auth SDK to simplify authentication state management, route protection, and login redirects in React applications. You can also implement these patterns using your preferred frontend framework.

Retrieving Authentication State

Because application session cookies should be configured with the HttpOnly attribute, frontend JavaScript cannot directly access the application session cookie or its contents.

Instead, your frontend must call the application's Session Endpoint to determine whether the user is authenticated and retrieve the session information needed to initialize the UI.

The Session Endpoint performs the following steps:

  1. Receive the Session Request: The frontend calls the Session Endpoint, including the application's session cookie.
  2. Validate the Application Session: The backend retrieves and validates the user's session using the session cookie.
  3. Return Session Information: If the session is valid, the backend returns the session information needed by the frontend. Otherwise, it returns a 401 Unauthorized response.

The frontend uses the response to initialize its authentication state and determine which pages and components to display.

Using the React Client Auth SDK

Framework-Agnostic Authentication Patterns

The following section use Wristband's React Client Auth SDK to demonstrate frontend authentication patterns. These same concepts can be implemented in other frontend frameworks using their respective state management and routing mechanisms.

For React applications, Wristband provides the @wristband/react-client-auth SDK to manage frontend authentication state.

The SDK includes a WristbandAuthProvider component that calls your backend's Session Endpoint, retrieves the user's session information, and makes authentication state available throughout your React application.

To configure the provider, specify the following URLs:

  • loginUrl: The URL of your application's Login Endpoint.
  • sessionUrl: The URL of your application's Session Endpoint.

Place the provider at your application's root so its authentication state is available to all child components.

Example: Configuring the WristbandAuthProvider

import { WristbandAuthProvider } from '@wristband/react-client-auth';

function AppRoot() {
  return (
    <WristbandAuthProvider
      loginUrl="/api/auth/login"
      sessionUrl="/api/auth/session"
    >
      <App />
    </WristbandAuthProvider>
  );
}

Disabling Automatic Redirects

By default, the WristbandAuthProvider automatically redirects users to your application's Login Endpoint when the Session Endpoint returns a 401 Unauthorized response. You can disable this behavior by setting disableRedirectOnUnauthenticated={true}.

When redirects are disabled, use the isAuthenticated state returned by the useWristbandAuth hook to determine whether the user is logged in.

The SDK also provides two hooks for accessing authentication information:

  • useWristbandAuth(): Provides authentication status, loading state, and functions for managing authentication state.
  • useWristbandSession(): Provides access to user information, tenant information, and optional session metadata returned by the Session Endpoint.

For additional configuration options, see the React Client Auth SDK documentation.

Protecting Frontend Routes and Components

Frontend Route Protection Is Not a Substitute for Backend Security

Frontend route guards and conditional rendering improve the user experience, but they do not provide a security boundary.

Your backend must independently validate the user's application session and enforce authorization checks for every protected API request. Hiding a page or component in the frontend does not prevent someone from calling its corresponding backend API directly.

Once your frontend has initialized the user's authentication state, it can use that information to control access to pages and components that require authentication.

There are two common approaches:

  • Route Protection: Prevent unauthenticated users from accessing specific application pages, such as dashboards, account settings, or other authenticated screens.
  • Conditional Rendering: Show or hide individual UI components based on whether the user is authenticated.

Protecting Routes

In React applications, you can implement a route guard that checks the user's authentication status before rendering protected pages.

The useWristbandAuth() hook provides two properties that are particularly useful:

  • isLoading: Indicates whether the application is still determining the user's authentication status.
  • isAuthenticated: Indicates whether the frontend currently considers the user authenticated.

The following example demonstrates a route guard using React Router and the Wristband React Client Auth SDK.

Example: Protected Route

import { Outlet } from 'react-router-dom';
import {
  useWristbandAuth,
  redirectToLogin,
} from '@wristband/react-client-auth';

function ProtectedRoute() {
  const { isLoading, isAuthenticated } = useWristbandAuth();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return (
      <button onClick={() => redirectToLogin('/api/auth/login')}>
        Sign In
      </button>
    );
  }

  return <Outlet />;
}

You can then wrap routes that require authentication with this component.

Example: Protecting Application Pages

<Route element={<ProtectedRoute />}>
  <Route path="/dashboard" element={<Dashboard />} />
  <Route path="/settings" element={<Settings />} />
</Route>

When the application is still checking authentication status, the guard displays a loading state. Once authentication is confirmed, the protected page is rendered.

If the user is unauthenticated, the guard displays redirect to the login page.

Conditionally Rendering Components

You can also use authentication state to conditionally render individual components without protecting an entire route.

For example, your application might display different navigation options depending on whether a user is authenticated.

Example: Conditional Rendering

import {
  useWristbandAuth,
  redirectToLogin,
} from '@wristband/react-client-auth';

function Navigation() {
  const { isLoading, isAuthenticated } = useWristbandAuth();

  if (isLoading) {
    return null;
  }

  return isAuthenticated ? (
    <AuthenticatedNavigation />
  ) : (
    <button onClick={() => redirectToLogin('/api/auth/login')}>
      Sign In
    </button>
  );
}

This pattern is useful for authentication-aware navigation, personalized content, and other UI elements that depend on the user's login status.

Handling 401 Unauthorized API Responses

Even after a user successfully authenticates, their application session may expire or become invalid while they're using the application. When this happens, subsequent requests to protected backend APIs may fail with a 401 Unauthorized response.

When a backend API receives a request without a valid application session, it should return a 401 Unauthorized response. The frontend is then responsible for handling the authentication failure.

The recommended approach is to redirect the user to your application's Login Endpoint when a protected API returns a 401 Unauthorized response. The Login Endpoint initiates a new authentication flow with Wristband. If the user still has an active Wristband authentication session, they may be automatically authenticated without needing to enter their credentials again.

To simplify error handling, frontend applications should use a centralized mechanism, such as an Axios response interceptor or a Fetch wrapper, to detect 401 responses and initiate the login redirect.

Handling 401 Unauthorized Responses with Axios

If your frontend uses Axios, you can configure a response interceptor to handle 401 Unauthorized responses centrally.

A response interceptor allows you to define common error-handling behavior without repeating the same logic in every API request.

Example: Axios Response Interceptor

// api-client.ts

import axios from 'axios';
import { redirectToLogin } from '@wristband/react-client-auth';

const apiClient = axios.create({
  baseURL: '/api',
  withCredentials: true,
});

let redirectingToLogin = false;

apiClient.interceptors.response.use(
  (response) => response,
  (error: unknown) => {
    if (
      axios.isAxiosError(error) &&
      error.response?.status === 401 &&
      !redirectingToLogin
    ) {
      redirectingToLogin = true;
      redirectToLogin('/api/auth/login');
    }

    return Promise.reject(error);
  }
);

export { apiClient };

With this interceptor configured, a protected API returning 401 Unauthorized triggers a redirect to your application's Login Endpoint.

The redirectingToLogin flag prevents multiple simultaneous failed API requests from initiating redundant redirects.

Other errors are propagated back to the calling code for appropriate handling.

Handling 401 Unauthorized Responses with Fetch

If your application uses the Fetch API, you can explicitly check HTTP response status codes and handle authentication failures.

Example: Fetch API

import { redirectToLogin } from '@wristband/react-client-auth';

async function getProjects() {
  const response = await fetch('/api/projects', {
    credentials: 'include',
  });

  if (response.status === 401) {
    redirectToLogin('/api/auth/login');
    return;
  }

  if (response.status === 403) {
    throw new Error('Access denied');
  }

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  return response.json();
}

For larger applications, consider wrapping Fetch in a reusable API client that applies consistent authentication and error-handling behavior across requests.



Did this page help you?