Integrate Sessions With The Frontend (React)

Create a React Context to store the user's session state and protect authenticated routes.

Now that you've updated your server endpoints to manage the user's session, we'll next go over how to store that session data into your frontend using a React Context. We'll also show how to use the authenticated state from the React Context to protect routes that require authentication.


Create a React Context to Store Session Data

In order to populate the user's session data into a React Context, we need to create a React Context Provider. The React Context Provider will call the Session Endpoint that was implemented earlier to retrieve the user's session data. If the call to the Session Endpoint succeeds, we can be sure that the user successfully authenticated and store any pertinent session data into the React Context. However, if the call to the Session Endpoint returns a 401 status code, then we know that the user does not have a valid session, and we can redirect them back to the Login Endpoint.

import React, { createContext, useContext, useEffect, useState } from 'react';

// This value should point to your application's login URL.
// For example: https://your.app.com/auth/login.
const LOGIN_URL = '<replace with your application Login Endpoint URL>'

// This value should point to your application's logout URL.
// For example: https://your.app.com/auth/logout.
const LOGOUT_URL = '<replace with your application Logout Endpoint URL>'

// The type definition of the authenticated user. The user type can be modified to 
// fit your you needs.  This is just a minimal example for demonstration purposes.
type User = {
  email: string
};

// The type definition of the object that will be stored in the React Context.
type AuthContextProps = {
  // Boolean indicating if the user successfully authenticated or not.
  isAuthenticated?: boolean;
  // Boolean indicating if the application is loading and the user's authenticated 
  // state has not yet been determined.
  isLoading?: boolean;
  // Function to modify the user stored within the React Context.
  setUser: (user: User) => void;
  // Object containing data representing the authenticated user.
  user: User;
};

// Creates the React Context
const AuthContext = createContext<AuthContextProps>({
  isAuthenticated: false,
  isLoading: false,
  setUser: () => {},
  user: { email: '' },
});

// Provider that loads the React Context with the user's session data.
const AuthProvider = ({ children: React.ReactNode  }) => {
  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [user, setUser] = useState<User>({ email: '' });

  // Bootstrap the application with the authenticated user's session data.
  useEffect(() => {
    const fetchSession = async () => {
      try {
        // Call your server's Session Endpoint to verify that the user
        // has a valid session cookie and update the user state with 
        // returned session data.
        const sessionData = await getSession();
        setIsAuthenticated(true);
        setUser(sessionData.user);
      } catch (error: unknown) {
        if (isUnauthorized(error)) { 
          window.location.href = redirectToLogin(); 
        } else {
          window.location.href = redirectToLogout();
        }
      } finally {
        setIsLoading(false);
      }
    };

    fetchSession();
  }, []);

  return (
    <AuthContext.Provider value={{ isAuthenticated, isLoading, user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
}

// Helper function for determining if an AxiosError was due to a 401.
function isUnauthorized(error: AxiosError) {
  if (!error) {
    return false;
  }
  if (error instanceof AxiosError) {
    return error.response?.status === 401;
  }
  return false;
}

function redirectToLogin() {
  window.location.href = LOGIN_URL;
}

function redirectToLogout() {
  window.location.href = LOGOUT_URL;
}

function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth() must be used within an AuthProvider');
  }
  return context;
}

export { AuthProvider, useAuth, redirectToLogin, redirectToLogout };



Propagate Authenticated State Using AuthProvider

Place the AuthProvider at your app's root to ensure the user's authenticated state is available throughout your application and verified on initial load.

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import './index.css';

import { AuthProvider } from '@/context';

const root = createRoot(document.getElementById('root'));

root.render(
  <StrictMode>
    <AuthProvider>
      {children}
    </AuthProvider>
  </StrictMode>
);
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

import './index.css';

import { App } from 'app';
import { AuthProvider } from 'context';

const root = createRoot(document.getElementById('root'));

root.render(
  <StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </StrictMode>
);



Protect Frontend Routes and Components

The useAuth() hook allows you to get access to the user's authentication state throughout your app. This enables common patterns like conditional rendering of authenticated/unauthenticated views, route protection, or dynamic UI updates based on the user's auth status.

Below are several common patterns you can apply as needed:


Use The User's Authenticated State to Render Different Components

import React from "react";

import { useAuth, redirectToLogin, redirectToLogout } from "@/context/auth-context";

function App() {
  const { isAuthenticated, user } = useAuth();

  const AuthenticatedView = () => (
    <>
      <h1>Welcome to Wristband Auth, {user.firstName}!</h1>
      <button onClick={redirectToLogout}>Logout</button>
    </>
  );
  const UnauthenticatedView = () => (
    <>
      <h1>Welcome to Wristband Auth</h1>
      <button onClick={redirectToLogin}>Login</button>
    </>
  );

  return (
    <div>
      {isAuthenticated ? <AuthenticatedView /> : <UnauthenticatedView />}
    </div>
  );
};

export default App;


Create an AuthGuard Component to Protect Explicit Routes

import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";

import {  Dashboard, Spinner } from "@/components";
import { useAuth } from "@/context/auth-context";

const AuthGuard = ({ children, fallback = null }) => {
  const { isAuthenticated } = useAuth();
  // Show fallback UI (e.g., a redirect, login prompt, etc.)
  if (!isAuthenticated) {
    return fallback; 
  }
  return children;
};

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route
          path="/dashboard"
          element={
            <AuthGuard fallback={<Login />}>
              <Dashboard />
            </AuthGuard>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}

export default App;


Congratulations on making it this far! You've now learned how to integrate Wristband authentication into both your C# server and React frontend!


What’s Next

Before we test the authentication flows, let's first determine if your application needs to configure CORS.