3

When a user goes to my site they get the login page. Once they successfully login they can logoff and a different user can login. However, if the user clicks the back button while logged in it goes to the login page. At this point a new user can no longer login. I receive an anti-forgery token error.

I have tried to logoff any user that goes to the login page. I have tried different ways to logoff. I even tried to Session.Abandon();

Account controller:

// GET: /Account/Login
    [AllowAnonymous]
    public ActionResult Login(string returnUrl)
    {
        EnsureLoggedOut();            
        ViewBag.ReturnUrl = returnUrl;
        // Store the originating URL so we can attach it to a form field
        var viewModel = new LoginViewModel { ReturnUrl = returnUrl };

        return View(viewModel);
    }

    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        ApplicationUser user = new ApplicationUser();
        try
        {
            user = DBcontext.Users.Where(u => u.Email.Equals(model.Email)).Single(); // where db is ApplicationDbContext instance

        }
        catch (InvalidOperationException)
        {
            // the user is not exist
            return View("The user does not exist.");
        }

        var result = await SignInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, shouldLockout: false);

        SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
        switch (result)
        {
            case SignInStatus.Success:
                return RedirectToLocal(returnUrl);
            case SignInStatus.LockedOut:
                return View("Lockout");
            case SignInStatus.RequiresVerification:
                return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
            case SignInStatus.Failure:
            default:
                ModelState.AddModelError("", "Invalid login attempt.");
                return View(model);
        }
    }

    // POST: /Account/LogOff
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff()
    { Session.Abandon();
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
        return RedirectToAction("Index", "Home");
    }

    private ActionResult RedirectToLocal(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl))
        {
            return Redirect(returnUrl);
        }
        return RedirectToAction("Index", "Home");
    }                

}

Login View:

@model LoginViewModel
@{ViewBag.PageId = "extr-page";
ViewBag.PageClass = "animated fadeInDown";}

@section topright{<span id="extr-page-header-space"> <span class="hidden-mobile">Need an account?</span> <a href="@Url.Action("register", "account")" class="btn btn-danger">Create account</a> </span>
}

<div id="content" class="container">
<div class="row">
    @{ Html.RenderPartial("_LoginText"); }
    <div class="col-xs-12 col-sm-12 col-md-5 col-lg-4">
        <div class="well no-padding">
            <form action="@Url.Action("Login", "Account")" method="POST" id="login-form" class="smart-form client-form">
                <header>
                    Sign In
                </header>
                @Html.HiddenFor(m => m.ReturnUrl)
                @Html.AntiForgeryToken()
                @Html.ValidationBootstrap()
                <fieldset>
                    <section>
                        <label class="label">E-mail</label>
                        <label class="input">
                            <i class="icon-append fa fa-user"></i>
                            <input type="Email" name="Email" value="demo@email.com">
                            <b class="tooltip tooltip-top-right"><i class="fa fa-user txt-color-teal"></i> Please enter email address/username</b>
                        </label>
                    </section>

                    <section>
                        <label class="label">Password</label>
                        <label class="input">
                            <i class="icon-append fa fa-lock"></i>
                            <input type="Password" name="Password" value="demo">
                            <b class="tooltip tooltip-top-right"><i class="fa fa-lock txt-color-teal"></i> Enter your password</b>
                        </label>
                        <div class="note">
                            <a href="@Url.Action("forgotpassword", "Account")"><i class="fa fa-frown-o"></i> Forgot password?</a>
                        </div>
                    </section>

                    <section>
                        <label class="checkbox">
                            <input type="checkbox" name="RememberMe" value="true" checked="checked">
                            <input type="hidden" name="RememberMe" value="false" />
                            <i></i>Stay signed in
                        </label>
                    </section>
                </fieldset>
                <footer>
                    <button type="submit" class="btn btn-primary">
                        Sign in
                    </button>
                </footer>
            </form>
        </div>
        @{ Html.RenderPartial("_SocialMedia"); }
    </div>
</div>

I was hoping for when a user hits the back button and he/she goes to the login page the previous user gets logged off.

enter image description here

Update 1: To be clear I am not worried about a user that just logged off and hits the back button. On the contrary, my site breaks when a user logs in successfully and then hits the back button. It takes them back to the Login page but no username or password works because of the aforementioned Anti-Forgery error.

Update 2: I tested the code in IE and it had no problems. Upon further research it looks like Chrome is saving the authentication cookie when I hit the back button. However, when I properly logoff the cookie is destroyed. I thought when the login page loads I was calling the LogOff method but its not deleting the cookie. I will continue to research this problem. Maybe someone has experience with this?

Update 3: I did notice that the cookie was not deleted when I hit the back btn. When I properly logoff the cookie gets deleted. When I not cache the page utilizing Shoe's method below, the cookie does get deleted upon hitting the back btn. However, I still get the anti-forgery token error. What's interesting is that I have a part of the header that pops up on the login page. That header should only come up when a user is authenticated. There should also be an aside menu to pop up on authentication. But it doesn't. I'm wondering if I have an async problem that's causing both problems.

jamesSampica
  • 12,230
  • 3
  • 63
  • 85
Igorski88
  • 985
  • 1
  • 9
  • 19
  • 2
    Possible duplicate of [How to prevent browser back button after logout?](https://stackoverflow.com/questions/21930487/how-to-prevent-browser-back-button-after-logout) – Heretic Monkey May 06 '19 at 23:38
  • 1
    @Heretic no that's the opposite of what I'm looking for. If I hit the back button from Home/Index it will go to the login page but no one can login due to the anti-cross forgery error. – Igorski88 May 07 '19 at 00:47
  • @Heretic The link you posted was for Webforms. I have read everything and it does not pertain to my situation. – Igorski88 May 07 '19 at 03:31

2 Answers2

1

This is probably happening because the page is getting cached and the anti-forgery token that was generated for an anonymous user can't be validated against the logged in user.

Try sticking the OutputCache (ResponseCache in core) attribute on your Login GET and this will set the right headers to not cache the page.

[OutputCache(NoStore = true, Duration = 0, Location = OutputCacheLocation.None)]
public ActionResult Login(string returnUrl)
{
    ...
}
jamesSampica
  • 12,230
  • 3
  • 63
  • 85
  • I wish I read this b4 the hour of research. I did notice that the cookie was not deleted when I hit the back btn. When I properly logoff the cookie gets deleted. When I not cache the page the cookie does get deleted upon hitting the back btn. However, I still get the anti-forgery token error. What's interesting is that I have a part of the header that pops up on the login page. That header should only come up when a user is authenticated. There should also be an aside menu to pop up on authentication. But it doesn't. I'm wondering if I have an async problem that's causing both problems. – Igorski88 May 07 '19 at 19:43
  • 1
    Check `User.IsAuthenticated` on your login view. If it's `true` then your anti-forgery token is still going to throw that error. try `if(User.IsAuthenticated){ EnsureLoggedOut(); RedirectToAction("Login"); }` that way a new request is generated – jamesSampica May 08 '19 at 05:13
  • Yes that is good practice. Thank you for all your help. I have posted the solution below. – Igorski88 May 08 '19 at 23:52
0

I solved this by doing a combination of two things.

Problem 1: I noticed that When I hit the back btn and the login view was displayed, the previous users cookie was not destroyed. This only occured in chrome but not in IE. This was solved with [OutputCache(NoStore = true, Duration = 0, Location = OutputCacheLocation.None)] attribute on my Login Get (Thanks @Shoe). See code below.

Login:

// GET: /Account/Login
    [AllowAnonymous]
    [OutputCache(NoStore = true, Duration = 0, Location = OutputCacheLocation.None)]
    public ActionResult Login(string returnUrl)
    {
        EnsureLoggedOut();

        // Store the originating URL so we can attach it to a form field
        var viewModel = new LoginViewModel { ReturnUrl = returnUrl };

        return View(viewModel);
    } 

Problem 2: The second problem was that once the login view was displayed, I called a method to signout the user with AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie, DefaultAuthenticationTypes.ExternalCookie); and Session.Abandon();. This was not unauthenticating the user until I hit the refresh button for a reason I don't understand. Not until I add a second step to clear the principal to ensure the user does not retain any authentication by adding HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null); to my EnsureLoggedOut method. See code below.

EnsureLoggedOut Method:

private void EnsureLoggedOut()
    {
        if (AuthenticationManager.User.Identity.IsAuthenticated)
        {
            //SignOut the current user
            AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie, DefaultAuthenticationTypes.ExternalCookie);
            Session.Abandon();

            // Second we clear the principal to ensure the user does not retain any authentication
            HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
        }
    }
Igorski88
  • 985
  • 1
  • 9
  • 19