Issues with implementing ValidatingAntiForgeryToken attribute for web API with MVC 4 RC

I am making JSON-based AJAX requests and using MVC controllers I am very grateful to Phil Haack for Preventing CSRF with AJAX and, Johan Driessen Updated Anti-XSRF for MVC 4 RC . But as I move APIs into Web APIs, I run into problems when the functionality between the two approaches is noticeably different, and I can’t switch to CSRF code.

Scotts recently raised a similar question , which was answered by Darin Dimitrov. Darin's solution includes an authorization filter implementation that calls AntiForgery.Validate. Unfortunately, this code does not work for me (see the next paragraph) and, frankly, too advanced for me.

As I understand it, Phil's solution overcomes the problem with MVC AntiForgery when executing JSON requests in the absence of a form element; the form element is supposed / expected using the AntiForgery.Validate method. I believe that this may be because I have problems with Darin’s decision. I get an HttpAntiForgeryException "Required form field to fake" __RequestVerificationToken "no". I am sure the token is POSTED (albeit in the header for the Phil Haack solution). Here's a snapshot of a client call:

$token = $('input[name=""__RequestVerificationToken""]').val(); $.ajax({ url:/api/states", type: "POST", dataType: "json", contentType: "application/json: charset=utf-8", headers: { __RequestVerificationToken: $token } }).done(function (json) { ... }); 

I tried to hack by combining Johan's solution with Darin and was able to make everything work, but I imagine HttpContext.Current, unsure whether this is suitable / safe and why I can not use the provided HttpActionContext.

Here's my inelegant mash-up .. change is 2 lines in a try block:

 public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) { try { var cookie = HttpContext.Current.Request.Cookies[AntiForgeryConfig.CookieName]; AntiForgery.Validate(cookie != null ? cookie.Value : null, HttpContext.Current.Request.Headers["__RequestVerificationToken"]); } catch { actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.Forbidden, RequestMessage = actionContext.ControllerContext.Request }; return FromResult(actionContext.Response); } return continuation(); } 

My questions:

  • Do I believe that Darin’s decision assumes the existence of an element of form?
  • What is an elegant way to put together a Darin Web API filter with RC Johan MVC 4 code?

Thanks in advance!

+19
asp.net-web-api asp.net-mvc-4 antiforgerytoken
Jul 30 2018-12-12T00:
source share
5 answers

You can try reading from the headers:

 var headers = actionContext.Request.Headers; var cookie = headers .GetCookies() .Select(c => c[AntiForgeryConfig.CookieName]) .FirstOrDefault(); var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault(); AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt); 

Note. GetCookies is an extension method that exists in the HttpRequestHeadersExtensions , which is part of System.Net.Http.Formatting.dll . Most likely, it will be C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll

+32
Jul 30 '12 at 17:32
source share

I just wanted to add that this approach also worked for me (.ajax, sending JSON to the endpoint of the web API), although I simplified it a bit by inheriting from ActionFilterAttribute and overriding the OnActionExecuting method.

 public class ValidateJsonAntiForgeryTokenAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { try { var cookieName = AntiForgeryConfig.CookieName; var headers = actionContext.Request.Headers; var cookie = headers .GetCookies() .Select(c => c[AntiForgeryConfig.CookieName]) .FirstOrDefault(); var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault(); AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt); } catch { actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized request."); } } } 
+12
Apr 18 '13 at 20:07 on
source share

Extension method using Darin's answer with a header check. Verification means that the resulting error message more indicates that it is incorrect ("Required anti-fake form field" __RequestVerificationToken "no.") Against "This header was not found."

 public static bool IsHeaderAntiForgeryTokenValid(this HttpRequestMessage request) { try { HttpRequestHeaders headers = request.Headers; CookieState cookie = headers .GetCookies() .Select(c => c[AntiForgeryConfig.CookieName]) .FirstOrDefault(); var rvt = string.Empty; if (headers.Any(x => x.Key == AntiForgeryConfig.CookieName)) rvt = headers.GetValues(AntiForgeryConfig.CookieName).FirstOrDefault(); AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt); } catch (Exception ex) { LogHelper.LogError(ex); return false; } return true; } 

Using ApiController:

 public IHttpActionResult Get() { if (Request.IsHeaderAntiForgeryTokenValid()) return Ok(); else return BadRequest(); } 
0
Dec 15 '14 at 10:22
source share

Implementation using AuthorizeAttribute:

 using System; using System.Linq; using System.Net.Http; using System.Web; using System.Web.Helpers; using System.Web.Http; using System.Web.Http.Controllers; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class ApiValidateAntiForgeryToken : AuthorizeAttribute { public const string HeaderName = "X-RequestVerificationToken"; private static string CookieName => AntiForgeryConfig.CookieName; public static string GenerateAntiForgeryTokenForHeader(HttpContext httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } // check that if the cookie is set to require ssl then we must be using it if (AntiForgeryConfig.RequireSsl && !httpContext.Request.IsSecureConnection) { throw new InvalidOperationException("Cannot generate an Anti Forgery Token for a non secure context"); } // try to find the old cookie token string oldCookieToken = null; try { var token = httpContext.Request.Cookies[CookieName]; if (!string.IsNullOrEmpty(token?.Value)) { oldCookieToken = token.Value; } } catch { // do nothing } string cookieToken, formToken; AntiForgery.GetTokens(oldCookieToken, out cookieToken, out formToken); // set the cookie on the response if we got a new one if (cookieToken != null) { var cookie = new HttpCookie(CookieName, cookieToken) { HttpOnly = true, }; // note: don't set it directly since the default value is automatically populated from the <httpCookies> config element if (AntiForgeryConfig.RequireSsl) { cookie.Secure = AntiForgeryConfig.RequireSsl; } httpContext.Response.Cookies.Set(cookie); } return formToken; } protected override bool IsAuthorized(HttpActionContext actionContext) { if (HttpContext.Current == null) { // we need a context to be able to use AntiForgery return false; } var headers = actionContext.Request.Headers; var cookies = headers.GetCookies(); // check that if the cookie is set to require ssl then we must honor it if (AntiForgeryConfig.RequireSsl && !HttpContext.Current.Request.IsSecureConnection) { return false; } try { string cookieToken = cookies.Select(c => c[CookieName]).FirstOrDefault()?.Value?.Trim(); // this throws if the cookie does not exist string formToken = headers.GetValues(HeaderName).FirstOrDefault()?.Trim(); if (string.IsNullOrEmpty(cookieToken) || string.IsNullOrEmpty(formToken)) { return false; } AntiForgery.Validate(cookieToken, formToken); return base.IsAuthorized(actionContext); } catch { return false; } } } 

Then just decorate your controller or methods with [ApiValidateAntiForgeryToken]

And add this to the razor file to generate a token for javascript:

 <script> var antiForgeryToken = '@ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader(HttpContext.Current)'; // your code here that uses such token, basically setting it as a 'X-RequestVerificationToken' header for any AJAX calls </script> 
0
Jan 05 '17 at 13:53 on
source share

If this helps someone, in the .net kernel the default header value is actually just "RequestVerificationToken", without "__". So if you change the title key instead, it will work.

You can also override the header name if you want:

services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")

0
Dec 21 '18 at 0:38
source share



All Articles