21

I am using JQuery with ASP.NET Core 1.0.1 and I have the Ajax call:

$("#send-message").on("submit", function (event) {
  event.preventDefault();
  var $form = $(this);   
  $.ajax({
    url: "api/messages",
    data: JSON.stringify($form.serializeToJSON()),
    dataType: "json",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json"
    },
    type: "post"
  })
  .done(function (data, status, xhr) { })
  .fail(function (xhr, status, error) { });

To the ASP.NET Core action:

[HttpPost("messages")]
public async Task<IActionResult> Post([FromBody]MessagePostApiModelModel model) {
   // Send message
}

The form is in a shared view and it is the following:

<form id="send-question" method="post">
  <textarea name="content"></textarea>
  <button class="button" type="submit">Enviar</button>
</form>

When I submit the form I get the error:

Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery header value "RequestVerificationToken" is not present.

How can I enable ASP.NET Core's AntiForgeryToken with JQuery Ajax calls?

UPDATE

I need to add the following asp-controller and asp-action to the form:

<form asp-controller="QuestionApi" asp-action="Post" id="send-question" method="post">
</form>

This will generate the antiforgery token. And I needed to manually add the token to the headers of the JQuery call as follows:

  headers: {
    "Accept": "application/json",
    "Content-Type": "application/json",
    "RequestVerificationToken": $form.find("input[name='af_token']").val()
  },

Is there a better way to do this?

How do solve this when there is not form and I have only an A tag that when clicked makes the Ajax call? Can I generate a common antiforgery token on my page head to be used by all ajax calls from that page?

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • 1
    Is your form generated server side using the form tag helper? If that's the case the token will be already auto-generated for you as a [hidden input](http://stackoverflow.com/questions/30959972/html-antiforgerytoken-still-required) field, so it will be included in `form.serializeToJSON`. Regardless of the answer, you also need the `[ValidateAntiForgeryToken]` attribute in your controller action. – Daniel J.G. Nov 10 '16 at 15:37
  • @DanielJ.G. Is it being generated server side but I am not using asp-controller and asp-action because I am submitting it using JQuery. I just update my question. Am I missing something? – Miguel Moura Nov 10 '16 at 16:21
  • I needed to add the following header manually in my JQuery call: headers: { "RequestVerificationToken": $form.find("input[name='af_token]").val() }, ... Is there a better way? – Miguel Moura Nov 10 '16 at 16:35
  • 1
    You can use your original form without asp-controller or asp-action, just add `asp-antiforgery="true"` so you convert that into a form tag helper which adds the antiforgery token hidden input. Re what to do when there is no form, you might find [this similar question](http://stackoverflow.com/questions/40511103/using-the-antiforgery-cookie-in-asp-net-core-but-with-a-non-default-cookiename/40513275?noredirect=1#comment68290751_40513275) useful. – Daniel J.G. Nov 10 '16 at 16:47
  • @DanielJ.G. I read the question you suggested and I have one question. Imagine on a page I one form, that will be submitted on server side so it has its hidden input with the AntiforgeryToken, and a link which when clicked calls an HTTP DELETE action using JQuery. So in this case I might use an Antiforgery Token added "into a JS object inside a script section in your js" ... So my page will have two antiforgery tokens. Does this seems logic? Is there a way to share ONE antiforgery token to be used by server side forms and jquery calls from an JS object. – Miguel Moura Nov 10 '16 at 17:14
  • 1
    As long as you are within the same request context, you get the same token. Give it a try by comparing `var tokenSet = antiforgery.GetAndStoreTokens(Context);` with the token inside the form hidden input – Daniel J.G. Nov 10 '16 at 17:22
  • You do not need to have a form. Without a form, put something like this in your Layout: @Html.AntiForgeryToken() Than get the value of this in the generic ajaxSend (change my answer below) – Pieter van Kampen Apr 01 '17 at 08:58
  • I recommend using this, if you are still having to manually add your request token to ajax calls -> https://github.com/aspnet/jquery-ajax-unobtrusive – IEnjoyEatingVegetables Dec 28 '18 at 16:21

5 Answers5

26

mode777's answer just needs a small addition to make this work (I tried it):

$(document).ajaxSend(function(e, xhr, options) {
    if (options.type.toUpperCase() == "POST") {
        var token = $form.find("input[name='af_token']").val();
        xhr.setRequestHeader("RequestVerificationToken", token);
    }
});

Actually, if you also submit using Ajax, you don't need to use a form at all. Put this in your _layout:

 <span class="AntiForge"> @Html.AntiForgeryToken() </span>

Then you pickup the token by adding this to your javascript:

$(document)
   .ajaxSend(function (event, jqxhr, settings) {
        if (settings.type.toUpperCase() != "POST") return;
        jqxhr.setRequestHeader('RequestVerificationToken', $(".AntiForge" + " input").val())
})

The @HtmlAntiForgeryToken generates a hidden input field with the antiforgery token, the same as when using a form. The code above finds it using the class selector to select the span, then gets the input field inside that to collect the token and add it as a header.

Pieter van Kampen
  • 1,957
  • 17
  • 21
  • Check the token name generated, in asp.core 1.1 it is __RequestVerificationToken, so you need to change the name = '...' accordingly – Pieter van Kampen Jan 15 '17 at 21:48
  • 1
    With regard to the comment by @Pieter van Kampen, just note that the form field name does begin with a double underscore but the header name does not. – jmcilhinney Sep 29 '17 at 04:38
  • There's an easier way to get that data without putting anything unnecessary into your HTML document, see my answer. – ygoe Dec 04 '17 at 12:14
  • @ygoe: not sure how that helps. You require the ajax call to be placed in the view, while there may be many ajax calls in javascript. The solution above is generic. The @Html.AntiforgeryToken() (in an hidden input or span) can be placed in the layout. The .ajaxSend script can be placed once in a javascript file and applies to all POST ajax calls you make. – Pieter van Kampen Dec 05 '17 at 12:35
14

Note: This answer applies to ASP.NET Core 2.0. It may not fit to older versions.

Here's what I've done after digging through aspnet's source code for a short while:

public static class HttpContextExtensions
{
    public static string GetAntiforgeryToken(this HttpContext httpContext)
    {
        var antiforgery = (IAntiforgery)httpContext.RequestServices.GetService(typeof(IAntiforgery));
        var tokenSet = antiforgery.GetAndStoreTokens(httpContext);
        string fieldName = tokenSet.FormFieldName;
        string requestToken = tokenSet.RequestToken;
        return requestToken;
    }
}

You can use it in a view like this:

<script>
    var data = {
        yourData: "bla",
        "__RequestVerificationToken": "@Context.GetAntiforgeryToken()"
    };
    $.post("@Url.Action("ActionName")", data);
</script>

You might modify the extension method to return the name of the field as well, in any form you wish, e. g. a JSON fragment.

ygoe
  • 18,655
  • 23
  • 113
  • 210
  • 1
    This appears to be a better approach than the inject and function method described in the docs, since there is always a Context object in the view, no extra 'stuff' required. – Erikest Jan 20 '18 at 04:16
  • I ended up combining this answer with mode777's, neither was sufficient on its own. – James Blake May 09 '18 at 19:19
  • I'm getting a 415 (Unsupported Media Type) error response when I add the __RequestVerificationToken. How do I resolve? – gbade_ Sep 10 '18 at 17:32
  • @gbade_ Sorry, never heard of that code. You should probably ask a new question with more details. – ygoe Sep 11 '18 at 08:08
7

In Asp.Net Core you can request the token directly, as documented:

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf    
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

And use it in javascript:

function DoSomething(id) {
    $.post("/something/todo/"+id,
               { "__RequestVerificationToken": '@GetAntiXsrfRequestToken()' });
}

You can add the recommended global filter, as documented:

services.AddMvc(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})
2

You can register a global ajax event that will add the header to all ajax calls that are not GET by this:

$(document).ajaxSend(function(e, xhr, options) {
    if (options.type.toUpperCase() != "GET") {
        xhr.setRequestHeader("RequestVerificationToken", token);
    }
});
mode777
  • 3,037
  • 2
  • 23
  • 34
1

In addition to ygoe's answer, if you want to pass a XSRF token as a header, e.g. X-XSRF-Token:

var ajax = {
    url: "/users",
    data: data,
    type: "post",

    // ...
};

var antiForgeryToken = $("input[name=__RequestVerificationToken]").val();
if (antiForgeryToken) {
    ajax.headers = {};
    ajax.headers["X-XSRF-Token"] = antiForgeryToken;
};

$.ajax(ajax);

then you will also need to specify the respective antiforgery option:

public void ConfigureServices(IServiceCollection services)
{
    // your services to inject are also configured here ...

    services.AddAntiforgery(options => options.HeaderName = "X-XSRF-Token");
    services.AddMvc();
}

Then you can use the standard ValidateAntiForgeryToken attribute to validate the request:

[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult Users(UserModel user)
{
    // ...
}
Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Dmitry Karpenko
  • 544
  • 5
  • 6