Aspnetcore: AntiForgeryToken validation keeps failing

Created on 4 Jan 2018  路  11Comments  路  Source: dotnet/aspnetcore

I am developing a web API app running using asp.net core2 and Angular. The detailed development environment config is here.
I am trying to configure AntiForgeryToken validation but it keeps failing. I followed the config. here, but I had to modify it as my angular app and asp.net servers are running on two different ports because the front end startup doesn't generate the token. I kick start the backend by calling an API path (/api/Account/ContactInitialization) at the app component ngOnInit which allowed me to generate the token.
The config is shown below,

IServiceCollection Service:

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

and at IApplicationBuilder Configure:

app.Use(next => context =>
                {
                    string path = context.Request.Path.Value;
                    if (

                        string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(path, "/api/Account/ContactInitialization", StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
                    {
                        // We can send the request token as a JavaScript-readable cookie, 
                        // and Angular will use it by default.
                         var tokens = antiforgery.GetAndStoreTokens(context);
                        context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                            new CookieOptions() { HttpOnly = false });
                    }

                    return next(context);
                });

asp.net. generates two set of keys,

enter image description here

I decorated my method with [ValidateAntiForgeryToken] and angular includes XSRF-TOKEN cookie content in my header request. yet I keep receiving a 400 (Bad Request) response after calling the API! what am I missing here?

Controller Method,

    [Authorize]
    [ValidateAntiForgeryToken]
    [HttpPost]
    public IEnumerable<string> AutherizeCookie()
    {
        return new string[] { "Hello", "Auth Cookie" };
    }

my detailed header request looks like below,

POST /api/Values/AutherizeBaseController HTTP/1.1
Host: localhost:4200
Connection: keep-alive
Content-Length: 2
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: http://localhost:4200
Authorization: **Bearer** eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhaG1hZCIsImp0aSI6IjJhMTA1NzJjLWY2MWMtNGQwNi05ZjEzLThmOWZlOTUyYjQ4NyIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvIiwiaWF0IjoxNTE1MDI4MzUzLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImNhYjIxYTdlLTMwZjAtNDJhNi05NGIyLWNiYjczN2MzNmNmMyIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJhaG1hZCIsIkRpc3BsYXlOYW1lIjoiQWJ1IE1haXphciIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvc2VyaWFsbnVtYmVyIjoiZjgyZDdlZTgtOTA5ZC00MTMzLTlmZjUtMjczYTBjMzAxNDgyIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy91c2VyZGF0YSI6ImNhYjIxYTdlLTMwZjAtNDJhNi05NGIyLWNiYjczN2MzNmNmMyIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6WyJEYXRhYmFzZSBTdXBlciBVc2VyIiwiQWRtaW5pc3RyYXRvciJdLCJuYmYiOjE1MTUwMjgzNTIsImV4cCI6MTUxNTAzMTk1MiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdC8ifQ.Jhnh-vRSANR0_dDqzBOEwtG5BxI3V6pX28Zdc5IQZMw
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36
Content-Type: application/json
Referer: http://localhost:4200/dbapp
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: .AspNetCore.Antiforgery.jY5Bh2HGy8s=CfDJ8MxOovOd259BlsEMLYWAq1gbumuwD_ifl51KgQeExpEGd81dp7zarQ2mZ0C4ZexLTiPJXNSbrCR06mzi2OxqeXh4ZcrQGuo-K4yyIYG3bUhIy1RiXdA6IuIjcYP13ylyWJeNJ7FiOzm3c5AGvsF2YTk; XSRF-TOKEN=CfDJ8MxOovOd259BlsEMLYWAq1gds0ipk9X5sh3kqK20RnvPBSoo5x5EpzzGyncHjx3-PXX40Orc9NUUlpTejlL8RWTDGB9QUuOk_PCWudkYXyiqdebgcnfuQNQt_2fJkhyijawUSGsX9HmATMsZrHWvGOk; .AspNetCore.Identity.Application=CfDJ8MxOovOd259BlsEMLYWAq1gHJUrf-RKdswXaTtdaDrRIDxhC1FejiDQPMXNMUkox-DIzcIblxOgwwkg-EdQd6q0B0BQd4V5G2tTdncGJWrfqaBwbhVCCIOFNxsJZ8Px0KiSeaL_chJ27VN9M3dSnhIm30gGVN8IEzzhA8JicJUooK2O4Dr4OCzBuYvJKez4A35nnO8XFxFmncSq5vc18hhgH4-CFNKPRT24t1XgN8s_FpV2hdNtSoqMVFlKvwQaiDieM3tLRWsYnjpvDprmCr-MPTNA5oCBEVE56vOc-gViAPPBrA_AVDH0EaOBOBFVrBP2UFUOHE0wz3y8jsHIOCRNOeowyQnPsBMobywd1bmioTi2LTX0tk4FbC_Y3kE7n4BkggOQiOE-XaXifoNoxaWPQTkWtClr-hVWW1orQZ7hDUIA_VyGxsrOetEd1KMLMVQD_fGAOrBBm4qJZvGFGqq1RWgAGdjf3c15Lrpj4DrSDyF7nICyn_z2LOQkXTQ81VByrqAy4C_boSbCjAO1YlpnBKJpVkrsUPr8szj6kwilumwhSB-PXAYU7Z8nVM3XcgKIvRWDYxLqS8AQZU7-Z4roMmAYlRrqUziEcpnSL5LNJgMUaBnFESpxFpnXmyOiFEKp6-W0f02uwrRhn__eP8vidr27cANwxghDxP92V-b8Wya5Ogbqw_DMmNYweEx1jbAYAqJOhF8Jx_QFoGIUQT8EEeAqsaCNIrMVVQsTWfqRkD_t4RDOaRF5zguTmKl5r_MQcM6xyOtj-UtzhWU8RzetumCQ4i2z7-_qnCLfpuEax3BrdTGMlkVUas__Up6-l_A
area-mvc

Most helpful comment

I notice that you appear to be using a bearer token;

Authorization': `Bearer ${this.authService.getToken()}`,

If this is your sole method of authentication then CSRF protection is _not_ needed. CSRF arises when authentication automatically flows, when it's cookie based, or uses basic authentication, or kerberos. If you are specifically adding the authorization header on every request, which you can only do within javascript, then a normal POST request which comes from elsewhere isn't going to have this header added, so all attempts to call the API from outside of the web site are either going to be blocked by same origin policy, or CORS.

Now if you have an HTML form somewhere that then triggers the AJAX calls then that form would have to be protected.

All 11 comments

Is it possible that the issue is in your AddAntiforgery where you use X-CSRF-TOKEN instead of X-XSRF-TOKEN ?

@kichalla any idea about this?

Couple of things I noticed here

  • As @masterjs suggested, you are using an incorrect header name. You need to use X-XSRF-TOKEN in options (which is what angular sends on seeing the XSRF-TOKEN cookie).
  • Also the POST request you posted doesn't have the X-XSRF-TOKEN header (even though there is a XSRF-TOKEN cookie), this tells me you are making a cross site request? (based on the referrer, host and origin headers). I think this is working as expected. @Eilon, do you agree?

Ah, interesting findings, I think you are right.

@masterjs , @Eilon , @kichalla , Thank you guys for chiming in. I added X-XSRF-TOKEN header and somehow started to work again (I will explain why!). However, I am confused. according to the documentation, Angular is supposed to add the XSRF-TOKEN cookie automatically, which it does, but there is nothing mentioned about adding the X-XSRF-TOKEN header explicitly, is it because I am using HttpClient API from Angular?
I am using HttpInterceptor and I updated the code as below,

 if (token) {
        const headers = new HttpHeaders({
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.authService.getToken()}`,
            'X-XSRF-TOKEN': `${this.authService.getCookie('XSRF-TOKEN')}`
        });
        const cloneReq = req.clone({ headers });

Now, generating the token at startup based on invoking an API as mentioned above, for some reason, renders the token as invalid. so I modified the code by removing the if block.

app.Use(next => context =>
                {
                        var tokens = antiforgery.GetAndStoreTokens(context);
                        context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                            new CookieOptions()
                            {
                                HttpOnly = false,
                                Secure = true  // I am currently using it because I enforced SSL
                            });
                    return next(context);
                });

I noticed the following,

There is a kind of conflict between Antiforgery and JwtBearer Auth scheme. In my project, I am using asp Identity cookie and JwtBearer as means of auth. whenever I add JwtBearer Auth scheme to the controller, the antiforgery token is invalidated.

        [HttpPost]
        [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
        [Authorize]
        [ValidateAntiForgeryToken]
        public IEnumerable<string> AutherizeBaseController()
        {
            return new string[] { "Hello", "Auth Base controller" };
        }

The token is generated every time an HTTP event takes place, is this a normal behavior?

Token is still invalid at login, I can only use it after I login, what am I still doing wrong here?

I am confused by the details here. Can we separate the issues?

(I am assuming you are using 2.0.0 version of ASP.NET Core MVC)

Antiforgery validation runs after authentication.

Questions:

  • Are you able to make authentication work without the ValidateAntiForgeryToken?
  • Are you able to make Antiforgery work without any authentication?

@kichalla
I am using version 2 of ASP.NET Core MVC, My authentication is working fine, with either of both attributes. I tested my controller with [Authorize] or [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] or both and I receive 200 response no issues. once I add [ValidateAntiForgeryToken] the controller sometimes respond other times give me a 400 bad request.
Where is the best place to generate the token at? can I generate it at login?

  var antiForgeryToken = antiforgery.GetTokens(HttpContext).RequestToken;
                    HttpContext.Response.Cookies.Append("XSRF-TOKEN", token, new CookieOptions { HttpOnly = false, Secure = true });

I am working through a proxy too, my angular app is served on a server different from dotnet

Edit:

Today after testing ValidateAntiForgeryToken is not working

@embryologist I would like to understand your application setup clearly.

I am trying to configure AntiForgeryToken validation but it keeps failing. I followed the config. here, but I had to modify it as my angular app and asp.net servers are running on two different ports because the front end startup doesn't generate the token. I kick start the backend by calling an API path (/api/Account/ContactInitialization) at the app component ngOnInit which allowed me to generate the token.

Based on the above comment from the first post in this thread, you have 2 apps on different ports. One an asp.net webapi app and other an angular app. Your login UI from angular app posts data to the asp.net webapi app? (have you enabled CORS on the asp.net webapi app to allow requests from the angular app?)
Basically you need to refresh the XSRF-TOKEN once the user is authenticated, but I would like to understand your application setup better.

@kichalla, your questions, and comments helped a lot, now it is working.
to answer your questions. yes, it is correct, my web API is served on a different port and yes I have my CORS enabled. I think my problem was mis-config in the latter.

            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder
                        .WithOrigins("https://www.artngcore.com:4200") //Note:  The URL must be specified without a trailing slash (/).
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
            });

and I decorate my controller,

[Route("/api/[controller]/[action]")]
    [EnableCors("CorsPolicy")]
    public class AccountController : ArtCoreSecuredController ....

and what did the trick is that I call the token generating API that I mentioned above at the time of login which allows refreshing the token after login.

one question though before closing the issue,
with the following middleware config, I noticed that I am getting a newXSRF-TOKEN with every Http request made to the server, is this a normal behavior or I will be running into issues later on?
in another word, shall I generate the token only once at the time of login and stick with it?
ps, if I uncomment the code below, I get a fixed token and does not refresh at each Http request

```
app.Use(next => context =>
{
string path = context.Request.Path.Value;
// if (

                //     string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
                //     string.Equals(path, "/api/Account/ContactInitialization", StringComparison.OrdinalIgnoreCase) ||
                //     string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
                // {
                    // We can send the request token as a JavaScript-readable cookie, 
                    // and Angular will use it by default.
                     var tokens = antiforgery.GetAndStoreTokens(context);
                    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                        new CookieOptions() { HttpOnly = false, Secure = true });
                // }

                return next(context);
            });

```
Thank you

I notice that you appear to be using a bearer token;

Authorization': `Bearer ${this.authService.getToken()}`,

If this is your sole method of authentication then CSRF protection is _not_ needed. CSRF arises when authentication automatically flows, when it's cookie based, or uses basic authentication, or kerberos. If you are specifically adding the authorization header on every request, which you can only do within javascript, then a normal POST request which comes from elsewhere isn't going to have this header added, so all attempts to call the API from outside of the web site are either going to be blocked by same origin policy, or CORS.

Now if you have an HTML form somewhere that then triggers the AJAX calls then that form would have to be protected.

Closing this issue now as we do not have any action item here.

Was this page helpful?
0 / 5 - 0 ratings