A way of properly handling HttpAntiForgeryException in MVC 4 application
Asked Answered
C

5

39

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.

Cunningham answered 19/10, 2012 at 5:30 Comment(1)
I agree wholeheartedly with your clarification Update in your Question and in your Answer. Login-Logic shouldn't rely on empty-try-catches, redirects to error-pages, reloading the page (to ask the user to log in again - immediately after entering in their credentials), or breaking when logging in as a different user (while another user is still currently logged in).Peeples
C
21

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.

Cunningham answered 10/9, 2013 at 10:49 Comment(1)
this worked great for me. I don't need it to do any fancy. Just not throw the yellow screen.Wenn
E
23

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 */
Echinoid answered 9/4, 2014 at 14:3 Comment(3)
Thanks, this seems to work great and still provide the same security.Unknow
this didn't work with me, it gives exactly the same error but on this line: System.Web.Helpers.AntiForgery.Validate();Jemena
@AmrElgarhy exactly, now you can catch it!Sunrise
C
21

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.

Cunningham answered 10/9, 2013 at 10:49 Comment(1)
this worked great for me. I don't need it to do any fancy. Just not throw the yellow screen.Wenn
D
5

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.

Dogmatism answered 22/10, 2012 at 15:16 Comment(2)
yes, but this all telling about redirectling (so handling this errors breaks current action (execution). I would like to handle this error without breaking action, so in this case handle, replace user name and continue login with proper dataCunningham
I think that when you can catch the error you can redirect it to a view (controller) where you replace the username and continue the login, with the previously entered data. Also see: #1795436Dogmatism
P
2

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://mcmap.net/q/169235/-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.

Peeples answered 6/8, 2021 at 18:19 Comment(0)
T
1

An old question - but I ran into this problem today, and the way I solved it was by redirecting to the logoff action like so:

public ActionResult Login(string returnUrl) 
{
    if (WebSecurity.IsAuthenticated)
        return RedirectToAction("LogOff");

    ...
}
Traveled answered 25/8, 2015 at 7:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.