jQuery appels Ajax et le Html.AntiForgeryToken()

j'ai mis en œuvre dans mon application l'atténuation à attaques CSRF suite aux informations que j'ai lues sur un billet de blog autour de l'internet. En particulier, ces postes ont été le moteur de ma mise en œuvre

  • Best Practices for ASP.NET MVC de la ASP.NET
  • Anatomie d'un Cross-site Request Forgery Attaque du blog Phil Haack
  • AntiForgeryToken dans le ASP.NET MVC Framework-Html.AntiForgeryToken et ValidateAntiForgeryToken attribut du blog de David Hayden

essentiellement ces articles et recommandations dit que pour prévenir L'attaque CSRF n'importe qui devrait mettre en œuvre le code suivant:

1) ajouter le [ValidateAntiForgeryToken] sur chaque action qui accepte le verbe Http POST

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}

2) Ajouter le <%= Html.AntiForgeryToken() %> aide à l'intérieur de formes qui envoie des données au serveur

<div style="text-align:right; padding: 8px;">
    <%= Html.AntiForgeryToken() %>
    <input type="submit" id="btnSave" value="Save" />
</div>

de toute façon dans certaines parties de mon application je fais des messages Ajax avec jQuery au serveur sans avoir aucune forme du tout. Cela se produit par exemple lorsque je laisse l'utilisateur cliquer sur une image pour effectuer une action spécifique.

supposons que j'ai un tableau avec une liste d'activités. J'ai une image dans une colonne de la table dit "marquer l'activité comme terminée" et quand l'utilisateur clique sur cette activité, je fais le POST Ajax comme dans l'exemple suivant:

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {},
        success: function (response) {
            // ....
        }
    });
});

comment utiliser le <%= Html.AntiForgeryToken() %> dans ces cas? Dois-je inclure l'appel helper dans le paramètre données de L'appel Ajax?

Désolé pour le long post et merci beaucoup pour votre aide

MODIFIER :

selon jayrdub réponse j'ai utilisé de la manière suivante

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {
            AddAntiForgeryToken({}),
            id: parseInt($(this).attr("title"))
        },
        success: function (response) {
            // ....
        }
    });
});
183
demandé sur Community 2010-11-02 03:39:14

18 réponses

j'utilise une simple fonction js comme celle-ci

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

puisque chaque forme sur une page aura la même valeur pour le token, il suffit de mettre quelque chose comme ça dans votre plus grande page maître

<%-- used for ajax in AddAntiForgeryToken() --%>
<form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>  

puis dans votre appel ajax faites (édité pour correspondre à votre deuxième exemple)

$.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
    success: function (response) {
        // ....
    }
});
229
répondu JeremyWeir 2010-11-02 02:37:55

j'aime la solution fournie par 360Airwalk, mais elle peut être améliorée un peu.

le premier problème est que si vous faites $.post() avec des données vides, jQuery n'ajoute pas d'en-tête Content-Type , et dans ce cas ASP.NET MVC ne reçoit pas et ne vérifie pas le token. Vous devez donc vous assurer que l'en-tête est toujours là.

une autre amélioration est le support de tous les verbes HTTP avec le contenu : POST, PUT, DELETE etc. Bien que vous puissiez utilisez seulement les messages dans votre application, il est préférable d'avoir une solution générique et de vérifier que toutes les données que vous recevez avec un verbe a un jeton anti-contrefaçon.

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $(document).ajaxSend(function (event, request, opt) {
        if (opt.hasContent && securityToken) {   // handle all verbs with content
            var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            opt.data = opt.data ? [opt.data, tokenParam].join("&") : tokenParam;
            // ensure Content-Type header is present!
            if (opt.contentType !== false || event.contentType) {
                request.setRequestHeader( "Content-Type", opt.contentType);
            }
        }
    });
});
26
répondu Bronx 2016-04-20 19:51:02

Ne pas utiliser Html.Anti-forgerytoken . Au lieu de cela, utilisez AntiForgery.GetTokens et AntiForgery.Valider à partir de API Web tel que décrit dans empêcher les attaques de falsification de requêtes intersite (CSRF).

18
répondu Edward Brey 2013-03-06 18:33:14

je sais qu'il y a beaucoup d'autres réponses, mais cet article est gentil et concis et vous oblige à vérifier tous vos poteaux de Httpp, pas seulement certains d'entre eux:

http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests /

il utilise des en-têtes HTTP au lieu d'essayer de modifier la collection de formulaires.

Serveur

//make sure to add this to your global action filters
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {
            //  Ajax POSTs and normal form posts have to be treated differently when it comes
            //  to validating the AntiForgeryToken
            if (request.IsAjaxRequest())
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value 
                    : null;

                AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}
Client

var token = $('[name=__RequestVerificationToken]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: headers,
    contentType: 'application/json; charset=utf-8',
    data: { title: "This is my title", contents: "These are my contents" },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});
16
répondu viggity 2014-09-03 22:28:31

je viens de mettre en œuvre ce problème réel dans mon projet actuel. Je l'ai fait pour tous les ajax-postes qui avaient besoin d'un utilisateur authentifié.

d'Abord, j'ai décidé d'accrocher mes appels ajax jquery donc je n'ai pas à me répéter trop souvent. cet extrait de javascript garantit que tous les appels ajax (post) ajouteront mon token de validation de requête à la requête. Note: le nom _ _ RequestVerificationToken est utilisé par le framework. Net pour que je puisse utiliser les fonctionnalités Anti-CSRF standard comme indiqué dessous.

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $('body').bind('ajaxSend', function (elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});

dans vos vues où vous avez besoin du token pour être disponible au javascript ci-dessus utilisez simplement L'aide HTML commune. Vous pouvez essentiellement ajouter ce code où vous voulez. Je l'ai placé à l'intérieur d'un if(Request.IsAuthenticated) statement:

@Html.AntiForgeryToken() // you can provide a string as salt when needed which needs to match the one on the controller

dans votre controller il suffit d'utiliser le standard ASP.Net MVC Anti-CSRF mécanisme. Je l'ai fait comme ça (même si j'ai utilisé du sel).

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
    // do something
    return Json(true);
}

avec coupe-feu ou un outil similaire vous pouvez facilement voir comment vos requêtes POST ont maintenant un paramètre _ _ RequestVerificationToken ajouté.

15
répondu 360Airwalk 2016-01-07 13:06:27

je me sens comme un nécromancien avancé ici, mais c'est encore un problème 4 ans plus tard dans MVC5.

pour traiter correctement les requêtes ajax, le token anti-contrefaçon doit être passé au serveur lors des appels ajax. L'intégrer à vos données post et modèles est compliqué et inutile. Ajouter le jeton comme un en-tête personnalisé est propre et réutilisable, et vous pouvez le configurer de sorte que vous n'avez pas à vous rappeler de le faire à chaque fois.

il y a une exception - Ajax discret n'a pas besoin de traitement spécial pour les appels ajax. Le jeton est passé comme d'habitude dans l'ordinaire de champ caché. Exactement le même QU'un poste régulier.

_Layout.CSHTML

Dans _layout.CSHTML j'ai ce bloc JavaScript. Il n'écrit pas le token dans le DOM, il utilise plutôt jQuery pour l'extraire de la saisie littérale cachée que le Helper MVC génère. La chaîne magique qui est le nom d'en-tête est définie comme une constante dans attribut de la classe.

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        ///q/how-to-test-if-a-url-string-is-absolute-or-relative-61629/"POST",
  url: "CSRFProtectedMethod",
  dataType: "json",
  contentType: "application/json; charset=utf-8",
  success: function (data) {
    //victory
  }
});

serveur Bibliothèque

un attribut personnalisé est requis pour traiter le token non standard. Cela s'appuie sur la solution de @viggity, mais gère correctement l'ajax discret. Ce code peut être caché dans votre bibliothèque commune

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public const string HTTP_HEADER_NAME = "x-RequestVerificationToken";

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {

            var headerTokenValue = request.Headers[HTTP_HEADER_NAME];

            // Ajax POSTs using jquery have a header set that defines the token.
            // However using unobtrusive ajax the token is still submitted normally in the form.
            // if the header is present then use it, else fall back to processing the form like normal
            if (headerTokenValue != null)
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value
                    : null;

                AntiForgery.Validate(cookieValue, headerTokenValue);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

Serveur / Contrôleur

maintenant vous appliquez l'attribut à votre Action. Même mieux, vous pouvez appliquer l'attribut à votre contrôleur et toutes les demandes seront validées.

[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
  return Json(true, JsonRequestBehavior.DenyGet);
}
14
répondu Will D 2014-12-16 03:11:36

je pense que tout ce que vous avez à faire est de vous assurer que l'entrée" __RequestVerificationToken " est incluse dans la requête POST. L'autre moitié des informations (c'est-à-dire le token dans le cookie de l'utilisateur) est déjà envoyée automatiquement avec une requête AJAX POST.

par exemple,

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: { 
            "__RequestVerificationToken":
            $("input[name=__RequestVerificationToken]").val() 
        },
        success: function (response) {
            // ....
        }
    });
});
11
répondu jball 2010-11-02 01:16:36

vous pouvez le faire aussi:

$("a.markAsDone").click(function (event) {
    event.preventDefault();

    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: $('<form>@Html.AntiForgeryToken()</form>').serialize(),
        success: function (response) {
        // ....
        }
    });
});

cela utilise Razor , mais si vous utilisez la syntaxe WebForms vous pouvez tout aussi bien utiliser <%= %> tags

5
répondu Leonardo Garcia Crespo 2017-02-21 07:08:26

suite à mon commentaire contre la réponse de @JBall qui m'a aidé en cours de route, c'est la réponse finale qui fonctionne pour moi. J'utilise MVC et Razor et je soumets un formulaire en utilisant jQuery AJAX donc je peux mettre à jour une vue partielle avec de nouveaux résultats et je ne voulais pas faire un postback complet (et le clignotant de page).

ajouter le @Html.AntiForgeryToken() à l'intérieur du formulaire comme d'habitude.

mon code de bouton de soumission AJAX (i.e. un événement onclick) est:

//User clicks the SUBMIT button
$("#btnSubmit").click(function (event) {

//prevent this button submitting the form as we will do that via AJAX
event.preventDefault();

//Validate the form first
if (!$('#searchForm').validate().form()) {
    alert("Please correct the errors");
    return false;
}

//Get the entire form's data - including the antiforgerytoken
var allFormData = $("#searchForm").serialize();

// The actual POST can now take place with a validated form
$.ajax({
    type: "POST",
    async: false,
    url: "/Home/SearchAjax",
    data: allFormData,
    dataType: "html",
    success: function (data) {
        $('#gridView').html(data);
        $('#TestGrid').jqGrid('setGridParam', { url: '@Url.Action("GetDetails", "Home", Model)', datatype: "json", page: 1 }).trigger('reloadGrid');
    }
});

j'ai laissé l'action" success " Dans car elle montre comment la vue partielle est mise à jour qui contient un MvcJqGrid et comment il est rafraîchi (grille jqGrid très puissante et c'est un emballage MVC brillant pour elle).

ma méthode de contrôleur ressemble à ceci:

    //Ajax SUBMIT method
    [ValidateAntiForgeryToken]
    public ActionResult SearchAjax(EstateOutlet_D model) 
    {
        return View("_Grid", model);
    }

je dois admettre que je ne suis pas un fan de poster les données d'un formulaire entier comme un modèle, mais si vous avez besoin de le faire, alors c'est une façon qui fonctionne. MVC fait juste les données se lient trop facilement donc plutôt que de soumettre 16 valeurs individuelles (ou un FormCollection faiblement typé) c'est OK, je suppose. Si vous le savez mieux s'il vous plaît laissez-moi savoir que je veux produire robuste code MVC C#.

4
répondu Ralph Bacon 2013-09-08 06:41:19

1.Définir la fonction pour obtenir le Token du serveur

@function
{

        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
}

2.Obtenir le token et définir l'en-tête avant d'envoyer au serveur

var token = '@TokenHeaderValue()';    

       $http({
           method: "POST",
           url: './MainBackend/MessageDelete',
           data: dataSend,
           headers: {
               'RequestVerificationToken': token
           }
       }).success(function (data) {
           alert(data)
       });

3. Validation Onserver sur HttpRequestBase sur la méthode que vous utilisez Post / get

        string cookieToken = "";
        string formToken = "";
        string[] tokens = Request.Headers["RequestVerificationToken"].Split(':');
            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        AntiForgery.Validate(cookieToken, formToken);
3
répondu Tonman Neverwalk alone 2015-05-22 19:17:00

a trouvé cette idée très intelligente de https://gist.github.com/scottrippey/3428114 pour chaque Dollar.ajax appelle il modifie la requête et ajoute le token.

// Setup CSRF safety for AJAX:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        var token = $("input[name^=__RequestVerificationToken]").first();
        if (!token.length) return;

        var tokenName = token.attr("name");

        // If the data is JSON, then we need to put the token in the QueryString:
        if (options.contentType.indexOf('application/json') === 0) {
            // Add the token to the URL, because we can't add it to the JSON data:
            options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize();
        } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) {
            // Append to the data string:
            options.data += (options.data ? "&" : "") + token.serialize();
        }
    }
});
3
répondu masterlopau 2015-07-07 22:53:55

je sais que cela fait un certain temps que cette question a été postée, mais j'ai trouvé la ressource vraiment utile, qui discute de l'usage D'Anti-Forgerytoken et le rend moins gênant à utiliser. Il fournit également jQuery plugin pour facilement inclure jeton antiforgery dans les appels AJAX:

Anti-Forgery Request Recipes For ASP.NET MVC et AJAX

Je ne contribue pas beaucoup, mais peut-être que quelqu'un le trouvera utile.

1
répondu slawek 2012-02-27 15:10:42

légère amélioration à la solution 360Airwalk. Cela imprime le jeton anti-contrefaçon dans la fonction javascript, so @Html.AntiForgeryToken() n'a plus besoin d'être inclus sur chaque vue.

$(document).ready(function () {
    var securityToken = $('@Html.AntiForgeryToken()').attr('value');
    $('body').bind('ajaxSend', function (elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});
0
répondu Barry MSIH 2014-10-03 18:56:20
function DeletePersonel(id) {

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

    $.ajax({
        type: 'POST',
        url: '/Personel/Delete/' + id,
        data: data,
        cache: false,
        processData: false,
        contentType: false,
        success: function (result) {
        }
    });
}

public static class HtmlHelper {
    public static string 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;
        }
        return "";
    }
}
0
répondu ismail eski 2016-09-01 20:40:23

j'utilise un post ajax pour lancer une méthode de suppression (qui se trouve être dans une ligne de temps visjs mais qui n'est pas relelevant). C'est ce que je dis:

C'est mon Index.CSHTML

@Scripts.Render("~/bundles/schedule")
@Styles.Render("~/bundles/visjs")
@Html.AntiForgeryToken()

<!-- div to attach schedule to -->
<div id='schedule'></div>

<!-- div to attach popups to -->
<div id='dialog-popup'></div>

Tout ce que j'ai ajouté ici était @Html.AntiForgeryToken() pour faire apparaître le jeton dans la page

puis dans mon post ajax j'ai utilisé:

$.ajax(
    {
        type: 'POST',
        url: '/ScheduleWorks/Delete/' + item.id,
        data: {
            '__RequestVerificationToken': 
            $("input[name='__RequestVerificationToken']").val()
              }
     }
);

qui ajoute la valeur du jeton, Raclée de la page, aux champs affichés

avant cela j'ai essayé de mettre la valeur dans les en-têtes mais j'ai eu la même erreur

n'hésitez pas à poster des améliorations. Cela semble être une approche simple que je peux comprendre

0
répondu Nick.McDermaid 2017-06-26 10:19:09

première utilisation @Html.AntiForgeryToken () en html

 $.ajax({
        url: "@Url.Action("SomeMethod", "SomeController")",
        type: 'POST',
        data: JSON.stringify(jsonObject),
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        async: false,
        beforeSend: function (request) {
            request.setRequestHeader("RequestVerificationToken", $("[name='__RequestVerificationToken']").val());
        },
        success: function (msg) {
            alert(msg);
        }
0
répondu Amir Reza 2018-09-30 07:32:13

voici le moyen le plus facile que j'ai vu. Note: assurez-vous d'avoir "@Html.AntiForgeryToken ()" de votre point de vue

  $("a.markAsDone").click(function (event) {
        event.preventDefault();
        var sToken = document.getElementsByName("__RequestVerificationToken")[0].value;
        $.ajax({
            url: $(this).attr("rel"),
            type: "POST",
            contentType: "application/x-www-form-urlencoded",
            data: { '__RequestVerificationToken': sToken, 'id': parseInt($(this).attr("title")) }
        })
        .done(function (data) {
            //Process MVC Data here
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            //Process Failure here
        });
    });
0
répondu Dominic Sputo 2018-10-03 20:27:22

AntiforgeryToken est encore une douleur, aucun des exemples ci-dessus travaillé mot pour mot pour moi. Trop nombreux pour. Alors je les ai tous combinés. Besoin d'un @Html.AntiforgeryToken dans une forme qui traîne autour de iirc

résolu comme suit:

function Forgizzle(eggs) {
    eggs.__RequestVerificationToken =  $($("input[name=__RequestVerificationToken]")[0]).val();
    return eggs;
}

$.ajax({
            url: url,
            type: 'post',
            data: Forgizzle({ id: id, sweets: milkway }),
});

en cas de doute, ajouter plus de $ signs

-2
répondu Hazza 2011-11-14 10:10:26