incluir antiforgerytoken no ajax post ASP.NET MVC

168

Estou tendo problemas com o AntiForgeryToken com ajax. Estou usando o ASP.NET MVC 3. Tentei a solução nas chamadas do jQuery Ajax e no Html.AntiForgeryToken () . Usando essa solução, o token agora está sendo passado:

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) {
            ...
        }
    });

Quando removo o [ValidateAntiForgeryToken]atributo apenas para ver se os dados (com o token) estão sendo passados ​​como parâmetros para o controlador, posso ver que eles estão sendo passados. Mas, por algum motivo, a A required anti-forgery token was not supplied or was invalid.mensagem ainda aparece quando eu coloco o atributo de volta.

Alguma ideia?

EDITAR

O token antiforgery está sendo gerado dentro de um formulário, mas não estou usando uma ação de envio para enviá-lo. Em vez disso, estou apenas obtendo o valor do token usando jquery e depois tentando postá-lo no ajax.

Aqui está o formulário que contém o token e está localizado na página principal do topo:

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

Respostas:

289

Você especificou incorretamente o contentTypepara application/json.

Aqui está um exemplo de como isso pode funcionar.

Controlador:

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

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

Visão:

@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
fonte
Oi, obrigado pela resposta rápida. Desculpe não ter mencionado na pergunta; Não estou usando a ação de envio no momento. (O token está em um formulário, mas não estou usando um botão de envio para enviá-lo). É possível apenas alterar o tipo de conteúdo para outra coisa?
JO Raqueño 23/01
13
O fato de você não estar usando uma ação de envio não altera muito minha resposta. Tudo o que você precisa fazer é se inscrever em algum outro evento (um clique no botão, um clique na âncora ou o que for e simplesmente leia o valor do campo oculto). No que diz respeito ao envio da solicitação AJAX, você pode usar o exemplo fornecido na minha resposta. Você não deve usar contentTypea application/jsonporque o servidor está esperando o __RequestVerificationTokenparâmetro a ser parte da carga útil solicitação POST usando application/x-www-form-urlencoded.
Darin Dimitrov
como esse código $(this).data('url'),pode entender qual seria o URL do meu controlador e ação. Por favor explique. graças
Mou
2
A pergunta original era sobre contentType: 'application / json'. Para postagens regulares do ajax, incluindo o __RequestVerificationToken na postagem do formulário, obviamente funcionará porque é como uma postagem regular do formulário. No entanto, quando você deseja publicar o json (daí o tipo de conteúdo), isso não parece funcionar. Portanto, este é um caso de aceitar incorretamente o acima como resposta.
John
Preciso usar "ModelState.IsValid"? Como posso saber que isso está funcionando?
Moran Monovich
61

Outra abordagem (menos javascript), que eu fiz, é mais ou menos assim:

Primeiro, um ajudante de 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));
}

que retornará uma string

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

para que possamos usá-lo assim

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

E parece que funciona!

Máx.
fonte
5
+1, legal. Eu apenas dividi o @Html.AntiForgeryTokenForAjaxPostem dois para obter o nome do token em uma mão e seu valor na outra. Caso contrário, o destaque da sintaxe estará todo bagunçado. Ele acaba assim (removeu as aspas simples do resultado retornado também, para que ele se comporta como qualquer ajudante MVC, por exemplo @url):'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
Askolein
4
nit nice. Com isso, você tem um arquivo ajax call n cshtm .... você não deve mox js com navalha na minha opinião.
bunny1985
Abaixei esta questão porque acredito que uma abordagem mais simples é usar a classe estática do AntiForgery. Obter HTML e substituí-lo em vez de obter diretamente o valor do token é uma prática recomendada. O ASP.NET é totalmente open source: github.com/ASP-NET-MVC/aspnetwebstack/blob/… (mas agora pode valer a pena escrever outra resposta com um método de extensão personalizado que obtém apenas o token)
usr-local- ΕΨΗΕΛΩΝ
4
Uma maneira mais limpa de obter apenas o valor do token seria usar o XElement. XElement.Parse(antiForgeryInputTag).Attribute("value").Value
31517 darrunategui
3
@transformervar antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
45

é tão simples! Quando você usa @Html.AntiForgeryToken()seu código html, significa que o servidor assinou esta página e cada solicitação enviada ao servidor a partir dessa página específica tem um sinal impedido de enviar uma solicitação falsa por hackers. portanto, para que esta página seja autenticada pelo servidor, você deve seguir duas etapas:

1. envie um parâmetro chamado __RequestVerificationTokene para obter seu valor, use os códigos abaixo:

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

por exemplo, faça uma chamada 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,
});

e a etapa 2 apenas decore seu método de ação [ValidateAntiForgeryToken]

Abolfazl
fonte
Obrigado, funciona muito bem para pós json ... i estava faltando contentType :(
Snziv Gupta
9

No Asp.Net Core, você pode solicitar o token diretamente, conforme documentado :

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

E use-o em javascript:

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

Você pode adicionar o filtro global recomendado, conforme documentado :

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

Atualizar

A solução acima funciona em scripts que fazem parte do .cshtml. Se não for esse o caso, não será possível usá-lo diretamente. Minha solução foi usar um campo oculto para armazenar o valor primeiro.

Minha solução alternativa, ainda usando GetAntiXsrfRequestToken:

Quando não há forma:

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

O nameatributo pode ser omitido desde que eu o use id.

Cada formulário inclui esse token. Portanto, em vez de adicionar mais uma cópia do mesmo token em um campo oculto, você também pode procurar por um campo existente por name. Observe: pode haver vários formulários dentro de um documento, portanto, namenesse caso, não é exclusivo. Ao contrário de um idatributo que deve ser exclusivo.

No script, encontre por id:

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

Uma alternativa, sem precisar fazer referência ao token, é enviar o formulário com script.

Formulário de exemplo:

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

O token é adicionado automaticamente ao formulário como um campo oculto:

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

E envie no script:

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

Ou usando um método de postagem:

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

    $.post("/something/todo/create", form.serialize());
}
Ruard van Elburg
fonte
Penso que esta solução só funciona se o seu javascript também estiver no seu arquivo cshtml.
carlin.scott
6

No Asp.Net MVC, quando você usa o @Html.AntiForgeryToken()Razor, cria um campo de entrada oculto com o nome __RequestVerificationTokenpara armazenar tokens. Se você deseja escrever uma implementação AJAX, é necessário buscar esse token e passá-lo como parâmetro ao servidor para que possa ser validado.

Etapa 1: obter o token

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

Etapa 2: passe o token na chamada 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')
      }
})
};

Nota : O tipo de conteúdo deve ser'application/x-www-form-urlencoded; charset=utf-8'

Fiz upload do projeto no Github; você pode baixar e experimentar.

https://github.com/lambda2016/AjaxValidateAntiForgeryToken

Frank Odoom
fonte
Como posso usar o formulário serialize aqui estudante:. $ ( '# Frm-aluno') serialize (),
LittleDragon
6

        função DeletePersonel (id) {

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

                $ .ajax ({
                    tipo: 'POST',
                    URL: '/ Pessoal / Excluir /' + id,
                    dados: dados,
                    cache: false,
                    processData: false,
                    contentType: false,
                    success: function (result) {

                    }
                });

        }
    

        classe estática pública HtmlHelper
        {
            sequência estática pública GetAntiForgeryToken ()
            {
                Valor System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match (System.Web.Helpers.AntiForgery.GetHtml (). ToString (), "(?: Value = \") (. *) (? : \ ")");
                if (value.Success)
                {
                    return value.Groups [1] .Value;
                }
                Retorna "";
            }
        }
ismail eski
fonte
3

Eu sei que esta é uma pergunta antiga. Mas vou adicionar minha resposta de qualquer maneira, pode ajudar alguém como eu.

Se você não quiser processar o resultado da ação posterior do controlador, como chamar o LoggOffmétodo de Accountscontrolador, faça o seguinte: a seguinte versão da resposta 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
fonte
3

No controlador de conta:

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

Em vista:

$.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);
    }
});

É importante conjunto contentTypepara 'application/x-www-form-urlencoded; charset=utf-8'ou apenas omitir contentTypedo objeto ...

Adel Mourad
fonte
não é realmente prático, significa que você precisa codificar todos os formulários, e se os formulários tiverem muitos elementos, pode ser uma dor :(
djack109
0

Eu tentei muitas soluções alternativas e nenhuma delas funcionou para mim. A exceção foi "O campo obrigatório do formulário anti-falsificação" __RequestVerificationToken ".

O que me ajudou foi mudar o formato .ajax para .post:

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

Sinta-se livre para usar a função abaixo:

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
fonte
0

O token não funcionará se tiver sido fornecido por um controlador diferente. Por exemplo, não funcionará se a visualização foi retornada pelo Accountscontrolador, mas você POSTpara o Clientscontrolador.

Conta para vencer
fonte