Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Posting a JSON model to ASP.Net MVC3 with Anti-forgery token

So, I've been banging my head against the wall with this, and I can't find any good sources for this. Maybe I'm forgetting how the model binding stuff works in MVC3, but here's what I'm trying to do: I have some an editor bound up with Knockout to handle editing of a model. There's not much to the model:

public class SetupTemplate
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Template { get; set; }
} 

The signature of the action i'm trying to call is:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UpdateTemplate(SetupTemplate template)

From another question on here, I picked up this rather helpful snippet to get the anti-forgery token:

window.addAntiForgeryToken = function(data) {
    data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
    return data;
};

Which all comes together with me trying to post an update via ajax:

payload = window.addAntiForgeryToken(ko.mapping.toJS(self.data));
$.ajax({
    type: "post",
    url: endpoint,
    data: payload,
    success: function(data) {
        //Handle success
    }});

Which results in this in the form data section of the Chrome developer tools

Id:1
Name:Greeting
Template: [Template Text]
__RequestVerificationToken: [The really long anti-forgery token]

The antiforgery token is picked up, but my model is null. Most examples I've seen of this just use a single parameter passed along, and not a model.

I'm sure I'm missing something obvious, any insights on what it could be?

EDIT: In response to @Mark, changing the call to this:

$.ajax({
type: "post",
dataType: "json",
contentType: 'application/json',
url: endpoint,
data: JSON.stringify(payload),
success: function(data) {
    //Do some stuff
}});

Results in a request payload of this:

{"Id":1,"Name":"Greeting","Template":"...","__RequestVerificationToken":"..."}:

And the server not picking up the anti-forgery token. This was tried both with and without the contentType parameters to $.ajax().

like image 340
Matt Sieker Avatar asked Aug 30 '12 05:08

Matt Sieker


3 Answers

The mapping did not work with the parameter as template because it clashes with one of the property that has the same name (bearing case). If you use anything other than template it will work well for that controller parameter.

There is a so link explaining the details, I'm not able to find that now easily.

public class SetupTemplate
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Template { get; set; }
} 
like image 64
gathreya Avatar answered Nov 15 '22 14:11

gathreya


Here is my solution. Define a jQuery function like this:

(function ($) {
    $.getAntiForgeryToken = function () {
        return $('input[name="__RequestVerificationToken"]').val();
    };

    // (!) use ValidateJsonAntiForgeryToken attribute in your controller
    $.ajaxJsonAntiforgery = function (settings) {

        var headers = {};
        headers['__RequestVerificationToken'] = $.getAntiForgeryToken();

        settings.dataType = 'json';
        settings.contentType = 'application/json; charset=utf-8';
        settings.type = 'POST';
        settings.cache = false;
        settings.headers = headers;
        return $.ajax(settings);
    };
})(jQuery);

It just puts your verification token to headers. You also need filter attribute to check your antiforgery token. Here it is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;

namespace MyProject.Web.Infrastructure.Filters
{

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
                    AllowMultiple = false, Inherited = true)]
    public sealed class ValidateJsonAntiForgeryTokenAttribute
                                : FilterAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            var httpContext = filterContext.HttpContext;
            var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
            AntiForgery.Validate(cookie != null ? cookie.Value : null,
                                 httpContext.Request.Headers["__RequestVerificationToken"]);
        }
    }
}

In your controller it's really easy, just mark it with new attribute (ValidateJsonAntiForgeryToken):

[Authorize, HttpPost, ValidateJsonAntiForgeryToken]
public ActionResult Index(MyViewModel viewModel)

And on the client side:

$.ajaxJsonAntiforgery({
    data: dataToSave,
    success: function() { alert("success"); },
    error: function () { alert("error"); }
});

It works for me. Enjoy!

like image 39
Roman Pushkin Avatar answered Nov 15 '22 14:11

Roman Pushkin


Can you try using JSON.stringify?

$.ajax({     
   type: "post",     
   url: endpoint,     
   data: JSON.stringify(payload),     
   success: function(data) {         
      //Handle success     
   } 
});
like image 34
VJAI Avatar answered Nov 15 '22 15:11

VJAI