Monday, December 18, 2017

.net Core MVC: X-SRF-TOKEN not accepted, 400 returned

Leave a Comment

I have a .net core app using angularJS, and I want to protect the api calls protected by our cookie based authentication. I Followed the steps in this article:

https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery

  • I added the services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); to my services configuration
  • I am seeing the XSRF-TOKEN cookie in my developer tools when loading the page.
  • I am seeing the X-XSRF-TOKEN header being added to my $http sent requests.

  • I have added the [AutoValidateAntiforgeryToken] to my controller that is handling the ajax request.

  • I have ssl enabled, and am accessing the pages via https.

I can make GET requests fine through this api endpoint as expected. However, I am receiving a 400 error without any details of why the request was bad on PUTs and POSTs.

I know the X-XSRF-TOKEN is on the request, (seen in the network tab of chrome dev tools) so I am unsure what I am missing to allow these requests to be received correctly.

TL;DR: Why is .net core rejecting my valid AntiforgeryToken?

UPDATE Attempted @joey's suggested solution, but it did not work, still receiving 400 responses. code below reflects another solution I tried to fix this problem (aka angularjs's solution to setting default cookies and headers for cross site scripting protection)

I have also attempted to configure AngularJS to change what the cookie and header names match what I configured in my Startup.cs. I changed their names to try both XSRF-TOKEN (cookie) and X-XSRF-TOKEN (header) as well as CSRF-TOKEN and X-CSRF-TOKEN, and while the configurations within angular is correctly using the new default to what ever I provide, my authentication code in .net core is still not working.

for more information here is how i am configuring AngularJS:

app.config(function ($httpProvider) {     $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';     $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN'; }); 

here is the ConfigureServices line I have in my Startup.cs file:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN"); 

and lastly here is the code I added to the Configure method of the Startup.cs file:

    app.Use(next => context =>      {         string path = context.Request.Path.Value;         if (path.Contains("/MyProtectedPath/"))         {             var tokens = antiforgery.GetAndStoreTokens(context);             context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,                 new CookieOptions { HttpOnly = false });         }         return next(context);     }); 

UPDATE 2: I have added the following to my controller action:

if (HttpContext.Request.Method.ToLower(CultureInfo.InvariantCulture) != "get") {     await _antiforgery.ValidateRequestAsync(HttpContext); } 

AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user.

I thought maybe antiforgery.GetAndStoreTokens(context) might be overriding the current cookie sent on the page load, so I make it only hit that code on GET requests, but I get the same result for the posts.

2 Answers

Answers 1

In the code you have posted, you are adding the header X-XSRF-TOKEN, but the header should be X-CSRF-TOKEN.

The example from the web page you linked provides the example:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");  

Update:

Thanks for clarifying with the additional code & information. The means of implementing CSRF selected here is one which passes the token as a header on the response of the initial HTML file. Here is an example of how such a case might be configured for a SPA web application:

app.Use(next => context => {     string path = context.Request.Path.Value;     if (         string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||          string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)     )     {         var tokens = antiforgery.GetAndStoreTokens(context);         context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,              new CookieOptions() { HttpOnly = false });     }      return next(context); }); 

However, with your service configured to using the String.Contains method [1], antiforgery.GetAndStoreTokens(context) is invoked for any path containing /MyProtectedPath/ anywhere. This means it has been configured in such a way that matches not only /MyProtectedPath/, but also /MyProtectectedPath/a/b/c or /a/b/c/MyProtectedPath/.

To check the CSRF token sent in subsequent requests of an applicable HTTP method as shown below:

if (string.Equals("POST", context.Request.Method, StringComparison.OrdinalIgnoreCase)) {     await antiforgery.ValidateRequestAsync(context);     // The line above will throw if the CSRF token is invalid. } 

If the method GetAndStoreTokens is called before this for any matching path, the token will be overwritten before it is checked, which is why .net examples will typically order GetAndStoreTokens first, but with the specific condition for looking at the path and HTTP method.

[1] https://msdn.microsoft.com/en-us/library/dy85x1sa(v=vs.110).aspx

Answers 2

Joey's answer seems to be right. But since you've mentioned it doesn't work for you here's what I had to do to get it working.

First, from the docs:

AngularJS uses a convention to address CSRF. If the server sends a cookie with the name XSRF-TOKEN, the Angular $http service will add the value from this cookie to a header when it sends a request to this server. This process is automatic; you don't need to set the header explicitly. The header name is X-XSRF-TOKEN. The server should detect this header and validate its contents.

Add the AntiForgery service

Add the Antiforgery service to the service collection in Startup.ConfigureServices() after the call to AddMvc():

services.AddAntiforgery(options => {     options.HeaderName = "X-XSRF-TOKEN"; }); 

We're basically telling ASP.NET to look for the X-XSRF-TOKEN header while validating the xsrf token.

Send a cookie with the token

Now on every request from the SPA, we need to send a XSRF-TOKEN cookie with the token value. We can do that with a quick middleware:

// TODO: Refactor this to a separate middleware class app.Use(next => context => {     // TODO: Add if conditions to ensure the cookies     // are only sent to our trusted domains      // Send the token as a javascript readable token     var tokens = antiforgery.GetAndStoreTokens(context);     context.Response.Cookies.Append(         "XSRF-TOKEN",          tokens.RequestToken,          new CookieOptions() { HttpOnly = false }     );      return next(context); }); 

Validate your actions

The easiest way to validate the tokens is to add the [ValidateAntiForgeryToken] attribute. A better option is to configure MVC to apply the AutoValidateAntiforgeryToken globally for all actions with the following in Startup.ConfigureServices():

services.AddMvc(options =>      options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())); 

Read the docs on token validation.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment