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:
- User logs in with credentials via a React form.
- 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.
- React:
- Does not need direct cookie access; it just calls APIs with
credentials: 'include'onfetchor appropriate config in Axios. - Uses an auth context or state to reflect “logged in” vs “logged out” based on API responses.
- Does not need direct cookie access; it just calls APIs with
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');
}
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;
}
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);
}
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;
}
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=LaxorStrictfor most cookies. - Use CSRF tokens for unsafe methods (POST, PUT, DELETE) if cookies are sent cross‑site.
- Use
-
Harden against XSS:
- Avoid putting long‑lived tokens in
localStorage/sessionStorage. - Sanitize user input and use frameworks’ XSS protections correctly.
- Avoid putting long‑lived tokens in
-
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)