Angular vs. Asp.Net WebApi, implement CSRF on the server

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?

+67
javascript angularjs asp.net-web-api csrf
Mar 22 '13 at 15:47
source share
4 answers

I had no problems with the code, so I am considering the question.

-6
Jan 07 '15 at 4:53 on
source share

Your code looks fine. The only thing you don’t need anymore, since web.api launches asp.net mvc from above, and the latter has support for anti-fake tokens.

In the comments, dbrunning and ccorrin express concern that you can only use assemblies in AntiForgery tokens only when using HTML MVC helpers. This is not true. Helpers can simply set up a pair of session-based tokens that you can check against each other. See below for more details.

UPDATE:

You can use two methods in AntiForgery:

  • AntiForgery.GetTokens uses two parameters to return a cookie token and form token

  • AntiForgery.Validate(cookieToken, formToken) validates the pair of tokens

You can completely convert these two methods and use formToken as headerToken and cookieToken as actual cookieToken. Then just call confirmation for both attributes.

Another solution is to use JWT (e.g. MembershipReboot )

This link shows how to use the built-in anti-blocking tokens using ajax:

 <script> @functions{ public string TokenHeaderValue() { string cookieToken, formToken; AntiForgery.GetTokens(null, out cookieToken, out formToken); return cookieToken + ":" + formToken; } } $.ajax("api/values", { type: "post", contentType: "application/json", data: { }, // JSON data goes here dataType: "json", headers: { 'RequestVerificationToken': '@TokenHeaderValue()' } }); </script> void ValidateRequestHeader(HttpRequestMessage request) { string cookieToken = ""; string formToken = ""; IEnumerable<string> tokenHeaders; if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders)) { string[] tokens = tokenHeaders.First().Split(':'); if (tokens.Length == 2) { cookieToken = tokens[0].Trim(); formToken = tokens[1].Trim(); } } AntiForgery.Validate(cookieToken, formToken); } 

Also take a look at this question AngularJS cannot find XSRF-TOKEN cookie

+6
May 10 '13 at 1:16
source share

I think your code is corrupted. The whole idea of ​​preventing CSRF is to prevent a unique token on each request, not on every session. If the anti-fake token is a stored session value, the ability to perform CSRF is still preserved. You need to provide a unique token for each request ...

0
Apr 7 '16 at 2:40
source share

This solution is unsafe because CSRF attacks are still possible if the Auth cookie is valid. Both auth and xsrf cookies will be sent to the server when an attacker forces you to execute a request through another site, and therefore you are still vulnerable until the user performs a “hard” logout.

Each request or session must have its own unique token in order to truly prevent CRSF attacks. But probably the best solution is not to use cookie-based authentication, but token-based authentication like OAuth. This prevents other websites from using your cookies to perform unwanted requests, as markers are used in http headers instead of cookies. And the http headers are not sent automatically.

These excellent blog posts provide information on how to implement OAuth for WebAPI. Blog posts also contain great information on how to integrate it with AngularJS.

Another solution might be to disable CORS and only accept incoming requests from white domains. However, this will not work for non-website applications, such as mobile and / or desktop clients. Next to once your site is vulnerable to an XSS attack, an attacker can still fake requests for your behavior.

0
Apr 11 '16 at 9:58 on
source share



All Articles