DEV Community

Cover image for Session Management in React
Agbo, Daniel Onuoha
Agbo, Daniel Onuoha

Posted on

Session Management in React

Sessions in React are about how you persist and validate user state (usually authentication) across page navigations and reloads in a single‑page application. In practice, this means combining a storage mechanism (cookies, storage, memory) with an auth/session strategy (stateful server sessions or stateless tokens) and wiring it into your React tree.

What “session” means in a React app

In a browser‑based React app, a “session” usually means:

  • The period during which the app considers a user authenticated.
  • The user- and auth-related state you keep while that’s true (user id, roles, permissions, preferences, CSRF tokens, etc.).
  • The rules for when that state starts (login), ends (logout, expiry), and is refreshed (token rotation, silent re‑auth).

Because React runs in the browser, session management is always a combination of client behavior and backend behavior; React only controls how you store and use data on the client, not whether a token is actually valid on the server.

Common session storage options

Mechanism Scope / lifetime Typical use cases Pros Cons
Cookies Sent automatically with matching requests, configurable TTL Traditional server sessions, HttpOnly auth cookies HttpOnly, Secure, SameSite flags; good for security CSRF care needed; limited size; must design cookie scope carefully
Local Storage Persistent until cleared by app or user Non‑sensitive preferences, feature flags Simple API; survives tab close and browser restart Accessible to JS (XSS risk); no built‑in expiry
Session Storage Lives as long as the tab/window is open Short‑lived, non‑sensitive session state Isolated per tab; clears on tab close Also JS‑accessible; no automatic server‑side awareness
JWT (JSON Web Token) Depends on where/how you store it Stateless auth with APIs and SPAs No server store needed; easy to share across services Easy to misuse; you still need secure storage and rotation
Server‑side session Server keeps session state keyed by id (often in a cookie) Classic web auth, many enterprise setups Server controls revocation and expiry centrally Requires scalable session store; more backend complexity

A key design decision is whether you use stateful sessions (server stores the session) or stateless tokens (JWT or similar) that the server validates on each request.

Implementing sessions in React

1. Cookie-based sessions (recommended for most apps)

Typical flow:

  1. User logs in with credentials via a React form.
  2. Backend:
    • Authenticates the user.
    • Creates a session record in a session store (DB, Redis, etc.).
    • Sets a HttpOnly, Secure, SameSite cookie with a session id or opaque token.
  3. React:
    • Does not need direct cookie access; it just calls APIs with credentials: 'include' on fetch or appropriate config in Axios.
    • Uses an auth context or state to reflect “logged in” vs “logged out” based on API responses.

Example (simplified):

// login.tsx
async function login(username: string, password: string) {
  const res = await fetch('/api/login', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  });

  if (!res.ok) throw new Error('Login failed');
}
Enter fullscreen mode Exit fullscreen mode

This approach keeps sensitive data in HttpOnly cookies (not accessible to JS), which significantly reduces XSS impact.

2. JWT-based sessions in React

With JWTs, the server issues a signed token that encodes claims like user id and expiry. The client then sends this token with each API call.

Safer patterns:

  • Prefer short‑lived access tokens plus refresh tokens.
  • Prefer storing:
    • Access token in memory (React state) or in a non‑persistent store,
    • Refresh token in an HttpOnly cookie, and
    • Using a refresh endpoint to get new access tokens when they expire.

Very simplified pattern:

// authClient.ts
let accessToken: string | null = null;

export function setAccessToken(token: string | null) {
  accessToken = token;
}

export async function apiFetch(input: RequestInfo, init: RequestInit = {}) {
  const headers = new Headers(init.headers);
  if (accessToken) {
    headers.set('Authorization', `Bearer ${accessToken}`);
  }

  const res = await fetch(input, { ...init, headers, credentials: 'include' });

  if (res.status === 401) {
    // try refresh
    const refreshed = await fetch('/api/refresh-token', {
      method: 'POST',
      credentials: 'include',
    });
    if (refreshed.ok) {
      const { token } = await refreshed.json();
      setAccessToken(token);
      headers.set('Authorization', `Bearer ${token}`);
      return fetch(input, { ...init, headers, credentials: 'include' });
    }
  }

  return res;
}
Enter fullscreen mode Exit fullscreen mode

Avoid long‑lived JWTs in localStorage/sessionStorage for highly sensitive applications, as they’re vulnerable to XSS.

3. Using localStorage and sessionStorage

These should mostly be used for non‑sensitive or low‑risk data:

  • Feature toggles.
  • UI preferences (theme, layout).
  • Last visited page or onboarding flags.

If you must store auth‑adjacent information (e.g., a non‑critical flag), consider:

// storage.ts
const THEME_KEY = 'app_theme';

export function getTheme() {
  return localStorage.getItem(THEME_KEY) ?? 'light';
}

export function setTheme(theme: string) {
  localStorage.setItem(THEME_KEY, theme);
}
Enter fullscreen mode Exit fullscreen mode

Always assume anything in these stores can be read by malicious scripts if XSS occurs.

Managing session state in React (Context pattern)

React needs a way to expose session state (user info, loading, error, login/logout methods) to the component tree. A common approach uses Context + a provider.

// AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';

type User = { id: string; email: string } | null;

type AuthContextValue = {
  user: User;
  loading: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
};

const AuthContext = createContext<AuthContextValue | undefined>(undefined);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [user, setUser] = useState<User>(null);
  const [loading, setLoading] = useState(true);

  // On mount, ask backend: "Am I logged in?"
  useEffect(() => {
    (async () => {
      try {
        const res = await fetch('/api/me', { credentials: 'include' });
        if (res.ok) {
          const data = await res.json();
          setUser(data);
        }
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  const login = async (email: string, password: string) => {
    const res = await fetch('/api/login', {
      method: 'POST',
      credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    });

    if (!res.ok) throw new Error('Login failed');
    const data = await res.json();
    setUser(data);
  };

  const logout = async () => {
    await fetch('/api/logout', { method: 'POST', credentials: 'include' });
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used within AuthProvider');
  return ctx;
}
Enter fullscreen mode Exit fullscreen mode

Components can then call useAuth() to read session state and perform login/logout.

Best practices for session management in React

  • Prefer HttpOnly cookies for storing truly sensitive tokens so that JavaScript cannot directly read them, reducing XSS impact.
  • Implement explicit session expiry on the backend and handle it gracefully in React (e.g., redirect to login or show a “session expired” banner).
  • Use token rotation for JWTs (short‑lived access tokens + rotating refresh tokens) to limit the impact of theft.
  • Protect against CSRF when using cookies:
    • Use SameSite=Lax or Strict for most cookies.
    • Use CSRF tokens for unsafe methods (POST, PUT, DELETE) if cookies are sent cross‑site.
  • Harden against XSS:
    • Avoid putting long‑lived tokens in localStorage/sessionStorage.
    • Sanitize user input and use frameworks’ XSS protections correctly.
  • Centralize auth logic:
    • Keep storage and token/ cookie handling in one module or provider.
    • Avoid scattering direct storage calls throughout components.
  • Log out correctly:
    • Clear client state (React state, storage).
    • Invalidate the session or refresh token on the server.

Wrapping up

In React, “sessions” are less about a specific browser API and more about a coordinated design across:

  • How the backend authenticates and stores session or token state.
  • Where and how the frontend stores any required identifiers or tokens.
  • How React exposes and reacts to that state across the component tree.

Choosing between cookies, JWTs, and storage should follow your security requirements, threat model, and infrastructure, not just convenience.

Top comments (0)