I am implementing a website in Angular.js that falls into the ASP.NET WebAPI backend.
Angular.js has built-in features to protect against anti-csrf. In each HTTP request, he will look for a cookie called "XSRF-TOKEN" and send it as the "X-XSRF-TOKEN" header.
It depends on how the web server can set the XSRF-TOKEN cookie after authenticating the user, and then check the X-XSRF-TOKEN header for incoming requests.
The Angular documentation says:
To take advantage of this, your server needs to set a token in a JavaScript-readable session cookie called XSRF-TOKEN on the first HTTP GET request. On subsequent non-GET requests, the server can check whether the cookie matches the X-XSRF-TOKEN HTTP header, and therefore make sure that only JavaScript running in your domain can read the token. The token must be unique for each user and must be checked by the server (so that JavaScript does not create its own tokens). We recommend that the token be the digest of your site’s authentication cookie with salt for added security.
I could not find good examples of this for ASP.NET WebAPI, so I applied my own using various sources. My question is: can anyone see something wrong with the code?
First, I defined a simple helper class:
public class CsrfTokenHelper { const string ConstantSalt = "<ARandomString>"; public string GenerateCsrfTokenFromAuthToken(string authToken) { return GenerateCookieFriendlyHash(authToken); } public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken) { return csrfToken == GenerateCookieFriendlyHash(authToken); } private static string GenerateCookieFriendlyHash(string authToken) { using (var sha = SHA256.Create()) { var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt)); var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash); return cookieFriendlyHash; } } }
Then I have the following method in my authorization controller, and I call it after calling FormsAuthentication.SetAuthCookie ():
// http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks // http://docs.angularjs.org/api/ng.$http private void SetCsrfCookie() { var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH"); Debug.Assert(authCookie != null, "authCookie != null"); var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value); var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false}; HttpContext.Current.Response.Cookies.Add(csrfCookie); }
Then I have a custom attribute that I can add to the controllers to make them check the csrf header:
public class CheckCsrfHeaderAttribute : AuthorizeAttribute { // http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc protected override bool IsAuthorized(HttpActionContext context) { // get auth token from cookie var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"]; if (authCookie == null) return false; var authToken = authCookie.Value; // get csrf token from header var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault(); if (String.IsNullOrEmpty(csrfToken)) return false; // Verify that csrf token was generated from auth token // Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header. // This proves that our site made the request. return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken); } }
Finally, I clear the Csrf token when the user logs out:
HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");
Is it possible to identify any obvious (or not so obvious) problems with this approach?