Networking in React — how the browser talks to servers, and how to secure it with HttpOnly cookies + CSRF tokens
1. What “networking” means in a React app (very short)
Networking = how your browser-based React app communicates with servers over HTTP(S). Typical flow:
Key building blocks: URLs, HTTP methods (GET/POST/PUT/PATCH/DELETE), headers, body, cookies, tokens, and CORS rules.
2. HTTP methods — when to use which
Always use the method that matches intent (helps caching, semantics, and security).
3. Headers & common headers you must know
4. Cookies vs Token-in-header (short)
Best modern recommendation (web apps): store authentication JWT in an HttpOnly cookie and protect write requests with a CSRF token.
5. CORS basics
When frontend origin ≠ backend origin, server must set CORS headers and allow credentials if you send cookies:
Server must include:
Access-Control-Allow-Origin: https://your.frontend.origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PATCH,PUT,DELETE
Access-Control-Allow-Headers: Content-Type, X-CSRF-TOKEN, Authorization
On the client fetch:
fetch(url, { credentials: 'include' })
6. The secure pattern: JWT in HttpOnly cookie + CSRF token
Why?
High-level flow:
Recommended by LinkedIn
7. Step-by-step implementation (Node.js/Express + React)
Two example approaches for sending the CSRF token to the client:
Backend (Node.js + Express) — minimal example
Install: npm i express cookie-parser cors jsonwebtoken
// server.js (simplified)
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
const app = express();
app.use(express.json());
app.use(cookieParser());
// configure CORS to allow the frontend origin and credentials
app.use(cors({
origin: 'http://localhost:3000', // your React app origin
credentials: true
}));
const JWT_SECRET = process.env.JWT_SECRET || 'changeme';
// helper to generate secure CSRF token
function generateCsrfToken() {
return crypto.randomBytes(32).toString('hex');
}
// LOGIN endpoint: verify credentials, set HttpOnly JWT cookie, return csrf token in body
app.post('/login', (req, res) => {
const { email, password } = req.body;
// --- validate credentials here ---
const user = { id: 1, email }; // example
const jwtPayload = { sub: user.id, email: user.email };
const token = jwt.sign(jwtPayload, JWT_SECRET, { expiresIn: '15m' }); // short lived access token
const csrfToken = generateCsrfToken();
// Set HttpOnly access_token cookie
res.cookie('access_token', token, {
httpOnly: true,
secure: true, // set to true in production (HTTPS)
sameSite: 'None', // None if cross-site; Lax/Strict per app
maxAge: 15 * 60 * 1000 // 15 minutes
});
// Optionally set a non-HttpOnly cookie with csrf so you can read it directly from cookie in client
// res.cookie('csrf_token', csrfToken, { secure:true, sameSite:'None' });
// Send CSRF to client in JSON (client stores it)
res.json({ csrfToken });
});
// Protected endpoint example (requires CSRF match and valid JWT)
app.patch('/profile', (req, res) => {
const csrfFromHeader = req.get('X-CSRF-TOKEN');
// If you also stored csrf server-side (e.g., in redis/session), compare to that.
// For demo, we expect the CSRF token to be sent in a second cookie or client-supplied header and matched server-side.
// If you used cookie 'csrf_token' you could compare req.cookies.csrf_token === csrfFromHeader.
const csrfFromCookie = req.cookies['csrf_token']; // if you used cookie approach
if (csrfFromCookie && csrfFromHeader !== csrfFromCookie) {
return res.status(403).json({ message: 'Invalid CSRF token' });
}
// If you didn't store csrf in cookie, you would store csrf server-side for the session and compare.
// Next: verify JWT from HttpOnly cookie
const token = req.cookies['access_token'];
if (!token) return res.status(401).json({ message: 'Unauthenticated' });
try {
const payload = jwt.verify(token, JWT_SECRET);
// proceed with update...
return res.json({ message: 'Profile updated', userId: payload.sub });
} catch (err) {
return res.status(401).json({ message: 'Invalid token' });
}
});
app.listen(5000, () => console.log('Server running on 5000'));
Notes:
Frontend (React + fetch)
Login — receive CSRF token and store it in memory/localStorage:
// auth.js
export async function login(email, password) {
const res = await fetch('http://localhost:5000/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // important: allow server to set cookie
body: JSON.stringify({ email, password })
});
const data = await res.json();
// save CSRF token safely — in memory is best; localStorage is acceptable with caution
localStorage.setItem('csrfToken', data.csrfToken);
}
Send authenticated, CSRF-protected request:
export async function updateProfile(payload) {
const csrf = localStorage.getItem('csrfToken'); // or from memory / react context
const res = await fetch('http://localhost:5000/profile', {
method: 'PATCH',
credentials: 'include', // sends HttpOnly JWT cookie automatically
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrf
},
body: JSON.stringify(payload)
});
if (!res.ok) {
// handle errors (401, 403, etc.)
throw new Error('Request failed');
}
return res.json();
}
Logout — clear CSRF token and tell backend to clear cookie:
export async function logout() {
await fetch('http://localhost:5000/logout', {
method: 'POST',
credentials: 'include',
});
localStorage.removeItem('csrfToken');
}
8. Where to store the CSRF token on the client
9. CSRF token generation & validation patterns
10. Token expiry & refresh
11. Common pitfalls & security checklist