21

I have an MVC 4 app and having issues when the forms session expires and then the user tries to logoff.

Ex. timeout is set to 5 min. User logs in. User does nothing for 10 min. User clicks the LogOff link. User gets error: "The provided anti-forgery token was meant for user "XXXX", but the current user is ""."

The user then had to go through some gymnastics to get around this so they can re-login then re-logout (logout is being used to close their timecard for the day).

I think I understand why this happens...but not sure how to fix it.

EDIT: Why I think this is happening is because originally when the page is loaded, the AntiForgery token is generated for the currently logged in user. But then when the session expires and they try to navigate to the logoff page, the current user is "" instead of the actual user. As such there is a mismatch and the error is rendered.

Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
crichavin
  • 4,672
  • 10
  • 50
  • 95
  • Can you share your hypothesis why the error occurs? – J0e3gan May 07 '13 at 01:50
  • A [related post](http://stackoverflow.com/questions/14970102/anti-forgery-token-is-meant-for-user-but-the-current-user-is-username) looks worth considering. – J0e3gan May 07 '13 at 01:55
  • Added my hypothesis. I saw that post you referred to, and though it may be related, I am not able to connect the dots...I'm not real familiar with this aspect of ASP.NET. – crichavin May 07 '13 at 02:14
  • Also, consider [this related post](http://stackoverflow.com/questions/5767768/troubleshooting-anti-forgery-token-problems) too - full of troubleshooting information based on the MVC code that you may be able to connect to what you see in your related code. – J0e3gan May 07 '13 at 04:37
  • Nor am I particularly familiar with this aspect of ASP.NET MVC: I am digging a bit when I can this evening to learn more and hopefully provide some help or at least ask questions that may help someone else help you. In digging around a bit further, it sounds like you almost need the opposite of solution #2 in the first related post above (i.e. "right after [session expiration], do[n't do] another AJAX request [to] replace your existing anti-forgery token with the response of the request"). – J0e3gan May 07 '13 at 04:43
  • 2
    Why does your logoff action require an anti-forgery token at all? Seems like it shouldn't. – Craig Stuntz May 07 '13 at 12:39
  • 2
    The behaviour you describe (and the "gymnastics" needed to work through it) is IMO correct. If your logoff page performs an action for the previously-logged-in user, then you better be certain you're performing the action for the right user... – C.B. Aug 18 '13 at 23:30
  • 1
    I assume your logoff does a post operation? If it was a get, this problem wouldn't happen, but it's recommended to do a post for logging off. – Brian Mains Nov 05 '14 at 21:15

2 Answers2

30

Actually you can handle it with IExceptionFilter, that will redirect to the /Account/Login

public class HandleAntiForgeryError : ActionFilterAttribute, IExceptionFilter
{
    #region IExceptionFilter Members

    public void OnException(ExceptionContext filterContext)
    {
        var exception = filterContext.Exception as HttpAntiForgeryException;
        if (exception != null)
        {
            var routeValues = new RouteValueDictionary();
            routeValues["controller"] = "Account";
            routeValues["action"] = "Login";
            filterContext.Result = new RedirectToRouteResult(routeValues);
            filterContext.ExceptionHandled = true;
        }
    }

    #endregion
}

[HandleAntiForgeryError]
[ValidateAntiForgeryToken]
public ActionResult LogOff() 
{
}

Also you can use [HandleError(ExceptionType=typeof(HttpAntiForgeryException)...] but it requires customErrors On.

cem
  • 1,911
  • 12
  • 16
  • I can't believe this hasn't been marked as the answer. This answer is incredible. – Alex Dresko Apr 22 '14 at 14:34
  • 1
    For what it's worth, this does not work for me. When I logoff, the application barfs on the same error. However, the exception type is InvalidOperationException instead of HttpAntiforgeryException. This is very generic, making it dangerous to handle this simply :/ – Sinaesthetic Apr 24 '14 at 00:12
  • I tried almost everything suggested here: http://stackoverflow.com/questions/14970102/anti-forgery-token-is-meant-for-user-but-the-current-user-is-username but THIS post finally did the trick – ckonig Feb 17 '15 at 20:54
  • @Sinaesthetic are you sure about that? I tested this and the exception type without any cast shows clearly a System.Web.Mvc.HttpAntiforgeryException – Javier Sep 12 '15 at 18:52
  • I am facing same problem, but at time of login. How to handle at login time ? – gaurav bhavsar Dec 14 '15 at 11:44
6

The answer of @cem was really helpful for me and I added a small change to include the scenario of ajax calls with antiforgerytoken and expired session.

public void OnException(ExceptionContext filterContext)
{
    var exception = filterContext.Exception as HttpAntiForgeryException;
    if (exception == null) return;

    if (filterContext.HttpContext.Request.IsAjaxRequest())
    {
        filterContext.HttpContext.Response.StatusCode = 403;
        filterContext.ExceptionHandled = true;
    }
    else
    {
        var routeValues = new RouteValueDictionary
        {
            ["controller"] = "Account",
            ["action"] = "Login"
        };
        filterContext.Result = new RedirectToRouteResult(routeValues);
        filterContext.ExceptionHandled = true;
    }
}

... and on the client side you can add a global ajax error handler to redirect to the login screen...

$.ajaxSetup({
    error: function (x) {
        if (x.status === 403) {
            window.location = "/Account/Login";
        }
    }
});
Javier
  • 2,093
  • 35
  • 50