fix(http): enable XSRF protection for same-origin absolute URLs · angular/angular@20474d3 · GitHub
Skip to content

Commit 20474d3

Browse files
alan-agius4pkozlowski-opensource
authored andcommitted
fix(http): enable XSRF protection for same-origin absolute URLs
Previously, the XSRF interceptor only added the XSRF token to requests with relative URLs. This commit updates the interceptor to also add the token to requests with absolute URLs, provided they match the current origin. (cherry picked from commit 0659d11)
1 parent 1f1856e commit 20474d3

5 files changed

Lines changed: 57 additions & 23 deletions

File tree

‎adev/src/content/guide/http/security.md‎

Lines changed: 2 additions & 2 deletions

‎adev/src/content/guide/security.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,10 +299,10 @@ The malicious code on `evil.com` can't.
299299

300300
`HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Because only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
301301

302-
By default, an interceptor sends this header on all mutating requests (such as `POST`) to relative URLs, but not on GET/HEAD requests or on requests with an absolute URL.
302+
By default, an interceptor sends this header on all mutating requests (such as `POST`) to relative and same origin URLs, but not on `GET` or `HEAD` requests.
303303

304304
<docs-callout helpful title="Why not protect GET requests?">
305-
CSRF protection is only needed for requests that can change state on the backend. By their nature, CSRF attacks cross domain boundaries, and the web's [same-origin policy](https://developer.mozilla.org/docs/Web/Security/Same-origin_policy) will prevent an attacking page from retrieving the results of authenticated GET requests.
305+
CSRF protection is only needed for requests that can change state on the backend. By their nature, CSRF attacks cross domain boundaries, and the web's [same-origin policy](https://developer.mozilla.org/docs/Web/Security/Same-origin_policy) will prevent an attacking page from retrieving the results of authenticated `GET` requests.
306306
</docs-callout>
307307

308308
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication cookie with a salt for added security.

‎packages/common/http/src/xsrf.ts‎

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {DOCUMENT, ɵparseCookieValue as parseCookieValue} from '../../index';
9+
import {DOCUMENT, ɵparseCookieValue as parseCookieValue, PlatformLocation} from '../../index';
1010
import {
1111
EnvironmentInjector,
1212
inject,
@@ -91,25 +91,28 @@ export abstract class HttpXsrfTokenExtractor {
9191
abstract getToken(): string | null;
9292
}
9393

94-
/**
95-
* Regex to match absolute URLs, including protocol-relative URLs.
96-
*/
97-
const ABSOLUTE_URL_REGEX = /^(?:https?:)?\/\//i;
98-
9994
export function xsrfInterceptorFn(
10095
req: HttpRequest<unknown>,
10196
next: HttpHandlerFn,
10297
): Observable<HttpEvent<unknown>> {
103-
// Skip both non-mutating requests and absolute URLs.
104-
// Non-mutating requests don't require a token, and absolute URLs require special handling
105-
// anyway as the cookie set
106-
// on our origin is not the same as the token expected by another origin.
107-
if (
108-
!inject(XSRF_ENABLED) ||
109-
req.method === 'GET' ||
110-
req.method === 'HEAD' ||
111-
ABSOLUTE_URL_REGEX.test(req.url)
112-
) {
98+
// Skip both non-mutating requests
99+
// Non-mutating requests generally don't require a token.
100+
if (!inject(XSRF_ENABLED) || req.method === 'GET' || req.method === 'HEAD') {
101+
return next(req);
102+
}
103+
104+
try {
105+
const locationHref = inject(PlatformLocation).href;
106+
const {origin: locationOrigin} = new URL(locationHref);
107+
// We can use `new URL` to normalize a relative URL like '//something.com' to
108+
// 'https://something.com' in order to make consistent same-origin comparisons.
109+
const {origin: requestOrigin} = new URL(req.url, locationOrigin);
110+
111+
if (locationOrigin !== requestOrigin) {
112+
return next(req);
113+
}
114+
} catch {
115+
// Handle invalid URLs gracefully.
113116
return next(req);
114117
}
115118

‎packages/common/http/test/BUILD.bazel‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ts_project(
1414
"//packages/common",
1515
"//packages/common/http",
1616
"//packages/common/http/testing",
17+
"//packages/common/testing",
1718
"//packages/core",
1819
"//packages/core/testing",
1920
"//packages/private/testing",

‎packages/common/http/test/xsrf_spec.ts‎

Lines changed: 33 additions & 3 deletions

0 commit comments

Comments
 (0)