|In the previous installment of this post series I talked about CSRF attack and how to prevent it using ASP.NET MVC built in components. Today I want to dive deeper into the framework code and show you what’s under the hood to anti-forgery token implementation in MVC.
Some time ago Microsoft took a huge step forward and open sourced complete ASP.NET MVC and Web API stack. Now developers can see what’s actually happens inside the framework and don’t have to rely solely on Microsoft documentation. The source code for MVC stack is located at http://aspnetwebstack.codeplex.com/.
The IT Leader's Guide to Multicloud Readiness
This guide provides practical key insights and important factors to consider to make informed decisions in your multicloud journey.
As you recall, there are two components that provide CRSF protection when used together – AntiForgeryToken methjod of Html helper (@Html.AntiForgeryToken()) which should be called from inside HTML in Razor view and ValidateAntiForgeryTokenAttribute ([ValidateAntiForgeryToken]) which should be applied to controller to validate tokens. Both of these classes are actually a thin wrappers on top of the AntiForgery class. AntiForgery class is a static class which is encapsulating all functionality for generating and validating tokens. Source code could be found there. This is a public class and could be used directly if somebody will decide to implement a custom generation and validation of anti-forgery tokens. In turn, AntiForgery is using other helper classes like AntiForgeryWorker and TokenValidator. Unlike AntiForgery these classes are internal and can’t be used directly by application code.
So, why it’s important to look into internal implementation of anti-forgery token generation and validation?
Some time ago, I run into an interesting issue: I was trying to make anti-forgery tokens work across two different application which both were residing on the same web server, on the same domains. I wanted to have a form in one of these applications to be posting to the controller method which was located in other application. And I wanted to secure the post with anti-forgery token. In theory, I should not be happening any issues with that since both applications were on the same domain and hence the cookie (with one of CSRF tokens) which is set by one application should be received fine by the other application. And I had encryption key statically configured in my system.config so it wasn’t varying by application. And yet, the controller on the receiving application was failing to validate the CSRF token posted from other application.
When I looked closely into what what was generated as anti-forgery tokens and what was expected by the receiving application I understood what’s was happening. I compared anti-forgery tokens generated by each application separately and noticed that the token cookie name which was set in one application is different than it was generated by the other. Of course, the receiving application was expecting to see a differently named cookie than the sender application was generating.
Why cookies were named differently? After some digging around, I found AntiForgeryConfig class source code and the following lines in it:
// If the app path is provided, we're generating a cookie name rather than a field name, and the cookie names should
// be unique so that a development server cookie and an IIS cookie - both running on localhost - don't stomp on
// each other.
internal static string GetAntiForgeryCookieName(string appPath)
if (String.IsNullOrEmpty(appPath) || appPath == "/")
return AntiForgeryTokenFieldName + "_" + HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(appPath));
So, MVC was adding an encoded application path to the cookie name! Of course it was different in each application.
The solution was to set AntiForgeryConfig.CookieName property to the same, fixed cookie name in both applications. That solved the issue.
In the next post in the series I’m going to talk about how to secure AJAX posts using anti-forgery tokens. Built in MVC components will not work for AJAX posts, but there is a work around…