HttpAntiForgeryException en ASP.NET MVC

Hola a todos, gracias a Jose María Aguilar hoy he resuelto un pequeño problema con ASP.NET MVC de esos de los que te pegas dos días loco.

La cosa se puede reproducir muy fácilmente creando un nuevo proyecto MVC tal cual nos lo genera Visual Studio 2012:

        MVC Default Project         

Si la ejecutamos directamente  nos aparecerá la web por defecto totalmente funcionando. Ahora, vamos a registrar un nuevo usuario, y nos mantendremos logados.

Para reproducir el error que me ha vuelto loco sólo tenemos que deslogarnos, hacer clic en login y logarnos. Una vez logamos le damos al botón del navegador para que vaya a la página anterior, y veremos que se mantiene el nombre de nuestro usuario:User Logged

Y la gracia está en que si escribimos nuestro password y nos logamos obtenemos un bonito error:

Server Error in '/' Application.


The provided anti-forgery token was meant for user "", but the current user is "juanma".

             Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.Mvc.HttpAntiForgeryException: The provided anti-forgery token was meant for user "", but the current user is "juanma".
Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[HttpAntiForgeryException (0x80004005): The provided anti-forgery token was meant for user "", but the current user is "juanma".]
   System.Web.Helpers.AntiXsrf.TokenValidator.ValidateTokens(HttpContextBase httpContext, IIdentity identity, AntiForgeryToken sessionToken, AntiForgeryToken fieldToken) +234369
   System.Web.Helpers.AntiXsrf.AntiForgeryWorker.Validate(HttpContextBase httpContext) +71
   System.Web.Helpers.AntiForgery.Validate() +80
   System.Web.Mvc.ValidateAntiForgeryTokenAttribute.OnAuthorization(AuthorizationContext filterContext) +22
   System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor) +96
   System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState) +446
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +302
   System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__17(AsyncCallback asyncCallback, Object asyncState) +30
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +382
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +317
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +15
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__2(AsyncCallback asyncCallback, Object asyncState) +71
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +249
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +50
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.17929            

 Simplemente precioso.

Es un problema de la caché,  y es que el forgery-token se guarda información relativa a la sesión que hemos iniciado con el servidor, entre ellas el usuario logado. Al ser una petición GET el navegador intenta obtener ese token de la caché, pero el servidor se da cuenta de que ese token ha sido cargado de la caché del navegador y no ha sido generada en la petición actual, por lo que, para evitar ataques de Cross-Site Request Forgery, nuestro servidor (que es muy listo) dice: eh!, ¿a donde te crees que vas?

La solución para que el navegador no nos cargue ese token (y otras cosas) en las peticiones GET que hagamos cuando queremos logarnos es indicarlo en nuestro método de login. En un proyecto MVC4 por defecto, ese método está en la clase AccountController.cs

 [AllowAnonymous]
 public ActionResult Login(string returnUrl)
 {
    ViewBag.ReturnUrl = returnUrl;
    return View();
 }

¿Cómo se lo indicamos?, añadiendo el atributo OutputCache de la siguiente manera:

 [AllowAnonymous]
 [OutputCache(NoStore = true, Duration = 0)]
 public ActionResult Login(string returnUrl)
 {
    ViewBag.ReturnUrl = returnUrl;
    return View();
 }

Si recompilamos y ejecutamos repitiendo el proceso de logarnos, navegar a la página anterior con el botón del navegador y haciendo clic en Login, ya no nos aparece el nombre de nuestro usuario anterior, sino que aparece vacío y podemos logarnos sin ningún problema y sin excepciones

Espero que os haya gustado y de nuevo tengo que darle las gracias a Jose María Aguilar por su ayuda 🙂

2 pensamientos en “HttpAntiForgeryException en ASP.NET MVC

  1. Marcin

    Nice, but I’m afraid this is only partial solution:
    1. The problem is on browser cache not on server one
    2. Safari totaly ignores such setting (especially mobile one)
    3. Even disabling browser cache with proper headers: no-cache, no-store,must-revalidate doean’t make any difference for safari

Los comentarios están cerrados.