inclure antiforgerytoken dans ajax post ASP.NET MVC

168

J'ai des problèmes avec l'AntiForgeryToken avec ajax. J'utilise ASP.NET MVC 3. J'ai essayé la solution dans les appels jQuery Ajax et le Html.AntiForgeryToken () . En utilisant cette solution, le jeton est maintenant passé:

var data = { ... } // with token, key is '__RequestVerificationToken'

$.ajax({
        type: "POST",
        data: data,
        datatype: "json",
        traditional: true,
        contentType: "application/json; charset=utf-8",
        url: myURL,
        success: function (response) {
            ...
        },
        error: function (response) {
            ...
        }
    });

Lorsque je supprime l' [ValidateAntiForgeryToken]attribut juste pour voir si les données (avec le jeton) sont transmises en tant que paramètres au contrôleur, je peux voir qu'elles sont transmises. Mais pour une raison quelconque, le A required anti-forgery token was not supplied or was invalid.message apparaît toujours lorsque je remets l'attribut.

Des idées?

ÉDITER

L'antiforgerytoken est généré dans un formulaire, mais je n'utilise pas d'action d'envoi pour le soumettre. Au lieu de cela, j'obtiens simplement la valeur du jeton en utilisant jquery, puis j'essaye d'ajax poster cela.

Voici le formulaire qui contient le jeton et se trouve sur la page maître supérieure:

<form id="__AjaxAntiForgeryForm" action="#" method="post">
    @Html.AntiForgeryToken()
</form>
OJ Raqueño
la source

Réponses:

289

Vous avez incorrectement spécifié le contentTypeà application/json.

Voici un exemple de la façon dont cela pourrait fonctionner.

Manette:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(string someValue)
    {
        return Json(new { someValue = someValue });
    }
}

Vue:

@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
    @Html.AntiForgeryToken()
}

<div id="myDiv" data-url="@Url.Action("Index", "Home")">
    Click me to send an AJAX request to a controller action
    decorated with the [ValidateAntiForgeryToken] attribute
</div>

<script type="text/javascript">
    $('#myDiv').submit(function () {
        var form = $('#__AjaxAntiForgeryForm');
        var token = $('input[name="__RequestVerificationToken"]', form).val();
        $.ajax({
            url: $(this).data('url'),
            type: 'POST',
            data: { 
                __RequestVerificationToken: token, 
                someValue: 'some value' 
            },
            success: function (result) {
                alert(result.someValue);
            }
        });
        return false;
    });
</script>
Darin Dimitrov
la source
Salut, merci pour la réponse rapide. Désolé de ne pas l'avoir mentionné dans la question; Je n'utilise pas l'action d'envoi pour le moment. (Le jeton est dans un formulaire, mais je n'utilise pas de bouton d'envoi pour le soumettre). Est-il possible de simplement changer le type de contenu en autre chose?
OJ Raqueño
13
Le fait que vous n'utilisiez pas d'action d'envoi ne change pas beaucoup ma réponse. Tout ce que vous avez à faire est de vous abonner à un autre événement (un clic sur un bouton, un clic d'ancrage ou autre et simplement lire la valeur du champ masqué). En ce qui concerne l'envoi de la requête AJAX, vous pouvez utiliser l'exemple fourni dans ma réponse. Vous ne devez pas utiliser contentTypeto application/jsoncar le serveur s'attend à ce que le __RequestVerificationTokenparamètre fasse partie de la charge utile de la requête POST en utilisant application/x-www-form-urlencoded.
Darin Dimitrov
comment ce code $(this).data('url'),peut comprendre quelle serait l'url de mon contrôleur et de mon action. S'il vous plaît, expliquez. merci
Mou
2
La question d'origine concernait contentType: 'application / json'. Pour les publications ajax régulières, y compris le __RequestVerificationToken dans le formulaire, la publication fonctionnera évidemment car elle est comme une publication au format régulier. Cependant, lorsque vous souhaitez publier json (d'où le type de contenu), cela ne semble pas fonctionner. Il s'agit donc d'accepter incorrectement ce qui précède comme réponse.
John
Dois-je utiliser "ModelState.IsValid"? Comment puis-je savoir que cela fonctionne?
Moran Monovich
61

Une autre approche (moins javascript), que j'ai faite, va quelque chose comme ceci:

Tout d'abord, un assistant Html

public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper)
{
    var antiForgeryInputTag = helper.AntiForgeryToken().ToString();
    // Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="PnQE7R0MIBBAzC7SqtVvwrJpGbRvPgzWHo5dSyoSaZoabRjf9pCyzjujYBU_qKDJmwIOiPRDwBV1TNVdXFVgzAvN9_l2yt9-nf4Owif0qIDz7WRAmydVPIm6_pmJAI--wvvFQO7g0VvoFArFtAR2v6Ch1wmXCZ89v0-lNOGZLZc1" />
    var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", "");
    var tokenValue = removedStart.Replace(@""" />", "");
    if (antiForgeryInputTag == removedStart || removedStart == tokenValue)
        throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect.");
    return new MvcHtmlString(string.Format(@"{0}:""{1}""", "__RequestVerificationToken", tokenValue));
}

qui renverra une chaîne

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

afin que nous puissions l'utiliser comme ça

$(function () {
    $("#submit-list").click(function () {
        $.ajax({
            url: '@Url.Action("SortDataSourceLibraries")',
            data: { items: $(".sortable").sortable('toArray'), @Html.AntiForgeryTokenForAjaxPost() },
            type: 'post',
            traditional: true
        });
    });
});

Et cela semble fonctionner!

Max
la source
5
+1, bien. Je viens de le diviser @Html.AntiForgeryTokenForAjaxPosten deux pour obtenir le nom du jeton dans une main et sa valeur dans l'autre. Sinon, la mise en évidence de la syntaxe est tout foirée. Cela se termine comme ceci (supprimant également les guillemets simples du résultat retourné, de sorte qu'il se comporte comme n'importe quel assistant MVC, par exemple @Url):'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
Askolein
4
nit sympa. Avec cela, vous avez un fichier cshtm appel ajax .... vous ne devriez pas mox js avec rasoir à mon avis.
bunny1985
J'ai rejeté cette question parce que je pense qu'une approche plus simple consiste à utiliser la classe statique AntiForgery. Obtenir du HTML et le remplacer au lieu d'obtenir directement la valeur du jeton est une mauvaise pratique. ASP.NET est entièrement open source: github.com/ASP-NET-MVC/aspnetwebstack/blob/… (mais maintenant, il pourrait être intéressant d'écrire une autre réponse avec une méthode d'extension personnalisée qui n'obtient que le jeton)
usr-local- ΕΨΗΕΛΩΝ
4
Un moyen plus propre d'obtenir uniquement la valeur du jeton serait d'utiliser XElement. XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
3
@transformervar antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
45

c'est si simple! lorsque vous utilisez @Html.AntiForgeryToken()dans votre code html, cela signifie que le serveur a signé cette page et que chaque demande envoyée au serveur à partir de cette page particulière a un signe qui est empêché d'envoyer une fausse demande par des pirates. donc pour que cette page soit authentifiée par le serveur, vous devez suivre deux étapes:

1. envoyez un paramètre nommé __RequestVerificationTokenet pour obtenir sa valeur, utilisez les codes ci-dessous:

<script type="text/javascript">
    function gettoken() {
        var token = '@Html.AntiForgeryToken()';
        token = $(token).val();
        return token;
   }
</script>

par exemple prendre un appel ajax

$.ajax({
    type: "POST",
    url: "/Account/Login",
    data: {
        __RequestVerificationToken: gettoken(),
        uname: uname,
        pass: pass
    },
    dataType: 'json',
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    success: successFu,
});

et étape 2, décorez simplement votre méthode d'action en [ValidateAntiForgeryToken]

Abolfazl
la source
Merci, fonctionne très bien pour json post ... il me manquait contentType :(
Snziv Gupta
9

Dans Asp.Net Core, vous pouvez demander le jeton directement, comme documenté :

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

Et utilisez-le en javascript:

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

Vous pouvez ajouter le filtre global recommandé, comme indiqué :

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

Mettre à jour

La solution ci-dessus fonctionne dans les scripts qui font partie du .cshtml. Si ce n'est pas le cas, vous ne pouvez pas l'utiliser directement. Ma solution était d'utiliser un champ caché pour stocker la valeur en premier.

Ma solution de contournement, toujours en utilisant GetAntiXsrfRequestToken:

Lorsqu'il n'y a pas de formulaire:

<input type="hidden" id="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

L' nameattribut peut être omis puisque j'utilise l' idattribut.

Chaque formulaire comprend ce jeton. Ainsi, au lieu d'ajouter une autre copie du même jeton dans un champ masqué, vous pouvez également rechercher un champ existant par name. Remarque: il peut y avoir plusieurs formulaires à l'intérieur d'un document, il namen'est donc pas unique dans ce cas. Contrairement à un idattribut qui devrait être unique.

Dans le script, recherchez par identifiant:

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

Une alternative, sans avoir à référencer le token, est de soumettre le formulaire avec script.

Exemple de formulaire:

<form id="my_form" action="/something/todo/create" method="post">
</form>

Le jeton est automatiquement ajouté au formulaire en tant que champ masqué:

<form id="my_form" action="/something/todo/create" method="post">
<input name="__RequestVerificationToken" type="hidden" value="Cf..." /></form>

Et soumettez dans le script:

function DoSomething() {
    $('#my_form').submit();
}

Ou en utilisant une méthode de publication:

function DoSomething() {
    var form = $('#my_form');

    $.post("/something/todo/create", form.serialize());
}
Ruard van Elburg
la source
Je pense que cette solution ne fonctionne que si votre javascript est également dans votre fichier cshtml.
carlin.scott le
6

Dans Asp.Net MVC lorsque vous utilisez @Html.AntiForgeryToken()Razor crée un champ d'entrée masqué avec un nom __RequestVerificationTokenpour stocker les jetons. Si vous voulez écrire une implémentation AJAX, vous devez récupérer ce jeton vous-même et le transmettre en tant que paramètre au serveur afin qu'il puisse être validé.

Étape 1: Obtenez le jeton

var token = $('input[name="`__RequestVerificationToken`"]').val();

Étape 2: Passez le jeton dans l'appel AJAX

function registerStudent() {

var student = {     
    "FirstName": $('#fName').val(),
    "LastName": $('#lName').val(),
    "Email": $('#email').val(),
    "Phone": $('#phone').val(),
};

$.ajax({
    url: '/Student/RegisterStudent',
    type: 'POST',
    data: { 
     __RequestVerificationToken:token,
     student: student,
        },
    dataType: 'JSON',
    contentType:'application/x-www-form-urlencoded; charset=utf-8',
    success: function (response) {
        if (response.result == "Success") {
            alert('Student Registered Succesfully!')

        }
    },
    error: function (x,h,r) {
        alert('Something went wrong')
      }
})
};

Remarque : le type de contenu doit être'application/x-www-form-urlencoded; charset=utf-8'

J'ai téléchargé le projet sur Github; vous pouvez le télécharger et l'essayer.

https://github.com/lambda2016/AjaxValidateAntiForgeryToken

Frank Odoom
la source
Comment puis-je utiliser le formulaire sérialiser ici étudiant: $ ('# frm-student').
Serialize
6

        function DeletePersonel (id) {

                var data = new FormData ();
                data.append ("__ RequestVerificationToken", "@ HtmlHelper.GetAntiForgeryToken ()");

                $ .ajax ({
                    tapez: 'POST',
                    url: '/ Personel / Delete /' + id,
                    données: données,
                    cache: faux,
                    processData: faux,
                    contentType: faux,
                    succès: fonction (résultat) {

                    }
                });

        }
    

        classe statique publique HtmlHelper
        {
            chaîne statique publique GetAntiForgeryToken ()
            {
                System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match (System.Web.Helpers.AntiForgery.GetHtml (). ToString (), "(?: Value = \") (. *) (? : \ ")");
                if (value.Success)
                {
                    return value.Groups [1] .Value;
                }
                revenir "";
            }
        }
ismail eski
la source
3

Je sais que c'est une vieille question. Mais je vais quand même ajouter ma réponse, peut-être aider quelqu'un comme moi.

Si vous ne souhaitez pas traiter le résultat de la post-action du contrôleur, comme appeler la LoggOffméthode du Accountscontrôleur, vous pouvez faire comme la version suivante de la réponse de @DarinDimitrov:

@using (Html.BeginForm("LoggOff", "Accounts", FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
    @Html.AntiForgeryToken()
}

<!-- this could be a button -->
<a href="#" id="ajaxSubmit">Submit</a>

<script type="text/javascript">
    $('#ajaxSubmit').click(function () {

        $('#__AjaxAntiForgeryForm').submit();

        return false;
    });
</script>
Aamir
la source
3

Dans le contrôleur de compte:

    // POST: /Account/SendVerificationCodeSMS
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public JsonResult SendVerificationCodeSMS(string PhoneNumber)
    {
        return Json(PhoneNumber);
    }

En vue:

$.ajax(
{
    url: "/Account/SendVerificationCodeSMS",
    method: "POST",
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    dataType: "json",
    data: {
        PhoneNumber: $('[name="PhoneNumber"]').val(),
        __RequestVerificationToken: $('[name="__RequestVerificationToken"]').val()
    },
    success: function (data, textStatus, jqXHR) {
        if (textStatus == "success") {
            alert(data);
            // Do something on page
        }
        else {
            // Do something on page
        }
    },
    error: function (jqXHR, textStatus, errorThrown) {
        console.log(textStatus);
        console.log(jqXHR.status);
        console.log(jqXHR.statusText);
        console.log(jqXHR.responseText);
    }
});

Il est important de mettre contentTypeà 'application/x-www-form-urlencoded; charset=utf-8'ou tout simplement omettre contentTypede l'objet ...

Adel Mourad
la source
pas vraiment pratique, cela signifie que vous devez coder chaque formulaire, et si les formulaires contiennent beaucoup d'éléments, cela pourrait être
pénible
0

J'ai essayé beaucoup de solutions de contournement et aucune n'a fonctionné pour moi. L'exception était "Le champ obligatoire du formulaire anti-contrefaçon" __RequestVerificationToken ".

Ce qui m'a aidé, c'est de passer du formulaire .ajax à .post:

$.post(
    url,
    $(formId).serialize(),
    function (data) {
        $(formId).html(data);
    });
Stefan Michev
la source
0

N'hésitez pas à utiliser la fonction ci-dessous:

function AjaxPostWithAntiForgeryToken(destinationUrl, successCallback) {
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;
$.ajax({
    type: "POST",
    url: destinationUrl,
    data: { __RequestVerificationToken: token }, // Your other data will go here
    dataType: "json",
    success: function (response) {
        successCallback(response);
    },
    error: function (xhr, status, error) {
       // handle failure
    }
});

}

Komal Narang
la source
0

Le jeton ne fonctionnera pas s'il a été fourni par un autre contrôleur. Par exemple, cela ne fonctionnera pas si la vue a été renvoyée par le Accountscontrôleur, mais par vous POSTau Clientscontrôleur.

Exceptionnel
la source