Cross-Site Request Forgery (CSRF) protection for SPAs

This section aims to explain how any single-page application (SPA) can be protected from 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 a 403 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:

Show moreShow less

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.

redirects-during-a-csrf-block-8924d185a3bb27b1d7533aa9d14d9bf6ed5654b623141d21b404df431c9092f0
  1. (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).
  2. (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.
  3. (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.
  4. (D) The configured mapping settings ensure that any request towards the configured invalid token redirect location will be answered by 403 Forbidden.
  5. (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.