gotchatypescriptreactMajor
CSRF protection in React forms — reading and sending the token
Viewed 0 times
CSRFX-CSRFTokenXSRF-TOKENdouble-submit cookiecredentials includesecurity formcross-site request forgery
Error Messages
Problem
Forms that use session-based authentication are vulnerable to Cross-Site Request Forgery (CSRF). Without a CSRF token, a malicious page can trigger authenticated requests on behalf of a logged-in user. SPAs commonly forget to include the CSRF token in AJAX requests.
Solution
Read the CSRF token from a cookie or meta tag and include it in request headers:
// Method 1: Read from a meta tag set by the server
function getCsrfToken(): string {
const meta = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]');
if (!meta) throw new Error('CSRF token meta tag not found');
return meta.content;
}
// Method 2: Read from a cookie (double-submit cookie pattern)
function getCsrfCookie(name = 'csrftoken'): string {
const match = document.cookie.match(new RegExp(
return match ? decodeURIComponent(match[2]) : '';
}
// Centralised fetch wrapper that adds the token to mutating requests
async function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
const method = (options.method ?? 'GET').toUpperCase();
const isMutating = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
const headers = new Headers(options.headers);
if (isMutating) {
headers.set('X-CSRFToken', getCsrfCookie());
// OR: headers.set('X-XSRF-TOKEN', getCsrfCookie('XSRF-TOKEN')); // Axios convention
}
return fetch(url, { ...options, credentials: 'include', headers });
}
// Usage in a form submit
const onSubmit = async (data: FormData) => {
await apiFetch('/api/profile', {
method: 'PUT',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
};
// Method 1: Read from a meta tag set by the server
function getCsrfToken(): string {
const meta = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]');
if (!meta) throw new Error('CSRF token meta tag not found');
return meta.content;
}
// Method 2: Read from a cookie (double-submit cookie pattern)
function getCsrfCookie(name = 'csrftoken'): string {
const match = document.cookie.match(new RegExp(
(^| )${name}=([^;]+)));return match ? decodeURIComponent(match[2]) : '';
}
// Centralised fetch wrapper that adds the token to mutating requests
async function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
const method = (options.method ?? 'GET').toUpperCase();
const isMutating = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
const headers = new Headers(options.headers);
if (isMutating) {
headers.set('X-CSRFToken', getCsrfCookie());
// OR: headers.set('X-XSRF-TOKEN', getCsrfCookie('XSRF-TOKEN')); // Axios convention
}
return fetch(url, { ...options, credentials: 'include', headers });
}
// Usage in a form submit
const onSubmit = async (data: FormData) => {
await apiFetch('/api/profile', {
method: 'PUT',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
});
};
Why
The CSRF token is a secret known only to the legitimate frontend (via cookie or meta tag). Attacker-controlled pages cannot read first-party cookies, so they cannot forge the token. The server validates the token and rejects requests without it.
Gotchas
- credentials: 'include' must be set in fetch for cookies to be sent on cross-origin requests — same-origin requests include cookies by default
- The CSRF cookie must NOT be HttpOnly — the JavaScript needs to read it
- Axios automatically reads and sends the XSRF-TOKEN cookie as X-XSRF-TOKEN — no manual setup needed if using Axios with Django or Rails
- SameSite=Strict or SameSite=Lax cookies provide additional protection but are not a complete substitute for CSRF tokens
Revisions (0)
No revisions yet.