CSRF protection for SPAs
This section aims to explain how any single-page application (SPA) can be protected from Cross-Site Request Forgery (CSRF) attacks by using the CSRF Token feature of the Security Gate.
Enable CSRF tokens on the Tab – CSRF Tokens of the corresponding mapping to protect your application against CSRF attacks.
CSRF blocking based on CSRF tokens
The Security Gate will block any state-changing request with a missing or invalid CSRF token. There are legitimate use cases for which the CSRF token is no longer valid. Typical examples are session timeouts or terminated sessions. In those cases, the gateway's response to a CSRF block might be inappropriate for a SPA.
Blocking responses:
- If no Invalid token redirect location was configured, the response is
400 Bad Request
with a generic HTML error page. SPAs typically do not expect HTML content in API responses. - Otherwise, the gateway's response is
303 See Other
with a location header pointing towards the configured Invalid token redirect location. Redirects cannot be handled by SPAs based on XMLHttpRequest (XHR), such as Angular, and will be directly handled by the web browser.
The following steps are needed for the SPA to handle this situation more transparently:
- Recognize that a CSRF block occurred.
- Retrieve a new CSRF token.
- Replay the original request with the new CSRF token.
Changing the gateway response for CSRF blocks
Set the invalid token redirect location in the CSRF Tokens tab on the corresponding mapping to /%ENTRYPATH%/invalid/csrf-token
.
Note that the exact path is not important as long as the configured value does not collide with any URL used in your application.
To ensure that the SPA can handle the gateway's response, transform the redirect into for example a 403 Forbidden
.
To do so, set the following Apache Expert Settings on the corresponding mapping:
RewriteEngine On RewriteRule "invalid/csrf-token" "-" [F]
The path in the rewrite rule must match the previously configured invalid token redirect location. With this rewrite rule, any request to /%ENTRYPATH%/invalid/csrf-token
will be responded to by a 403 Forbidden
.
The changes above ensure that in case of a CSRF block:
- The user's session ID and CSRF token will be renewed. These new values are delivered as cookies in the gateway's response (
303 See Other
towards/%ENTRYPATH%/invalid/csrf-token
) to the CSRF block. - The user's browser follows the redirect and effectuates a
GET /%ENTRYPATH%/invalid/csrf-token
which is answered by a403 Forbidden
. This error is propagated to your SPA which can then handle it appropriately.
Intercepting in your SPA gateway response to CSRF Blocks
The exact syntax to intercept an HTTP request or response depends on the exact framework used by your SPA.
The following example has been successfully tested with Angular 11 and 12. It assumes that the SPA uses the Angular framework and implements the HttpInterceptor
interface:
Example of a CSRF Block Interceptor in Angular:
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {Observable} from 'rxjs'; import {catchError} from 'rxjs/operators'; import {Injectable} from '@angular/core'; @Injectable() export class CsrfBlockInterceptor implements HttpInterceptor { intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req).pipe( catchError((error: HttpErrorResponse) => { if (this.isCsrfBlock(error)) { return this.replayRequest(req, next); } return this.anyOtherInternalErrorHandling(error); }) ); } private isCsrfBlock (error: HttpErrorResponse): boolean | undefined { //the end of the url needs to match the one configured as "Invalid token redirect location" return error.status === 403 && error.url?.endsWith('/invalid/csrf-token'); } private replayRequest (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(req); } private anyOtherInternalErrorHandling (error: HttpErrorResponse) { return observableThrowError(error); } }
Sequences of requests and responses
Note that the normal sequence of requests and responses between your SPA and the Security Gate is not modified in any way by our exemplary configuration as long as no CSRF block occurs.
When a CSRF block occurs, then an extra round-trip between your application and the gateway is made to handle the CSRF block transparently. This additional delay should be acceptable for most applications since a CSRF block typically occurs at most once per user session.
In case a CSRF block occurs, the sequence of requests and responses with our exemplary configuration is depicted below. Note that only relevant headers are displayed.
- (A) Your SPA does some state-changing requests (here a
POST
request) with an invalid CSRF token. This is for example the case when the user session is no longer valid (possibly due to a timeout). - (B) The Security Gate verifies the provided CSRF token and considers it to be invalid since the corresponding session is invalid. This triggers a CSRF block.
The gateway responds with a redirect towards the configured Invalid token redirect location and provides via cookies a new session ID and CSRF token. - (C) The redirect is not propagated to your SPA but is instead directly handled by the user's web browser, which issues a
GET
request towards the configured invalid token redirect location. - (D) The configured mapping settings ensure that any request towards the configured invalid token redirect location will be answered by
403 Forbidden
. - (E) The
403 Forbidden
response is propagated to your SPA, which intercepts it and recognizes from the request URL in (C) that a CSRF block occurred.
The SPA can then replay the original request from (A) transparently using the new session ID and CSRF token. The script initially injected by the Security Gate will automatically inject the CSRF token to the appropriate header.
Further information and links
- Pre-configured mapping templates are included in Airlock IAM starting with IAM 7.5. See Configuration of IAM mappings
- What are single-page applications (SPAs)?