Llamadas jQuery Ajax y Html.AntiForgeryToken()

Resuelto Lorenzo asked hace 14 años • 21 respuestas

He implementado en mi aplicación la mitigación de ataques CSRF siguiendo la información que leí en alguna publicación de blog en Internet. En particular, estas publicaciones han sido el motor de mi implementación.

  • Mejores prácticas para ASP.NET MVC del equipo de contenido para desarrolladores de herramientas web y ASP.NET
  • Anatomía de un ataque de falsificación de solicitudes entre sitios del blog de Phil Haack
  • AntiForgeryToken en ASP.NET MVC Framework: atributos Html.AntiForgeryToken y ValidateAntiForgeryToken del blog de David Hayden

Básicamente, esos artículos y recomendaciones dicen que para prevenir el ataque CSRF cualquiera debería implementar el siguiente código:

  1. Agregue el [ValidateAntiForgeryToken]en cada acción que acepte el verbo POST Http

    [HttpPost] [ValidateAntiForgeryToken] ActionResult público SomeAction (modelo SomeModel) {}

  2. Agregue el <%= Html.AntiForgeryToken() %>ayudante dentro de los formularios que envía datos al servidor

De todos modos, en algunas partes de mi aplicación estoy haciendo POST Ajax con jQuery al servidor sin tener ningún formulario. Esto sucede, por ejemplo, cuando permito que el usuario haga clic en una imagen para realizar una acción específica.

Supongamos que tengo una tabla con una lista de actividades. Tengo una imagen en una columna de la tabla que dice "Marcar actividad como completada" y cuando el usuario hace clic en esa actividad estoy haciendo el Ajax POST como en el siguiente ejemplo:

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

¿ Cómo puedo utilizar el <%= Html.AntiForgeryToken() %>en estos casos? ¿Debo incluir la llamada de ayuda dentro del parámetro de datos de la llamada Ajax?

Perdón por la publicación tan larga y muchas gracias por ayudar.

EDITAR :

Según la respuesta de jayrdub , lo he usado de la siguiente manera

$("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) {
            // ....
        }
    });
});
Lorenzo avatar Nov 02 '10 07:11 Lorenzo
Aceptado

Yo uso una función js simple como esta

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

Dado que cada formulario en una página tendrá el mismo valor para el token, simplemente coloque algo como esto en su página maestra superior.

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

Luego, en su llamada ajax, haga (editado para que coincida con su segundo ejemplo)

$.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
    success: function (response) {
        // ....
    }
});
JeremyWeir avatar Nov 02 '2010 01:11 JeremyWeir

Me gusta la solución proporcionada por 360Airwalk, pero puede mejorarse un poco.

El primer problema es que si lo hace $.post()con datos vacíos, jQuery no agrega un Content-Typeencabezado y, en este caso, ASP.NET MVC no puede recibir ni verificar el token. Por lo tanto, debes asegurarte de que el encabezado esté siempre ahí.

Otra mejora es la compatibilidad con todos los verbos HTTP con contenido : POST, PUT, DELETE, etc. Aunque puede usar solo POST en su aplicación, es mejor tener una solución genérica y verificar que todos los datos que reciba con cualquier verbo tengan protección contra falsificación. simbólico.

$(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);
            }
        }
    });
});
Bronx avatar Aug 24 '2012 20:08 Bronx

Sé que hay muchas otras respuestas, pero este artículo es agradable y conciso y te obliga a revisar todos tus HttpPosts, no solo algunos de ellos:

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

Utiliza encabezados HTTP en lugar de intentar modificar la colección de formularios.

Servidor

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

Cliente

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 () {
        ...
    }
});
viggity avatar Jan 17 '2014 21:01 viggity

Me siento como un nigromante avanzado aquí, pero esto sigue siendo un problema 4 años después en MVC5.

Para manejar las solicitudes ajax correctamente, el token antifalsificación debe pasarse al servidor en las llamadas ajax. Integrarlo en los datos y modelos de sus publicaciones es complicado e innecesario. Agregar el token como un encabezado personalizado es limpio y reutilizable, y puedes configurarlo para no tener que acordarte de hacerlo cada vez.

Hay una excepción: el ajax discreto no necesita un tratamiento especial para las llamadas ajax. El token se pasa como de costumbre en el campo de entrada oculto normal. Exactamente igual que un POST normal.

_Diseño.cshtml

En _layout.cshtml tengo este bloque de JavaScript. No escribe el token en el DOM, sino que utiliza jQuery para extraerlo del literal de entrada oculto que genera MVC Helper. La cadena mágica que es el nombre del encabezado se define como una constante en la clase de atributo.

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative

        $.ajaxSetup({
            beforeSend: function (xhr) {
                if (!isAbsoluteURI.test(this.url)) {
                    //only add header to relative URLs
                    xhr.setRequestHeader(
                       '@.ValidateAntiForgeryTokenOnAllPosts.HTTP_HEADER_NAME', 
                       $('@Html.AntiForgeryToken()').val()
                    );
                }
            }
        });
    });
</script>

Tenga en cuenta el uso de comillas simples en la función beforeSend: el elemento de entrada que se representa utiliza comillas dobles que romperían el literal de JavaScript.

JavaScript del cliente

Cuando esto se ejecuta, se llama a la función beforeSend anterior y el AntiForgeryToken se agrega automáticamente a los encabezados de la solicitud.

$.ajax({
  type: "POST",
  url: "CSRFProtectedMethod",
  dataType: "json",
  contentType: "application/json; charset=utf-8",
  success: function (data) {
    //victory
  }
});

Biblioteca del servidor

Se requiere un atributo personalizado para procesar el token no estándar. Esto se basa en la solución de @viggity, pero maneja correctamente el ajax discreto. Este código se puede guardar en su biblioteca común.

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

Servidor/Controlador

Ahora simplemente aplica el atributo a su Acción. Aún mejor, puede aplicar el atributo a su controlador y todas las solicitudes serán validadas.

[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
  return Json(true, JsonRequestBehavior.DenyGet);
}
Will D avatar Dec 16 '2014 02:12 Will D