Search code examples
angularasp.net-corecsrf

In an ASP.NET Core AntiforgeryTokenSet, what's the difference between the usage of RequestToken and CookieToken?


We have an ASP.NET Core 2.2 web application with an Angular SPA using cookie authentication.

I'm following the documentation to configure antiforgery features with IAntiforgery.

The relevant code snippets are:

services.AddAntiforgery();
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
    app.Use(next => context =>
    {
        string path = context.Request.Path.Value;

        if (
            string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
        {
            // The request token can be sent as a JavaScript-readable cookie, 
            // and Angular uses it by default.
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                new CookieOptions() { HttpOnly = false });
        }

        return next(context);
    });
}

The call to antiforgery.GetAndStoreTokens(context) returns an AntiforgeryTokenSet which has RequestToken and CookieToken properties.

If I use the code above with the default configuration, I get two cookies: .AspNetCore.Antiforgery.* (matching CookieToken) and XSRF-TOKEN (matching RequestToken), with different values.

What's the difference in usage between the RequestToken and CookieToken


Solution

  • The reason there are two cookies is that ASP.NET Core uses the Double Submit Cookie pattern, described in the OWASP Cross-Site Request Forgery (CSRF) Cheat Sheet.

    This excellent article, ASP.NET Core CSRF defence with Antiforgery describes the process in much more detail than the Microsoft documentation:


    ...it provides a stateless defence mechanism composed of 2 items (or token set) that should be found on any request being validated by the Antiforgery package:

    • An antiforgery token included as a cookie, generated as a pseudorandom value and encrypted using the new Data Protection API

    • An additional token included either as a form field, header or cookie. This includes the same pseudorandom value, plus additional data from the current user’s identity. It is also encrypted using the Data Protection API.

    These tokens will be generated server-side and propagated along with the html document to the user’s browser. The cookie token will be included by default whenever the browser sends a new request while the application needs to make sure the request token is also included. (We will see in the next sections how to do this)

    A request will be then rejected if:

    • any of the 2 tokens is missing or have an incorrect format/encryption
    • their pseudorandom values are different
    • the user data embedded in the second token doesn’t match the currently authenticated user

    In the case of Angular, you will be using their $http service for sending AJAX requests. This service will automatically include a header with the name X-XSRF-TOKEN if it can find the token value as a cookie with the name XSRF-TOKEN. So the easiest way is to play the way Angular wants us to, and create some middleware that will get the request token, and store its value as the XSRF-TOKEN cookie.

    Even if it is added as a cookie, this is still the request token and not the cookie token! It might sound confusing, so let me try to clarify it:

    The application will send back to the browser a cookie XSRF-TOKEN with the request token and another cookie .AspNetCore.Antiforgery.* with the cookie token. Whenever Angular sends an Ajax request, the request will include a header X-XSRF-TOKEN with the request token and the cookie .AspNetCore.Antiforgery.* with the cookie token. The Antiforgery validation will make sure that both tokens are valid and share the same secret, etc.


    So there should be two cookies. In my scenario, the .AspNetCore.Antiforgery.* cookie, sent with every request makes up one half of the token set, and then the XSRF-TOKEN is used by Angular to set the X-XSRF-TOKEN header which makes up the other half of the token set.


    See also: