CSRF Protection
Cross-site request forgery (CSRF, sometimes spelled XSRF) tricks a logged-in user’s browser into firing an unwanted state-changing request to your server. Because cookies are sent automatically with every request to their origin, an attacker’s page can submit a form or fire a request to your API and ride on the victim’s authenticated session. Angular’s HttpClient ships with built-in support for the most common defence — the double-submit cookie pattern — and wiring it up correctly takes only a few lines.
How the attack works
Imagine a banking app that authenticates users with a session cookie. A user logs in, then visits a malicious site in another tab. That site contains a hidden form that POSTs to https://bank.example/transfer. The browser dutifully attaches the bank’s session cookie because the request targets the bank’s origin, and the transfer succeeds — the user never clicked anything meaningful.
The root cause is that cookies are ambient: the browser sends them based on the destination, not on who initiated the request. CSRF defences work by requiring a secret value that an attacker’s cross-origin page cannot read or guess.
The double-submit token pattern
Angular’s HttpClient implements the double-submit cookie pattern. The server sets a cookie containing a random token, and the client reads that cookie and echoes its value back in a custom request header. An attacker cannot read your cookies from a different origin (the same-origin policy forbids it), so they cannot forge the matching header. The server accepts a request only when the header value matches the cookie value.
HttpClient does the client half automatically: on every mutating request it reads a token from a cookie and copies it into a header. By default it looks for a cookie named XSRF-TOKEN and sends a header named X-XSRF-TOKEN.
The token is sent only for mutating verbs —
POST,PUT,PATCH,DELETE— and only for relative or same-origin URLs.GETandHEADrequests, and requests to absolute cross-origin URLs, never receive the header. This prevents leaking your token to third-party APIs.
Enabling XSRF protection
When you bootstrap a standalone app, register HttpClient with the withXsrfConfiguration feature (or rely on the defaults, which provideHttpClient already enables). This is where you customise the cookie and header names to match your backend.
import { bootstrapApplication } from '@angular/platform-browser';
import {
provideHttpClient,
withXsrfConfiguration,
} from '@angular/common/http';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN',
}),
),
],
});
With this in place, an ordinary HttpClient call needs no special handling — the interceptor that ships with the XSRF feature adds the header for you.
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-transfer',
standalone: true,
template: `<button (click)="transfer()">Send $100</button>`,
})
export class TransferComponent {
private http = inject(HttpClient);
transfer() {
// The X-XSRF-TOKEN header is attached automatically
this.http.post('/api/transfer', { amount: 100 }).subscribe();
}
}
The outgoing request now carries the token copied from the cookie:
Output:
POST /api/transfer HTTP/1.1
Content-Type: application/json
Cookie: XSRF-TOKEN=9f3c1a...; session=abc123
X-XSRF-TOKEN: 9f3c1a...
Configuration options
The withXsrfConfiguration feature accepts two options that must agree with what your server produces and expects.
| Option | Default | Purpose |
|---|---|---|
cookieName | XSRF-TOKEN | Name of the cookie HttpClient reads the token from |
headerName | X-XSRF-TOKEN | Name of the header the token is copied into |
If your backend does not use CSRF tokens at all, you can opt out entirely with withNoXsrfProtection():
import { provideHttpClient, withNoXsrfProtection } from '@angular/common/http';
provideHttpClient(withNoXsrfProtection());
What the server must do
Angular only handles the client side. The backend is responsible for the other half of the contract:
- On a safe request (such as serving the app shell), set the token cookie — for example
XSRF-TOKEN— with a cryptographically random, per-session value. It must not beHttpOnly, because JavaScript needs to read it. - On every mutating request, compare the value in the
X-XSRF-TOKENheader against the expected token and reject mismatches with403 Forbidden.
The token cookie must be readable by JavaScript, so do not set
HttpOnlyon it. Your session cookie, by contrast, should stayHttpOnlyandSecure. Pair this withSameSite=LaxorStricton session cookies for defence in depth.
A note on token-based auth
If your app authenticates with a bearer token stored in memory (an Authorization: Bearer ... header) rather than a cookie, classic CSRF largely does not apply — an attacker’s page cannot read your in-memory token or attach the header, and the browser does not send it ambiently. CSRF protection matters most when authentication rides on cookies that the browser sends automatically.
Best Practices
- Keep XSRF protection enabled (the default) whenever your app authenticates via cookies.
- Make sure
cookieNameandheaderNameinwithXsrfConfigurationexactly match your server’s configuration — a mismatch silently drops the header. - Issue a fresh, unpredictable token per session and validate it server-side on every mutating request; never trust the client alone.
- Set
SameSite=Lax(orStrict) andSecureon session cookies as a complementary layer to the token check. - Use relative or same-origin URLs for your API so
HttpClientactually attaches the token; absolute cross-origin URLs are deliberately skipped. - Prefer storing auth tokens in memory over cookies when feasible to sidestep CSRF for that surface entirely.