Enable Antiforgery Token with ASP.NET Core and JQuery
Asked Answered
A

5

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?

Arlynearlynne answered 10/11, 2016 at 14:49 Comment(8)
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 field, so it will be included in form.serializeToJSON. Regardless of the answer, you also need the [ValidateAntiForgeryToken] attribute in your controller action.Alexandraalexandre
@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?Arlynearlynne
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?Arlynearlynne
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 useful.Alexandraalexandre
@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.Arlynearlynne
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 inputAlexandraalexandre
You do not need to have a form. Without a form, put something like this in your Layout: <span id="[email protected]"> @Html.AntiForgeryToken() </span> Than get the value of this in the generic ajaxSend (change my answer below)Betsybetta
I recommend using this, if you are still having to manually add your request token to ajax calls -> github.com/aspnet/jquery-ajax-unobtrusiveJuback
B
27

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.

Betsybetta answered 15/1, 2017 at 21:44 Comment(4)
Check the token name generated, in asp.core 1.1 it is __RequestVerificationToken, so you need to change the name = '...' accordinglyBetsybetta
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.Lenny
There's an easier way to get that data without putting anything unnecessary into your HTML document, see my answer.Protomorphic
@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.Betsybetta
P
13

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.

Protomorphic answered 4/12, 2017 at 12:10 Comment(4)
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.Riesman
I ended up combining this answer with mode777's, neither was sufficient on its own.Nowhither
I'm getting a 415 (Unsupported Media Type) error response when I add the __RequestVerificationToken. How do I resolve?Inarticulate
@Inarticulate Sorry, never heard of that code. You should probably ask a new question with more details.Protomorphic
A
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());
})
Acolyte answered 24/11, 2018 at 16:21 Comment(0)
M
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);
    }
});
Marjoriemarjory answered 4/12, 2016 at 18:45 Comment(0)
K
2

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)
{
    // ...
}
Kithara answered 22/7, 2018 at 0:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.