In my last couple of projects we have used a combination of MVC, AngularJS and Web API. We have used MVC Bundling and minification, MVC layout file to provide a master page and authorization on page level. Each of our pages has been implemented using AngularJS. The applications has been implemented as a collection of SPA applications. MVC is delivering the skeleton of each page/SPA while AngularJS is used to provide dynamic features within each page. In this post I will go into the details on how we are combining MVC and AngularJS to implement Anti-Forgery tokens used to secure our Web API against Cross-Site Request Forgery (CSRF) Attacks.
Cookie based authentication or what we used to call Forms authentication is a simple and well known authentication mechanism for ASP.NET web sites. If we expect our Web API to only be called from the pages of the same site we can use the same cookie authentication to secure our Web API as well. There is one catch though, the ValidateAntiForgeryToken attribute that we would use on Post actions of our MVC controllers to prevent CSRF has no counterpart in Web API.
Mike Wasson describes in an article on the ASP.NET site how to include a token in custom headers of every ajax call. It works, but using AngularJS, a Html helper and a custom action attribute we can make it all much nicer.
Setting the header once using AngularJS $http service
The $http service has a really nice feature in that we can set common headers. These will be used in all subsequent requests once set. This also includes any requests done using the more high level $resource service. If the header has been set using $http they will be included also in by $resource calls as $resource is built on top of $http. To set our header we use to following line of javascript code:
$http.defaults.headers.common['RequestVerificationToken'] = 'token should go here';
Passing the token from MVC to AngularJS
We use the AntiForgery .Net class to create the AntiForgery token. This token needs to be passed to our AngularJS javascript code so we can put it into the headers. We will use the MVC view to pass the token through the generated HTML markup. This means that our AngularJS code needs to read the token from the markup. The only AngularJS code that should interact directly with the markup is Angular directives, so we need to create a directive. We will put the token into a custom attribute that we will use on a HTML element that also holds our angular controller to ensure that our directive will be able to set the header before we do any ajax requests from angular
The directive is very simple as we can see below
angular.module('myApp').directive('ncgRequestVerificationToken', ['$http', function ($http) {
return function (scope, element, attrs) {
$http.defaults.headers.common['RequestVerificationToken'] = attrs.ncgRequestVerificationToken || "no request verification token";
};
}]);
We use a MVC Html helper method to create the RequestVerificationToken attribute we need for the angular directive
public static class AntiForgeryExtension
{
public static string RequestVerificationToken(this HtmlHelper helper)
{
return String.Format("ncg-request-verification-token={0}", GetTokenHeaderValue());
}
private static string GetTokenHeaderValue()
{
string cookieToken, formToken;
System.Web.Helpers.AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
Once we have our angular directive and the RequestVerificationToken helper in place, including a token in all headers on our page becomes as simple as adding a call to our helper on the root element of our page like this:
<div ng-controller="myController" @Html.RequestVerificationToken()>
// rest of page goes here
</div>
Validate the token in Web API
Now that all our http requests will have our custom header the only task left to secure our Web API action methods is to validate this header. Mike Wasson provide some sample code for this which we wrap in a custom attribute to make it easy to reuse for all methods that we want to secure. Note that we only need to secure methods that change data which typically would be our post, put and delete actions.
The attribute code looks like this
public class AntiForgeryValidate : ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string cookieToken = "";
string formToken = "";
IEnumerable tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
System.Web.Helpers.AntiForgery.Validate(cookieToken, formToken);
base.OnActionExecuting(actionContext);
}
}
We apply the attribute to a method like this:
[HttpPost]
[AntiForgeryValidate]
public async Task UpdateSomething(MySomething something)
{
//some code here
}
Summary
Cookie based authentication is simple to use and supported by all browsers. As long as we know that all calls to our Web API will be from browsers and our own pages it works well with Web API too. As we have seen implementing measuers to prevent CSRF can be made just as simple for Web API as it is for MVC action methods.
Just do the following two steps:
- Add @Html.RequestVerificationToken() to the HTML element in the view
- Add an AntiForgeryValidate attribute to the Web API controller action