How does Angular's HttpClient help defend against XSRF by default, and which half of the protection must the server implement?
answer
- cookie read, header written
- XSRF-TOKEN and X-XSRF-TOKEN
- only mutating, only same-origin
- cookie must be script-readable
- server compares the two
basics
~20 sA built-in interceptor copies the XSRF-TOKEN cookie into an X-XSRF-TOKEN header on same-origin requests other than GET and HEAD. The server must set that script-readable cookie and reject state-changing requests whose header is missing or does not match.
solid answer
~50 s`provideHttpClient()` registers an XSRF interceptor by default. For every request whose method is not `GET` or `HEAD` and whose URL resolves to the page's own origin, it reads the `XSRF-TOKEN` cookie from `document.cookie` and sets its value as the `X-XSRF-TOKEN` header, unless the request already has that header. That is only the client half. The server must issue the token in a cookie JavaScript can read, so not `HttpOnly`, on page load or the first `GET`, make it unique per user and verifiable, and on every state-changing request compare cookie and header, rejecting the request if either is missing or they differ. The defence works because a page on another origin can neither read your cookie nor attach it as a custom header. Angular's guide warns that without the server check the default protection is ineffective.
code
ts · 8 linesimport {ApplicationConfig} from '@angular/core';
import {provideHttpClient} from '@angular/common/http';
// XSRF handling is on by default: POST, PUT, PATCH and DELETE to the
// page's own origin get X-XSRF-TOKEN copied from the XSRF-TOKEN cookie.
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
};go deeper
Recall the names XSRF-TOKEN and X-XSRF-TOKEN and that HttpClient copies the cookie into the header for you.
Explain the skip rules, GET and HEAD and cross-origin URLs, and the server's duties: set a readable cookie and compare it with the header.
Spot the silent failures: an HttpOnly token cookie, a server that never checks, or state-changing GET endpoints that the header never covers.
Decide how XSRF defence fits the overall auth design, cookie sessions with this pattern versus bearer tokens, and who owns the server-side check.
## The attack in one sentence In **cross-site request forgery (XSRF or CSRF)**, a page on another site makes the victim's browser send a request to your API, and the browser attaches your session cookie automatically. Without an extra check, the server cannot tell that request from one your own app sent. Why the browser behaves this way is a web-platform topic; what matters here is the part Angular automates. ## What HttpClient does on the client `provideHttpClient()` always includes a built-in **XSRF interceptor** unless you turn it off. For each outgoing request it runs these checks, in order: 1. If the method is `GET` or `HEAD`, it does nothing. Every other method, such as `POST`, `PUT`, `PATCH`, `DELETE` and `OPTIONS`, continues. 2. It resolves the request URL against the page's location and compares **origins** (scheme, host and port). If they differ, it does nothing, so the token is never sent to another origin. 3. It reads the cookie named `XSRF-TOKEN` from `document.cookie`. If there is none, it does nothing. 4. If the request does not already carry an `X-XSRF-TOKEN` header, it clones the request with that header set to the cookie's value. A few details are worth knowing: - The interceptor **never overwrites** a header of the same name that your code set explicitly. - During **server-side rendering** the extractor returns no token, so requests made on the server carry no XSRF header. - The names are defaults only: `withXsrfConfiguration` changes them and `withNoXsrfProtection` removes the interceptor. | Request | Header added? | | :--- | :--- | | `POST /api/transfers` (relative) | yes, if the cookie exists | | `GET /api/accounts` | no: `GET` is skipped | | `DELETE https://app.example.com/api/x` from `https://app.example.com` | yes: same origin | | `POST https://api.example.com/x` from `https://app.example.com` | no: different origin | ## What the server must do Angular's guide is explicit that `HttpClient` implements **only the client half**. The server has to: - **Issue the token** in a cookie named `XSRF-TOKEN` (or your configured name) on page load or on the first `GET`. It must be readable by JavaScript, so it cannot be `HttpOnly`. - **Make the token unique per user and verifiable by the server**, so a client cannot invent one. The guide suggests a salted digest of the site's authentication cookie. - **Check every state-changing request**: compare the cookie value with the header value and reject the request if either is missing or they differ. - **Keep safe methods safe**: because `GET` and `HEAD` carry no header, endpoints reached by those methods must not change state. If the server never sets the cookie, the interceptor silently sends nothing; if it sets the cookie but never checks, the header is decoration. Either way the app looks protected and is not. ## Why the pairing works A hostile page can make the victim's browser send the cookie, but it cannot **read** it, because cookies for your site are only visible to script running on your site, and it cannot add a custom header to a cross-site request without the server's CORS permission. Only your own code can copy the cookie into the header, so a matching header proves the request came from your app. The finer points of this cookie-to-header pattern, and the attacks that defeat it, belong to CSRF theory rather than to Angular. ## Common failure modes - **The cookie never arrives.** The server sets it only after login, or on a path the app's pages do not match, so the first mutating request has no token. - **The cookie is `HttpOnly`.** It is sent to the server but invisible to `document.cookie`, so HttpClient never finds it. - **The request is cross-origin.** A separate API host or port is skipped by design, and the server's check fails with a `403`. - **The names differ.** The server issues `CSRF-TOKEN` and expects `X-CSRF-TOKEN`, while HttpClient looks for the defaults. ## Configuration notes - When several Angular apps share a domain, the guide recommends giving each app a **unique cookie name** so their tokens do not collide. - `HttpClientXsrfModule`, the NgModule way to configure this, is deprecated; the current API is the pair of `provideHttpClient` features. ## Interview checklist - Name the cookie, the header and the two skip rules (safe methods, other origins). - State that the server issues the readable cookie and validates the header. - Mention that the header is never overwritten and never sent cross-origin.
- Why does Angular's HttpClient not add the XSRF header to GET requests?Because XSRF protection is only needed for requests that change state, and a cross-site page cannot read the response of a forged `GET` thanks to the same-origin policy. The corollary is a server rule: endpoints reached by `GET` or `HEAD` must never change state, or they are unprotected.
- The XSRF-TOKEN cookie is set with HttpOnly for extra safety. What happens?HttpOnly hides the cookie from `document.cookie`, so HttpClient's extractor finds no token and silently sends no header. Every protected request then fails the server's check. The token cookie must be script-readable; the session cookie is the one that should be HttpOnly.
- If your code sets X-XSRF-TOKEN on a request itself, does the interceptor replace it?No. The interceptor only adds the header when the request does not already have one with the configured name, so an explicitly set value is sent unchanged. That also means a stale value set by hand would win over the current cookie.
saying these in an interview costs you the question
- HttpClient's XSRF support protects the app with no server changes.
- The XSRF-TOKEN cookie should be HttpOnly so script cannot read it.
- HttpClient sends the XSRF header on every request, including GET.
- HttpClient adds the token to requests for any origin the app calls.
- The interceptor overwrites an X-XSRF-TOKEN header set by application code.