Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A way of properly handling HttpAntiForgeryException in MVC 4 application

Here is the scenario:

I have a login page, when user sign it it is redirected to home application page. Then user is using browser back button, and now he is on login page. He tries to login again but now an exception is thrown:

HttpAntiForgeryException (0x80004005): The provided anti-forgery token was meant for user "", but the current user is "userName".

I know this is related to caching. I disabled browser caching for login action using custom NoCache filter which sets all required headers - no-cache, no-store, must-revalidate, etc. But

  • this is not working on all browsers
  • especially Safari (mobile in most cases) totaly ignores such settings

I will try to make hacks and force safari mobile to refresh, but this is not what I'm expecting.

I would like to know if I can:

  • handle exception without showing user any problem exists (totally transparent for user)
  • prevent this problem by replacing anti forgery token user name which will allow user login again without this exception, if my hacks related to browser caching will stop work in next versions of browsers.
  • I really would like not to rely on browser behavior, since each one behaves differently.

UPDATE 1

To make some clarification, I know how to handle errors in MVC. The problem is that this handling errors is not solving my problem at all. Basic idea of error handling is redirect to custom error page with nice message. But I want to prevent this error to happen, not to handle it in user visible way. By handle I mean catch make username replace or other suitable action then continue login.

UPDATE 2

I've added below solution which is working for me.

like image 628
Marcin Avatar asked Oct 19 '12 05:10

Marcin


4 Answers

After some time of investigation I think I found some way how to get rid of this error for user. It is not perfect but at least not display error page:

I created filter based on HandleErrorAttribute:

    [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", 
        Justification = "This attribute is AllowMultiple = true and users might want to override behavior.")]
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class LoginAntiforgeryHandleErrorAttribute : FilterAttribute, IExceptionFilter
    {
        #region Implemented Interfaces

        #region IExceptionFilter

        /// <summary>
        /// </summary>
        /// <param name="filterContext">
        /// The filter context.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// </exception>
        public virtual void OnException(ExceptionContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (filterContext.IsChildAction)
            {
                return;
            }

            // If custom errors are disabled, we need to let the normal ASP.NET exception handler
            // execute so that the user can see useful debugging information.
            if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            {
                return;
            }

            Exception exception = filterContext.Exception;

            // If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
            // ignore it.
            if (new HttpException(null, exception).GetHttpCode() != 500)
            {
                return;
            }

            // check if antiforgery
            if (!(exception is HttpAntiForgeryException))
            {
                return;
            }

            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary
                {
                    { "action", "Index" }, 
                    { "controller", "Home" }
                });

            filterContext.ExceptionHandled = true;
        }

        #endregion

        #endregion
    }

Then I applied this filter to Login POST action:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[LoginAntiforgeryHandleError]
public ActionResult Login(Login model, string returnUrl)
{

The main idea of this solution is to redirect anti-forgery exception to main index action. If user will still won't be unauthenticated it will show then login page if user will be already authenticated it will show index page.

UPDATE 1 There is one potential problem with this solution. If somebody is logging in with different credentials then on error it should be added additional login runtime - logout previous user and login new one. This scenario is not handled.

like image 103
Marcin Avatar answered Nov 15 '22 12:11

Marcin


If you only have one or a few functions affected, creating a filter might be slightly technical overkill. A simpler but non generic solution is to simply remove the [ValidateAntiForgeryToken] for the specific method and add a manual validation after checking if the user is logged in.

if (User.Identity.IsAuthenticated)
{
    return RedirectToAction("Index", "Home");
}
System.Web.Helpers.AntiForgery.Validate();
/* proceed with authentication here */
like image 31
Crypth Avatar answered Nov 15 '22 11:11

Crypth


You should be able to handle the exception by adding a action filter to handle your error.

[HandleError(View="AntiForgeryExceptionView", ExceptionType = typeof(HttpAntiForgeryException))]

Todo so make sure that custom errors are turned on in your web.config.

<customErrors mode="On"/>

You could also take a look at this blog for more info about handle error.

Edit Since you're using MVC4 and the blog is about MVC3 you could also take a look at the MSDN library - HandleErrorAttribute, but the version shouldn't really make a difference.

like image 5
Jos Vinke Avatar answered Nov 15 '22 11:11

Jos Vinke


The message appears while Logging In, after you have previously Authenticated.
Steps to Reproduce:
1.) Open your Login Page and verify you are not Authenticated.
2.) Duplicate the Tab and Login on the Second Tab.
3.) Return to the First Tab and try Logging In (without reloading the page).
4.) You see this Error; if your Login Action is Decorated with the [ValidateAntiForgeryToken] Attribute:

System.Web.Mvc.HttpAntiForgeryException:
The provided anti-forgery token was meant for user "",
but the current user is "YourUserNameOrEmailAddress".

This Helper performs the same Validation as the [ValidateAntiForgeryToken] Attribute:

System.Web.Helpers.AntiForgery.Validate()

Remove [ValidateAntiForgeryToken] from the Login Action and use this Method instead.

Now, when the User is already Authenticated, it will Redirect to the Home Page.
If already Authenticated, but Logging In as someone else, then Logout the Current-User and continue with the Validation of the Anti-Forgery Token before Authenticating as the New-User.

if (User.Identity.IsAuthenticated)
{
    if (User.Identity.Name == UserName)//User is already Logged in.
        return RedirectToAction("Index", "Home");
    else//Else: User is Logging In as someone else, so Log Out the Current User.
        ResetUser();
}
System.Web.Helpers.AntiForgery.Validate();//Replaces [ValidateAntiForgeryToken].
//Add your Login Logic below here.

Then, add this Function to safely Reset the User without having to Reload the page again:

private void ResetUser()
{
    //Add any additional custom Sign-Out/Log-Off Logic here.
    Session.Abandon();
    FormsAuthentication.SignOut();

    //Source: https://stackoverflow.com/questions/4050925/page-user-identity-isauthenticated-still-true-after-formsauthentication-signout
    //The User.Identity is Read-Only, but it reads from HttpContext.User, which we may Reset.  Otherwise it will still show as Authenticated until the next Page Load.
    HttpContext.User = new System.Security.Principal.GenericPrincipal(new System.Security.Principal.GenericIdentity(string.Empty), null);//Do not set Identity to null, because other parts of the code may assume it's blank.
}

.net Core Thoughts:
I should note that if you are using .net Core and have the [AutoValidateAntiforgeryToken] Attribute on your Controller - or you've added a Global Filter to the whole Site like services.AddMvc(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }); - then you would have the option to Decorate your Login-Action Method with [IgnoreAntiforgeryToken] to avoid the automatic Validation Exception and give you the opportunity to either Redirect or Sign-Out before continuing on and manually calling the Validation Helper Method yourself.
Note: I haven't used .net Core to verify this yet, but adding my findings here in case it helps.

like image 2
MikeTeeVee Avatar answered Nov 15 '22 12:11

MikeTeeVee