3

A team i'm working on has a UI and an API on two different domains x-ui.app.example.com and x-api.app.example.com. This architecture obviously doesn't work with the traditional means of XSRF in Angular / .NET Core.

The traditional way that it has been described is:

  1. When x-api receives it's first request, or when x-ui calls a certain endpoint, x-api will generate an 'XSRF-TOKEN' cookie, described here: https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.2
  2. x-ui will read this cookie on later data-manipulating requests, such as a POST, and will include the contents of the 'XSRF-TOKEN' cookie in a request header: 'X-XSRF-TOKEN'

This doesn't work for me since there are two different subdomains, which means that the UI is not allowed to read the cookie created by the x-api subdomain. I know that the correct thing to do is collapse the domain into a single ui/api domain and then everything would work magically, however, due to time constraints, or assuming that I can't do that, I could work around this by adding the 'XSRF-TOKEN' cookie to the app.example.com domain:

context.Response.Cookies.Append("XSRF-TOKEN", token, new CookieOptions
{
    HttpOnly = false,
    IsEssential = true,
    Secure = true,
    SameSite = SameSiteMode.None,
    Domain = "app.example.com"
});                   

This gives the UI access to the cookie since it's in the parent domain. However I don't want to do this since that would mean any other apps created in app.example.com would be able to see the cookie as well, and that's just rude.

The other way I was considering doing it, was to send the XSRF-TOKEN back in a response header from the initial call, at which point the UI would be responsible for saving the content as a cookie on its own domain, and then subsequent requests could use that cookie.

My question is: is this an acceptable way of doing XSRF? Is there any sort of vulnerability by passing back the XSRF-TOKEN in a response header from the API and then storing it as a cookie in the UI?

It would look something like this:

.NET Core

app.Use(next => context =>
    {
        if (context.Request.Method == HttpMethods.Get)
        {
            var token = antiforgery.GetAndStoreTokens(context).RequestToken;
            context.Response.Headers.Append("XSRF-TOKEN", token);
        }

        return next(context);
    });
CorsPolicyBuilder corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.WithExposedHeaders(new string[]{ "XSRF-TOKEN" });
corsBuilder.WithOrigins("http://localhost:4200", "null", "x-ui.app.example.com");
corsBuilder.AllowCredentials();
services.AddCors(options => { options.AddPolicy("AllowAll", corsBuilder.Build()); });

And the Angular Interceptor: (Note I'd add more checks in here to see whether the cookie was already set so it wouldn't happen on every single GET request, but to get the point across).

import {
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpEvent,
  HttpXsrfTokenExtractor, HttpResponse
} from '@angular/common/http';
import { Observable } from 'rxjs';
import {Injectable} from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { tap } from 'rxjs/operators';

@Injectable()
export class HttpXsrfInterceptorService implements HttpInterceptor {
  private cookieValue: string;
  constructor(private tokenExtractor: HttpXsrfTokenExtractor, private cookieService: CookieService) {}
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.method === 'GET') {
      return next.handle(req).pipe(
        tap(evt => {
          if (evt instanceof HttpResponse) {
            console.log(evt.headers.get('XSRF-TOKEN'));
            this.cookieService.set( 'XSRF-TOKEN', evt.headers.get('XSRF-TOKEN'), null, '/', null, false, 'Lax' );
            this.cookieValue = this.cookieService.get('XSRF-TOKEN');
          }
        })
      );
    }

    // We will just forward HEAD, and OPTIONS requests on as usual, without
    // trying to append the X-XSRF-TOKEN header, since XSRF validation isn't done
    // on these methods anyways.
    if (req.method === 'HEAD' || req.method === 'OPTIONS' ) {
      return next.handle(req);
    }

    let requestToForward = req;
    const headerName = 'X-XSRF-TOKEN';

    // Try to extract the 'XSRF-TOKEN' cookie from the request.
    const token = this.tokenExtractor.getToken() as string;

    // If we successfully retrieved a token, we will set a header called 'X-XSRF-TOKEN' which
    // will be used for XSRF validation on the API.
    if (token !== null && !req.headers.has(headerName)) {
      requestToForward = req.clone({setHeaders: {'X-XSRF-TOKEN': token}, withCredentials: true });
    }
    return next.handle(requestToForward);
  }
}
cdhawke1
  • 31
  • 4
  • Just clarifying: Adding XSRF token to the cookie, beats the purpose of it. The protection XSRF token provides is - the attacker is never able to set this header. The attacker can always successfully make the victim make a network request from attacker's website. Attacker's malicious website having something like http://bank.com/transfer=5$&source=victim&dest=attacker So here the victim cookie's will be sent to bank site, but the malicious website won't be able to set XSRF token, hence the protection Have you looked on the same site cookie attribute, which is a new way protection? – Shivankar Sep 04 '19 at 21:03

0 Answers0