incluir antiforgerytoken en ajax post ASP.NET MVC

168

Tengo problemas con el AntiForgeryToken con ajax. Estoy usando ASP.NET MVC 3. Probé la solución en llamadas jQuery Ajax y Html.AntiForgeryToken () . Usando esa solución, el token ahora se pasa:

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

Cuando elimino el [ValidateAntiForgeryToken]atributo solo para ver si los datos (con el token) se pasan como parámetros al controlador, puedo ver que se pasan. Pero por alguna razón, el A required anti-forgery token was not supplied or was invalid.mensaje sigue apareciendo cuando devuelvo el atributo.

¿Algunas ideas?

EDITAR

El antiforgerytoken se está generando dentro de un formulario, pero no estoy usando una acción de envío para enviarlo. En cambio, solo obtengo el valor del token usando jquery y luego intento publicarlo en ajax.

Aquí está el formulario que contiene el token y se encuentra en la página maestra superior:

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

Respuestas:

289

Ha especificado incorrectamente el contentTypepara application/json.

Aquí hay un ejemplo de cómo esto podría funcionar.

Controlador:

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

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

Ver:

@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
fuente
Hola, gracias por la rápida respuesta. Lo siento, no lo mencioné en la pregunta; No estoy usando la acción de enviar en este momento. (El token está en un formulario, pero no estoy usando un botón de enviar para enviarlo). ¿Es posible cambiar el tipo de contenido a otra cosa?
OJ Raqueño
13
El hecho de que no esté utilizando una acción de envío no cambia mucho mi respuesta. Todo lo que necesita hacer es suscribirse a algún otro evento (un clic de botón, un clic de anclaje o lo que sea y simplemente leer el valor del campo oculto). En lo que respecta al envío de la solicitud AJAX, puede utilizar el ejemplo proporcionado en mi respuesta. No se debe utilizar contentTypepara application/jsonque el servidor está a la espera del __RequestVerificationTokenparámetro a ser parte de la carga útil petición POST usando application/x-www-form-urlencoded.
Darin Dimitrov
cómo este código $(this).data('url'),puede entender cuál sería la url de mi controlador y acción. por favor explique. gracias
Mou
2
La pregunta original era sobre contentType: 'application / json'. Para las publicaciones regulares de ajax, incluido el __RequestVerificationToken en la publicación del formulario, obviamente funcionará porque es como una publicación de formulario regular. Sin embargo, cuando desea publicar json (de ahí el tipo de contenido), esto no parece funcionar. Entonces este es un caso de aceptar incorrectamente lo anterior como respuesta.
John
¿Necesito usar "ModelState.IsValid"? ¿Cómo puedo saber que esto está funcionando?
Moran Monovich
61

Otro enfoque (menos javascript) que hice, es algo así:

Primero, un ayudante 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));
}

eso devolverá una cadena

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

para que podamos usarlo así

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

¡Y parece funcionar!

Max
fuente
55
+1, bien. Simplemente dividí el @Html.AntiForgeryTokenForAjaxPosten dos para obtener el nombre del token en una mano y su valor en la otra. De lo contrario, el resaltado de sintaxis está en mal estado. Termina así (también elimina las comillas simples del resultado devuelto, de modo que se comporta como cualquier ayudante de MVC, por ejemplo @Url):'@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'
Askolein
44
nit bien. Con esto, tienes un archivo ajax call n cshtm ... en mi opinión, no deberías mox js con navaja de afeitar.
bunny1985
He rechazado esta pregunta porque creo que un enfoque más simple es usar la clase estática AntiForgery. Obtener HTML y reemplazarlo en lugar de obtener directamente el valor del token es una mala práctica. ASP.NET es completamente de código abierto: github.com/ASP-NET-MVC/aspnetwebstack/blob/… (pero ahora podría valer la pena escribir otra respuesta con un método de extensión personalizado que solo obtenga el token)
usr-local- ΕΨΗΕΛΩΝ
44
Una forma más limpia de obtener solo el valor del token sería usar XElement. XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
3
@transformervar antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").Value
darrunategui
45

¡Es tan simple! cuando lo usa @Html.AntiForgeryToken()en su código html significa que el servidor ha firmado esta página y cada solicitud que se envía al servidor desde esta página en particular tiene un signo que los hackers impiden enviar una solicitud falsa. para que el servidor autentique esta página, debe seguir dos pasos:

1.Envíe un parámetro llamado __RequestVerificationTokeny para obtener su valor use los siguientes códigos:

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

por ejemplo tomar una llamada 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,
});

y el paso 2 simplemente decora tu método de acción [ValidateAntiForgeryToken]

Abolfazl
fuente
Gracias, funciona muy bien para json post ... me faltaba contentType :(
Snziv Gupta
9

En Asp.Net Core puede solicitar el token directamente, como se documenta :

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

Y úsalo en javascript:

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

Puede agregar el filtro global recomendado, como se documenta :

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

Actualizar

La solución anterior funciona en scripts que forman parte de .cshtml. Si este no es el caso, entonces no puede usarlo directamente. Mi solución fue usar un campo oculto para almacenar el valor primero.

Mi solución alternativa, todavía uso GetAntiXsrfRequestToken:

Cuando no hay forma:

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

El nameatributo se puede omitir ya que uso el idatributo.

Cada formulario incluye este token. Entonces, en lugar de agregar otra copia del mismo token en un campo oculto, también puede buscar un campo existente por name. Tenga en cuenta: puede haber múltiples formularios dentro de un documento, por namelo que en ese caso no es único. A diferencia de un idatributo que debería ser único.

En el script, busque por id:

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

Una alternativa, sin tener que hacer referencia al token, es enviar el formulario con script.

Formulario de muestra:

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

El token se agrega automáticamente al formulario como un campo oculto:

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

Y enviar en el script:

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

O usando un método de publicación:

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

    $.post("/something/todo/create", form.serialize());
}
Ruard van Elburg
fuente
Creo que esta solución solo funciona si su javascript también está en su archivo cshtml.
carlin.scott
6

En Asp.Net MVC cuando usas @Html.AntiForgeryToken()Razor crea un campo de entrada oculto con nombre __RequestVerificationTokenpara almacenar tokens. Si desea escribir una implementación AJAX, debe buscar este token usted mismo y pasarlo como un parámetro al servidor para que pueda ser validado.

Paso 1: Obtén el token

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

Paso 2: Pase el token en la llamada 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 : El tipo de contenido debe ser'application/x-www-form-urlencoded; charset=utf-8'

He subido el proyecto en Github; puedes descargarlo y probarlo.

https://github.com/lambda2016/AjaxValidateAntiForgeryToken

Frank Odoom
fuente
¿Cómo puedo usar el formulario serializar aquí estudiante: $ ('# frm-student'). Serialize (),
LittleDragon
6

        función DeletePersonel (id) {

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

                $ .ajax ({
                    tipo: 'POST',
                    url: '/ Personel / Delete /' + id,
                    datos: datos,
                    caché: falso
                    processData: falso,
                    contentType: falso,
                    éxito: función (resultado) {

                    }
                });

        }
    

        clase estática pública HtmlHelper
        {
            cadena estática pública GetAntiForgeryToken ()
            {
                System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match (System.Web.Helpers.AntiForgery.GetHtml (). ToString (), "(?: Value = \") (. *) (? *) (? : \ ")");
                if (value.Success)
                {
                    valor de retorno. Grupos [1] .Valor;
                }
                regreso "";
            }
        }
ismail eski
fuente
3

Sé que esta es una vieja pregunta. Pero agregaré mi respuesta de todos modos, podría ayudar a alguien como yo.

Si no desea procesar el resultado de la acción posterior del controlador, como llamar al LoggOffmétodo del Accountscontrolador, puede hacer lo siguiente en la respuesta 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
fuente
3

En el controlador de cuenta:

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

En 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);
    }
});

Es importante establecer contentTypeque 'application/x-www-form-urlencoded; charset=utf-8'o simplemente omitir contentTypedesde el objeto ...

Adel Mourad
fuente
no es realmente práctico, significa que tiene que codificar cada formulario, y si los formularios tienen muchos elementos, podría ser una molestia :(
djack109
0

Probé muchos entornos de trabajo y ninguno de ellos funcionó para mí. La excepción fue "El campo de formulario antifalsificación requerido" __RequestVerificationToken ".

Lo que me ayudó fue cambiar la forma .ajax a .post:

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

Siéntase libre de usar la función a continuación:

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

El token no funcionará si fue suministrado por un controlador diferente. Por ejemplo, no funcionará si el Accountscontrolador devolvió la vista , sino usted POSTal Clientscontrolador.

Factura pendiente
fuente