jQuery Ajax llama y Html.AntiForgeryToken ()

Implementé en mi aplicación la mitigación de ataques CSRF siguiendo las informaciones que he leído en algunas publicaciones de blogs en Internet. En particular, estas publicaciones han sido el motor de mi implementación

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

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

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

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

2) Agregue el ayudante dentro de los formularios que envían datos al servidor

 

De todos modos, en algunas partes de mi aplicación estoy haciendo AJAx POST con jQuery para el servidor sin tener ningún formulario en absoluto. Esto sucede, por ejemplo, cuando estoy dejando 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 la actividad como completada” y cuando el usuario hace clic en esa actividad, estoy haciendo la POST de Ajax como en la siguiente muestra:

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

¿Cómo puedo usar en estos casos? ¿Debería incluir la llamada de ayuda dentro del parámetro de datos de la llamada Ajax?

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

EDITAR :

Según la respuesta jayrdub , 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) { // .... } }); }); 

Yo uso una función js simple como esta

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

Como cada formulario en una página tendrá el mismo valor para el token, simplemente ponga algo como esto en su página maestra superior

 <%-- used for ajax in AddAntiForgeryToken() --%> 
<%= Html.AntiForgeryToken()%>

Luego, en tu ajax call do (editado para que coincida con tu segundo ejemplo)

 $.ajax({ type: "post", dataType: "html", url: $(this).attr("rel"), data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }), success: function (response) { // .... } }); 

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

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

Otra mejora es el soporte de 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 una antifalsificació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); } } }); }); 

No use Html.AntiForgeryToken . En su lugar, use AntiForgery.GetTokens y AntiForgery.Validate desde Web API como se describe en Prevención de ataques de falsificación de solicitudes entre sitios (CSRF) .

Sé que hay muchas otras respuestas, pero este artículo es agradable y conciso y te obliga a verificar 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 () { ... } }); 

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 llamadas ajax. Integrarlo en sus datos y modelos de publicaciones es desordenado e innecesario. Agregar el token como un encabezado personalizado es limpio y reutilizable, y puede configurarlo para que no tenga que recordar 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 lo mismo que un POST regular.

_Layout.cshtml

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

  

Tenga en cuenta el uso de comillas simples en la función beforeSend: el elemento de entrada que se representa usa 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 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 ajax discreto. Este código puede guardarse 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 solo aplica el atributo a tu 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); } 

Solo estaba implementando este problema real en mi proyecto actual. Lo hice para todos los ajax-POST que necesitaban un usuario autenticado.

En primer lugar, decidí enganchar mis llamadas jquery ajax para no repetirme demasiado a menudo. este fragmento de javascript garantiza que todas las llamadas ajax (posteriores) agregarán mi token de validación de solicitud a la solicitud. Nota: el nombre __RequestVerificationToken es utilizado por .Net framework para que pueda utilizar las funciones estándar de Anti-CSRF como se muestra a continuación.

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

En sus Vistas, donde necesita que el token esté disponible para el javascript anterior, simplemente use el HTML-Helper común. Básicamente puedes agregar este código donde quieras. Lo coloqué dentro de una statement if (Request.IsAuthenticated):

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

En su controlador simplemente use el mecanismo estándar ASP.Net MVC Anti-CSRF. Lo hice así (aunque en realidad usé Salt).

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

Con Firebug o una herramienta similar, puede ver fácilmente cómo sus solicitudes POST ahora tienen un parámetro __RequestVerificationToken adjunto.

Creo que todo lo que tiene que hacer es asegurarse de que la entrada “__RequestVerificationToken” esté incluida en la solicitud POST. La otra mitad de la información (es decir, el token en la cookie del usuario) ya se envía automáticamente con una solicitud AJAX POST.

P.ej,

 $("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) { // .... } }); }); 

Además de mi comentario en contra de la respuesta de @ JBall que me ayudó en el camino, esta es la respuesta final que me funciona. Estoy usando MVC y Razor y estoy enviando un formulario usando jQuery AJAX para poder actualizar una vista parcial con algunos resultados nuevos y no quería hacer una devolución de datos completa (y parpadeo de página).

Agregue @Html.AntiForgeryToken() dentro del formulario como de costumbre.

Mi código de botón de envío AJAX (es decir, un evento onclick) es:

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

He dejado la acción de “éxito”, ya que muestra cómo se está actualizando la vista parcial que contiene un MvcJqGrid y cómo se está actualizando (cuadrícula jqGrid muy poderosa y esta es una envoltura MVC shiny para ello).

Mi método de controlador se ve así:

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

Tengo que admitir que no soy partidario de PUBLICAR los datos de un formulario completo como un Modelo, pero si necesita hacerlo, esta es una de las formas que funciona. MVC simplemente hace que el enlace de datos sea demasiado fácil, así que en lugar de subponer 16 valores individuales (o un FormCollection de tipo débil), está bien, supongo. Si lo sabe mejor, hágamelo saber, ya que quiero producir un sólido código MVC C #.

Puedes hacer esto también:

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

Esto está usando Razor , pero si está usando la syntax de WebForms , puede usar las tags <%= %>

1. Definir la función para obtener Token del servidor

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

2. Obtiene el token y establece el encabezado antes de enviarlo al servidor

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

3. Validación de Onserver en HttpRequestBase en el método que maneja 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); 

encontré esta idea muy inteligente de https://gist.github.com/scottrippey/3428114 por cada llamada de $ .ajax, modifica la solicitud y agrega el 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(); } } }); 

Sé que hace tiempo que no se publicó esta pregunta, pero encontré un recurso realmente útil, que analiza el uso de AntiForgeryToken y lo hace menos problemático. También proporciona el plugin jquery para incluir fácilmente token antiforgery en llamadas AJAX:

Recetas de solicitud antifalsificación para ASP.NET MVC y AJAX

No estoy contribuyendo mucho, pero tal vez alguien lo encuentre útil.

Ligera mejora de la solución 360Airwalk. Esto incluye el token Anti falsificación dentro de la función javascript, por lo que ya no es necesario incluir @ Html.AntiForgeryToken () en cada vista.

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

Estoy usando una publicación de Ajax para ejecutar un método de eliminación (pasa a ser de una línea de tiempo de visjs pero eso no es relevante). Esto es lo que yo digo:

Este es mi Index.cshtml

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

Todo lo que agregué aquí fue @Html.AntiForgeryToken() para hacer que el token aparezca en la página

Luego, en mi publicación de Ajax, utilicé:

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

Lo cual agrega el valor del token, raspado fuera de la página, a los campos publicados

Antes de esto intenté poner el valor en los encabezados, pero obtuve el mismo error

Siéntase libre de publicar mejoras. Esto ciertamente parece ser un enfoque simple que puedo entender

AntiforgeryToken sigue siendo un dolor, ninguno de los ejemplos anteriores funcionó palabra por palabra para mí. Demasiados para allí. Así que los combiné a todos. Necesita un @ Html.AntiforgeryToken en un formulario que cuelga alrededor de iirc

Resuelto como tal:

 function Forgizzle(eggs) { eggs.__RequestVerificationToken = $($("input[name=__RequestVerificationToken]")[0]).val(); return eggs; } $.ajax({ url: url, type: 'post', data: Forgizzle({ id: id, sweets: milkway }), }); 

En caso de duda, agregue más $ signos