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 nameXSRF-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 isX-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()));
0 comments:
Post a Comment