Table of Contents
Today I had to help one of my developers who was experiencing a strange issue in an ASP.NET MVC Application (based upon .NET Framework 4.8) he was debugging.
The error was happening right after the POST request of the website Login view - which was a standard HTML Form pointing to an [HttpPost] Action method protected with a standard [ValidateAntiForgeryToken] attribute. Once filling up the form and clicking the Login button he was hit by the following exception:
System.Web.WebPages - Void ValidateTokens(System.Web.HttpContextBase, System.Security.Principal.IIdentity, System.Web.Helpers.AntiXsrf.AntiForgeryToken, System.Web.Helpers.AntiXsrf.AntiForgeryToken)
in System.Web.Helpers.AntiXsrf.TokenValidator.ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken)
in System.Web.Helpers.AntiXsrf.AntiForgeryWorker.Validate(HttpContextBase httpContext)
in System.Web.Helpers.AntiForgery.Validate()
in System.Web.Mvc.ValidateAntiForgeryTokenAttribute.OnAuthorization(AuthorizationContext filterContext)
in System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor)
in System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass21.<BeginInvokeAction>b__19(AsyncCallback asyncCallback, Object asyncState)
Oddly enough, the error was triggered only when the project was executed in debug mode on any developer machine: conversely, on any production server there were no issues and the web application was working just fine.
The problem
Long story short, it turned out that such exception was caused by a cookie configuration issue, which hindered the ASP.NET anti-forgery token validation. To better clarify that, we need to take a step back and be sure to understand what anti-forgery tokens are and how they are implemented within the ASP.NET MVC Framework.
CSRF attacks
In case you don't know it already, anti-forgery tokens are a security mechanism designed to defend against cross-site request forgery (CSRF) attacks. If you want to read the whole story, we strongly recommend checking out the CSRF Wikipedia page, which contains a great explanation and valid references to better understand such a threat. To briefly summarize it, a CSRF attack happens when someone "steals" (or "rides", or "forges" a copy of) a trusted HTTP session belonging to another unaware user (the victim), and uses it to access and/or alter the data sent and received by the website itself.
Anti-Forgery Tokens
The anti-forgery token concept has been designed to overcome this kind of scenario and works in the following way: when you send a form to the user, you add an extra hidden field that includes one half of a cryptographic token. Additionally, a cookie is set with the other half of the token. When the form is posted to the server, the two halves of the token are verified to check if the request is legit or not.
In our specific scenario, we were getting the exception because the website was configured because our cookie had the Secure flag on (requireSSL=true in the web.config file, as explained here). Such behavior was working fine our production servers since they are configured to only accept HTTPS requests (and to redirect non-HTTPS ones to HTTPS using a dedicated IIS Rewrite rule), but couldn't work on local debugging sessions which are based on IIS Express and a simple HTTP binding
The solution
Once we acknowledged the issue, understanding the fix was easy enough. We just set the requireSSL to FALSE in the web.config file...
1 2 |
<!-- IMPORTANT: requireSSL flag will be set to TRUE in release - see web.Release.config --> <httpCookies httpOnlyCookies="true" requireSSL="false" /> |
...and added the following xdt:Transform feature to the web.Relase.config file:
1 2 |
<!-- Set Cookies Secure flag ON in release builds --> <httpCookies httpOnlyCookies="true" requireSSL="true" xdt:Transform="Replace" /> |
That fixed our issue for good, allowing us to overcome the exception and properly debug the web application without compromising the security measures of the production build.
Conclusion
That's it, at least for the time being. I hope that this post will help other developers struggling with this nasty issue: see you next time!