3

I am having problems getting Microsoft's MVC's [ValidateAntiForgeryToken] to work with a Single Page Application (SPA) written using Marionette & Backbone. The problem seems to be that the MVC [ValidateAntiForgeryToken] method fails to see the token we send as part of the JSON. We thought it was because the token had to be in the Forms part of the reply but MrOggy85 says that isn't a problem (see his answer below).

The code is in my api controllers, which use AttributeRouting, which we assume is causing the problem. A typical action looks like this:

    // POST api/vizschemes/
    [POST("")]
    [Authorize(Roles = "...some role...")]
    [ValidateAntiForgeryToken]
    public ActionResult Add(CreateUpdateSmVizSchemeDto dto,  ICreateSmVizScheme service)
    {
       ... code to update the VizScheme and return json
    }

Has anyone else overcome this? A lot of googling turned up the comment in is post "ASP.NET MVC provides built-in support for anti-forgery tokens, through the AntiForgery class and the [ValidateAntiForgeryToken] attribute. Currently, this functionality is not built into Web API. However, the (KnockoutJS) template includes a custom implementation for Web API.". This suggests that they write their own, which I can do.

Has anyone else hit this, and if so how did you solve it? Am I missing something obvious or should I just write my own ValidateAntiForgeryToken method? Your input would be appreciated.

UPDATE

GREAT stackoverflow link provided by @MrOggy85 with lots more information in it. See How can i supply an AntiForgeryToken when posting JSON data using $.ajax? . I plan to write my own AntiForgery test and will post when I have done.

Community
  • 1
  • 1
Jon P Smith
  • 2,141
  • 1
  • 25
  • 50

2 Answers2

2

When you use the helper @Html.AntiForgeryToken() in a view this is the actual HTML result:

<input name="__RequestVerificationToken" type="hidden" 
value="{ long cryptic code }">

This input field is normally inside your <form>-element which means it will be attached to the request if you submit that form.

The problem occurs when this input field is outside of your form or if you are not submitting a form. Have no fear, there is a solution for this. The attribute [ValidateAntiForgeryToken] tells the server to look for a key with the name: "__RequestVerificationToken". Let's give the server what it wants!

First, get that value!

var antiforgeytoken = $('input[name=__RequestVerificationToken]').val();

Second, attach it to your AJAX-request (I use jQuery)

$.ajax({
  url: 'something/something',
  type: 'POST',
  contentType: 'application/x-www-form-urlencoded; charset=UTF-8', // Default
  data: { 'somekey': 'someval', 
          '__RequestVerificationToken', antiforgeytoken }
});

Now your server is happy, and you are too!

Update:
The content type is important because of how the MVC Binder validates the request. If you want to use another content type this solution How can i supply an AntiForgeryToken when posting JSON data using $.ajax? proposes to separate the antiforgery token and the postdata in two different parameters.

Community
  • 1
  • 1
オスカー
  • 1,399
  • 4
  • 16
  • 37
  • Hi @MrOggy85. We already tried exactly the code you wrote and it failed with "The required anti-forgery form field "__RequestVerificationToken" is not present". We assumed it was because the token wasn't in the Form part of the HttpRequest. We have verified the token is in the response. Have you got the code above to work in an applciation? Maybe we are missing something? – Jon P Smith Aug 01 '13 at 11:02
  • Have you also put [HttpPost] on the action (I forget to do this sometimes...)? And yes, I have my solution working in an active web application. – オスカー Aug 01 '13 at 11:20
  • Thanks for your input MtOggy85, which is very helpful but I still have a problem. At least you have cleared up that it doesn't have to be in the Form part of the request. I have updated my question to include the code we have in our api. I can only assume that AttributeRouting causes a problem, but I can't see how. – Jon P Smith Aug 01 '13 at 16:16
  • Hi MrOggy85. One thought - we are sending the data as JSON. I assume you are using x-form-www-urlencoded - can you confirm. That could be the problem, although the MVC binder should sort that out. – Jon P Smith Aug 01 '13 at 19:30
  • I will check tomorrow (in a couple of hours ;)) – オスカー Aug 01 '13 at 20:26
  • Sry for the late answer. Yes, I am using that content type: Content-Type:application/x-www-form-urlencoded; charset=UTF-8 – オスカー Aug 06 '13 at 10:34
  • OK, that confirms that the [ValidateAntiForgeryToken] doesn't use the standard binder but always expects it in the URL. We could do that but we have some quote big and complex data to send back so I think I will write my own validator. Thanks for your help on this. – Jon P Smith Aug 06 '13 at 14:14
  • Take a look at this solution: http://stackoverflow.com/questions/2906754/how-can-i-supply-an-antiforgerytoken-when-posting-json-data-using-ajax If you separate the antiforgery token and the other data you should be able to post with other content type. – オスカー Aug 07 '13 at 06:37
0

I don't know about AntiForgery, but Backbone can be configured to emulate REST and emulate JSON to circumvent this kind of limitation from the server.

Backbone.emulateHTTP = true;
Backbone.emulateJSON = true;

This will tell Backbone to use only POST method for delete() and update() and send the data as application/x-www-form-urlencoded instead of application/json.

mor
  • 2,313
  • 16
  • 28
  • Hi @mor, Thanks for that. My problem is handling the MVC [ValidateAntiForgeryToken] which, if I use the standard method inside MVC, requires all data to come back as a Form Submit. The question is, should I change Backbone to do that or implement my own check for the token in MVC. I wondered if anyone else had hit this issue when building single page applications with MVC and what they did about it. – Jon P Smith Jul 29 '13 at 11:07