AngularJS has some in-built features to help with anti-CSRF protection. As stated in the Angular documentation:

on each http request, the $http service (or anything built on top of it, like $resource) will look for a cookie (by default, "XSRF-TOKEN") and, if it finds it, it will submit it as a header ("X-XSRF-TOKEN"). This relies on your web server being able to set the XSRF-TOKEN cookie after authenticating the user, and then checking the X-XSRF-TOKEN header for incoming requests.

In fact, Asp.NET already has the ability to set that cookie --albeit with a name that differs from the one AngularJS expects by default. The problem is that Asp.NET actually marks it as HttpOnly (which prevents JavaScript from reading it), and there’s no way around it. So instead of just renaming the cookie and relying on Html.AntiForgeryToken to create the token and set the cookie, we now have to come up with our own implementation:

public static class HtmlExtensions  
{
    const string AntiForgeryCookieName = "XSRF-TOKEN";

    public static IHtmlString AngularJSAntiForgeryToken(this HtmlHelper html)
    {
        var httpContext = new HttpContextWrapper(HttpContext.Current);

        var antiForgeryCookieToken = GetCookieToken(httpContext, AntiForgeryConfig.CookieName);
        var angularAntiForgeryCookieToken = GetCookieToken(httpContext, AntiForgeryCookieName);

        string oldCookieToken = null;
        if (!string.IsNullOrEmpty(antiForgeryCookieToken))
        {
            oldCookieToken = antiForgeryCookieToken;
        }

        string newCookieToken;
        string formToken;
        AntiForgery.GetTokens(oldCookieToken, out newCookieToken, out formToken);

        if (string.IsNullOrEmpty(antiForgeryCookieToken) || newCookieToken != null)
        {
            // set default antiforgery cookie
            httpContext.Response.SetCookie(
                new HttpCookie(AntiForgeryConfig.CookieName, newCookieToken ?? oldCookieToken)
            {
                HttpOnly = true,
                Secure = AntiForgeryConfig.RequireSsl
            }); 
        }

        if (string.IsNullOrEmpty(angularAntiForgeryCookieToken) || newCookieToken != null)
        {
            // set angular's specific antiforgery cookie
            httpContext.Response.SetCookie(new HttpCookie(AntiForgeryCookieName, formToken)
            {
                HttpOnly = false,
                Secure = AntiForgeryConfig.RequireSsl
            });
        }

        return null;
    }

    private static string GetCookieToken(HttpContextBase httpContext, string cookieName)
    {
        var httpCookie = httpContext.Request.Cookies[cookieName];
        if (httpCookie == null || string.IsNullOrEmpty(httpCookie.Value))
        {
            return null;
        }

        return httpCookie.Value;
    }
}

At this point, whenever we decorate our view using the extension method @Html.AngularJSAntiForgeryToken, Asp.NET will set a cookie named "XSRF-TOKEN" with the actual token in it. AngularJS will then, every time it needs to make a call to the server, look for this cookie, try to read the token and submit it as a HTTP header named "X-XSRF-TOKEN".

The last piece of the puzzle it’s to make sure Asp.NET checks and validates the token included in the HTTP header of the request. Therefore we have to tell Asp.NET where to look for the token delivered by AngularJS. In order to do that, we’ll also need a custom implementation of the ValidateAntiForgeryToken filter attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAngularJSAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter  
{
    const string XsrfHeaderName = "X-XSRF-TOKEN";

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request; 
        var cookieToken = GetCookieToken(request, AntiForgeryConfig.CookieName);
        var headerToken = GetHeaderToken(request, XsrfHeaderName);

        AntiForgery.Validate(cookieToken, headerToken);
    }

    private static string GetCookieToken(HttpRequestBase request, string cookieName)
    {
        var httpCookie = request.Cookies.Get(cookieName);
        if (httpCookie == null || string.IsNullOrEmpty(httpCookie.Value))
        {
            return null;
        }

        return httpCookie.Value;
    }

    private static string GetHeaderToken(HttpRequestBase request, string headerName)
    {
        return request.Headers.Get(headerName);
    }
}

As you can see, instead of going with a custom validation, we’re reusing the AntiForgery.Validate() method already present in Asp.NET MVC.

Also, if you are using Asp.NET WebAPI, consider registering a custom DelegateHandler that checks and validates the token for each HTTP request. This will save you from having to decorate your controllers with the ValidateAngularJSAntiForgeryToken filter attribute.

GIST

http://git.io/rTp1jQ

Tags

CSRF , Asp.NET , AngularJS

Written by