How to POST JSON data to an Asp.Net MVC 2 app with ValidateAntiForgeryToken

The title says it all really, earlier this week I was attempting to submit data in the form of JSON to a controller method that implemented the ValidateAntiForgeryTokenAttribute.

On the client side of things  I managed to get the validation input generated by Html.AntiForgeryToken into my JSON object but I just kept on getting the “A required anti-forgery token was not supplied or was invalid” exception.

After a bit of research I found the problem to lie in the Asp.Net MVC 2 source, if you look at the ValidateAntiForgeryTokenAttribute file specifically on line 56 (OnAuthorization method) you’ll see this:

string formValue = filterContext.HttpContext.Request.Form[fieldName];

The validation class always checks the form for the token but we don’t have any form data when we post JSON!

So my solution was to modify like so:

      string value;
      if (filterContext.HttpContext.Request.ContentType.ToLower().Contains("json"))
      {
        var bytes = new byte[filterContext.HttpContext.Request.InputStream.Length];
        filterContext.HttpContext.Request.InputStream.Read(bytes, 0, bytes.Length);
        filterContext.HttpContext.Request.InputStream.Position = 0;
        var json = Encoding.ASCII.GetString(bytes);
        var jsonObject = JObject.Parse(json);
        value = (string)jsonObject[fieldName];
      }
      else
      {
        value = filterContext.HttpContext.Request.Form[fieldName];
      }

What happens here is I do a check to see if the post is json, if so I then pull the string from the input stream and using a JSON parser extract the token.

Since we’re modifying the ValidateAntiForgery class we might as allow it to work  with (or restrict to) any http verb.

This is what I’ve ended up with (sorry about the formatting):

namespace Your.App
{
  using System;
  using System.Linq;
  using System.Text;
  using System.Web;
  using System.Web.Mvc;
  using Newtonsoft.Json.Linq;

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
  public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
  {
    private string _salt;
    private AntiForgeryDataSerializer _serializer;
    private readonly AcceptVerbsAttribute _verbs;

    public string Salt
    {
      get
      {
        return _salt ?? String.Empty;
      }
      set
      {
        _salt = value;
      }
    }

    internal AntiForgeryDataSerializer Serializer
    {
      get
      {
        if (_serializer == null)
        {
          _serializer = new AntiForgeryDataSerializer();
        }
        return _serializer;
      }
      set
      {
        _serializer = value;
      }
    }

    public ValidateJsonAntiForgeryTokenAttribute(HttpVerbs verbs = HttpVerbs.Post):this(null, verbs)
    {
    }

    public ValidateJsonAntiForgeryTokenAttribute(string salt, HttpVerbs verbs = HttpVerbs.Post)
    {
      this._verbs = new AcceptVerbsAttribute(verbs);
      this._salt = salt;
    }

    private bool ValidateFormToken(AntiForgeryData token)
    {
      return (String.Equals(Salt, token.Salt, StringComparison.Ordinal));
    }

    private static HttpAntiForgeryException CreateValidationException()
    {
      return new HttpAntiForgeryException("A required anti-forgery token was not supplied or was invalid.");
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
      if (filterContext == null)
      {
        throw new ArgumentNullException("filterContext");
      }

      // We only need to validate this if it's a post
      string httpMethodOverride = filterContext.HttpContext.Request.GetHttpMethodOverride();
      if (!this._verbs.Verbs.Contains(httpMethodOverride, StringComparer.OrdinalIgnoreCase))
      {
        return;
      }

      string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
      string cookieName = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);

      HttpCookie cookie = filterContext.HttpContext.Request.Cookies[cookieName];
      if (cookie == null || String.IsNullOrEmpty(cookie.Value))
      {
        // error: cookie token is missing
        throw CreateValidationException();
      }
      AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

      string value;
      if (filterContext.HttpContext.Request.ContentType.ToLower().Contains("json"))
      {
        var bytes = new byte[filterContext.HttpContext.Request.InputStream.Length];
        filterContext.HttpContext.Request.InputStream.Read(bytes, 0, bytes.Length);
        filterContext.HttpContext.Request.InputStream.Position = 0;
        var json = Encoding.ASCII.GetString(bytes);
        var jsonObject = JObject.Parse(json);
        value = (string)jsonObject[fieldName];
      }
      else
      {
        value = filterContext.HttpContext.Request.Form[fieldName];
      }

      if (String.IsNullOrEmpty(value))
      {
        // error: form token is missing
        throw CreateValidationException();
      }
      AntiForgeryData formToken = Serializer.Deserialize(value);

      if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal))
      {
        // error: form token does not match cookie token
        throw CreateValidationException();
      }

      if (!ValidateFormToken(formToken))
      {
        // error: custom validation failed
        throw CreateValidationException();
      }
    }
  }
}

Also I had to copy over 2 classes from the MVC source. The “AntiForgeryData” and “AntiForgeryDataSerializer” since they are interal and my project wouldn’t compile without them.

Final note, if you’re having trouble with the Serializer you copied over (in the Deserialize method and casting to a Triplet) then this may be of some help (.Net 4.0 only):

                dynamic deserializedObj = formatter.Deserialize(serializedToken);
                return new AntiForgeryData() {
                    Salt = deserializedObj[0],
                    Value = deserializedObj[1],
                    CreationDate = (DateTime)deserializedObj[2]
                };

7 Comments

  • Pablo
    November 2, 2010 - 4:05 pm | Permalink

    Thanks!! It works perfect!

  • kdawg
    November 5, 2010 - 8:49 pm | Permalink

    Fantastic, love it!

    When you said you had to bring over the two sealed classes for your new Validation attribute, did you have to rename them or do anything so they wouldn’t conflict with the sealed versions within the MVC assembly?

    It doesn’t look like you renamed them. I haven’t tried it out yet, was just curious if you had any issues with it.

    Thanks again!

  • November 5, 2010 - 8:52 pm | Permalink

    No it worked fine for me.

  • February 4, 2011 - 5:39 pm | Permalink

    Thanks. This really came in handy this morning.

  • October 20, 2012 - 1:42 am | Permalink

    Hi !

    Is it possible to have an example (source code, albertini.olivier@gmail.com) because i have this problem… and it’ll make 2 days that i search a solution.. i’m on .NET 4. Json with validationToken doesn’t work …

    Please,
    i really appreciate that !!!

    • November 5, 2012 - 10:25 am | Permalink

      Sorry for the late reply, I’m afraid I don;’t have an example (I used this in live code) but what I did above is essentially the same code I.

  • December 6, 2012 - 4:53 pm | Permalink

    Tony,
    Great article, thanks! One thing though, in my case I needed to get the MvcResources,resx and cs files as well. Other than that. It was a pretty easy fix.

  • Leave a Reply