137

I'm building a single page application and experiencing an issue with anti-forgery tokens.

I know why the issue happens I just don't know how to fix it.

I get the error when the following happens:

  1. Non-logged-in user loads a dialog (with a generated anti-forgery token)
  2. User closes dialog
  3. User logs in
  4. User opens the same dialog
  5. User submits form in dialog

Anti forgery token is meant for user "" but the current user is "username"

The reason this happens is because my application is 100% single-page, and when a user successfully logs in through an ajax post to /Account/JsonLogin, I simply switch out the current views with the "authenticated views" returned from the server but do not reload the page.

I know this is the reason because if I simple reload the page between steps 3 and 4, there is no error.

So it seems that @Html.AntiForgeryToken() in the loaded form still returns a token for the old user until the page is reloaded.

How can I change @Html.AntiForgeryToken() to return a token for the new, authenticated user?

I inject a new GenericalPrincipal with a custom IIdentity on every Application_AuthenticateRequest so by the time @Html.AntiForgeryToken() gets called HttpContext.Current.User.Identity is, in fact my custom Identity with IsAuthenticated property set to true and yet @Html.AntiForgeryToken still seems to render a token for the old user unless I do a page reload.

Ahmed
  • 511
  • 3
  • 6
  • 26
parliament
  • 21,544
  • 38
  • 148
  • 238
  • Can you actually verify that the @Html.AntiForgeryToken code is being called without reloading? – Kyle C Feb 20 '13 at 01:04
  • It definitely is, I can successful break there to inspect HttpContext.Current.User object like I mentioned – parliament Feb 20 '13 at 05:59
  • 2
    Please refer to this: http://stackoverflow.com/a/19471680/193634 – Rosdi Kasim Jan 23 '14 at 05:27
  • @parliament could you please tell which option did you go for in the answer below. – KKS Feb 18 '14 at 13:49
  • I believe I made an exception to go with a full reload if i remember correctly. But I expect to encounter this issue very soon in a new project. Will post back if I opt with a better working option. – parliament Feb 28 '14 at 13:18
  • Does this answer your question? [A way of properly handling HttpAntiForgeryException in MVC 4 application](https://stackoverflow.com/questions/12967917/a-way-of-properly-handling-httpantiforgeryexception-in-mvc-4-application) – MikeTeeVee Aug 06 '21 at 16:23

10 Answers10

173

This is happening because the anti-forgery token embeds the username of the user as part of the encrypted token for better validation. When you first call the @Html.AntiForgeryToken() the user is not logged in so the token will have an empty string for the username, after the user logs in, if you do not replace the anti-forgery token it will not pass validation because the initial token was for anonymous user and now we have an authenticated user with a known username.

You have a few options to solve this problem:

  1. Just this time let your SPA do a full POST and when the page reloads it will have an anti-forgery token with the updated username embedded.

  2. Have a partial view with just @Html.AntiForgeryToken() and right after logging in, do another AJAX request and replace your existing anti-forgery token with the response of the request.

Note that setting AntiForgeryConfig.SuppressIdentityHeuristicChecks = true does not disable username validation, it simply changes how that validation works. See the ASP.NET MVC docs, the source code where that property is read, and the source code where the username in the token is validated regardless of the value of that config.

Johann
  • 4,107
  • 3
  • 40
  • 39
epignosisx
  • 6,152
  • 2
  • 30
  • 31
  • 21
    @parliament: you accepted this answer, could you share with us which option you chose? – R. Schreurs May 17 '13 at 08:56
  • 9
    +1 for the nice & simple option 3. Timed logouts by OAuth providers also cause this problem. – iCollect.it Ltd Oct 09 '13 at 13:04
  • 3
    yea +1 for option 3. its really difficult to figure out where this happens on my site. Swithcing users or logging out causes this error but when I refresh its gone. Ehh. Thanks. This fixed it. – Piotr Kula Jan 30 '14 at 14:32
  • 18
    Option 3 didn't work for me. Whilst logged out, I opened two windows onto the login page. Logged in as one user in one window, then logged in as another user in the other and received the same error. – McGaz Jan 31 '14 at 10:19
  • 2
    @McGaz -- good catch. Did you find a solution for this? Experiencing the same problem. – awright May 08 '14 at 11:58
  • 6
    Unfortunately, I couldn't get a good solution to this. I removed the token from the login page. I still include it on posts after login. – McGaz May 09 '14 at 14:20
  • 8
    Option 3 didn't work for me neither. Still getting same error. – Joao Leme Jul 22 '14 at 16:00
  • 2
    Option 3 did not work for me either. But option 2 worked perfectly well. I'm calling a $.get() on document load and reloading the token each time. The error only occurs for me on the login page. A user will log in, click back, and then login again. That was what was causing it for me. – Paul Sep 10 '14 at 14:53
  • 4
    #3 should be removed from this answer. It doesnt disable the identiy check as implied. See http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages `If this value is true, the system will assume that IIdentity.Name is appropriate for use as a unique per-user identifier and will not try to special-case IClaimsIdentity` – wal Sep 29 '14 at 04:07
  • 1
    For option #3 Ms is saying that "Use caution when setting this value. Using it improperly can open security vulnerabilities in the application." http://msdn.microsoft.com/en-us/library/system.web.helpers.antiforgeryconfig.suppressidentityheuristicchecks(v=vs.111).aspx So should we really use this? – Anshul Nigam Nov 04 '14 at 04:57
  • 1
    Number 2 was the one :) Cheers ! – Kaizer Oct 09 '15 at 10:29
  • Option 2 worked for me. My code has to GET a new token, put that in __RequestVerificationToken, then POST. – joe Jul 01 '17 at 04:18
  • can anyone provide a sample code for option #2? I've tried option #3 and it doesn't work. – Sam Nov 11 '17 at 10:23
  • Same, had option 3 in my solution, still getting error – ransems Dec 31 '17 at 19:19
  • 1
    `AntiForgeryConfig` auto-complete does not give me an option to select `SuppressIdentityHeuristicChecks`. I'm using Asp .NET Web API – DevEng Feb 21 '18 at 20:23
  • I am having same issue with Windows Authentication mode. `System.Web.Mvc.HttpAntiForgeryException: 'The provided anti-forgery token was meant for user "Domain\User Name", but the current user is "User Name".'` – Rajamohan Rajendran Oct 03 '18 at 05:53
  • `AntiForgeryConfig.SuppressIdentityHeuristicChecks = true` not working for me – Beingnin Jun 17 '19 at 08:25
  • 1
    @DevEng It is because that property is decorated with `[EditorBrowsable(EditorBrowsableState.Never)]` attribute. hence you cannot get that in autocomplete – Beingnin Jun 17 '19 at 08:35
26

To fix the error you need to place the OutputCache Data Annotation on the Get ActionResult of Login page as:

[OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")] 
public ActionResult Login(string returnUrl)
AmanVirdi
  • 1,667
  • 2
  • 22
  • 32
user3401354
  • 369
  • 3
  • 5
  • My use case was the user tried a login, and got shown an error e.g. "account disabled" via ModelState.AddError(). Then if they clicked login again they would see this error. However, this fix just gave them a blank fresh login view again rather than the anti forgery token error. So, not a fix. – yourpublicdisplayname Aug 29 '16 at 01:22
  • My Case : 1. User LogIn() and lands in the home page. 2. User hits back button and goes back to the Login view. 3. User login again and see the error "Anti forgery token is meant for user “” but the current user is “username”" On the error page If the user clicks on any other tabs from the menu, the application was working as expected. Using above code user can still hit back button but it gets redirected to the home page. So no matter how many time the user hits the back button it will redirect it to the home page. Thank you – Jas Jul 18 '18 at 07:09
  • Any ideas why this doesn't work on a Xamarin webview? – Noobie3001 May 13 '19 at 15:27
  • 1
    For a full explanation see [improving performance with output caching](https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/improving-performance-with-output-caching-cs) – stomy Jun 12 '19 at 18:39
17

The message appears when you login when you are already authenticated.

This Helper does exactly the same thing as [ValidateAntiForgeryToken] attribute.

System.Web.Helpers.AntiForgery.Validate()

Remove the [ValidateAntiForgeryToken] attribut from controller and place this helper in action methode.

So when user is already authentificated, redirect to the home page or if not continue with the verification of the valid anti-forgery token after this verification.

if (User.Identity.IsAuthenticated)
{
    return RedirectToAction("Index", "Home");
}

System.Web.Helpers.AntiForgery.Validate();

To try to reproduce the error, proceed as follows: If you are on your login page and you are not authenticated. If you duplicate the tab and you login with the second tab. And if you come back to the first tab on the login page and you try to log in without reloading the page ... you have this error.

A. Morel
  • 9,210
  • 4
  • 56
  • 45
  • 1
    Excellent solution! This solved my issue after trying a lot of other suggestions that did not work. First off it was a pain reproducing the error, until I discovered that it could be because of 2 browsers or tabs open with the same page, and the user logging in from one, and then logging in from the second without reloading. – Nicki Oct 25 '19 at 09:12
  • Thanks for this solution. Worked for me too. I added a check to see if the Identity was the same as the login username, and if so I happily continue trying to log the user in, and log them out if it's not. Eg, try { System.Web.Helpers.AntiForgery.Validate();} catch (HttpAntiForgeryException) { if (!User.Identity.IsAuthenticated || string.Compare(User.Identity.Name, model.Username) != 0) { // Your log off logic here } } – Steve Owen Jul 10 '20 at 15:32
  • Hope you don't mind, I used this answer and combined it with some other logic I found (to handle logging in as someone different) for answering an older question here: https://stackoverflow.com/a/68685999/555798 . Thanks for your help! – MikeTeeVee Aug 06 '21 at 18:26
15

It happens a lot of times with my application, so I decided to google for it!

I found a simple explanation about this error! The user are double-clicking the button for login! You can see another user talking about that on the link below:

MVC 4 provided anti-forgery token was meant for user "" but the current user is "user"

I hope it helps! =)

viggity
  • 15,039
  • 7
  • 88
  • 96
Ricardo França
  • 2,923
  • 2
  • 18
  • 18
9

I had the same problem, and this dirty hack got it fixed, at least until I can fix it in a cleaner way.

    public ActionResult Login(string returnUrl)
    {
        if (AuthenticationManager.User.Identity.IsAuthenticated)
        {
            AuthenticationManager.SignOut();
            return RedirectToAction("Login");
        }

...

mnemonics
  • 91
  • 1
  • 2
2

I have the same exception occurring most of the time on the production server.

Why does it happen?

It happens when user login with valid credentials and once logged in and redirect to another page, and after they press the back button will show login page and again he entered valid credentials that time this exception will occur.

How to solve?

Just add this line and work perfect, no get an error.

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Brijesh Mavani
  • 1,070
  • 1
  • 15
  • 23
1

I had a fairly specific yet similar problem within the registration process. Once the user clicked on the email link sent to them, they'd be logged in and sent straight to an account details screen to fill in some more information. My code was:

    Dim result = Await UserManager.ConfirmEmailAsync(userId, code)
    If result.Succeeded Then
        Dim appUser = Await UserManager.FindByIdAsync(userId)
        If appUser IsNot Nothing Then
            Dim signInStatus = Await SignInManager.PasswordSignInAsync(appUser.Email, password, True, shouldLockout:=False)
            If signInStatus = SignInStatus.Success Then
                Dim identity = Await UserManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ApplicationCookie)
                AuthenticationManager.SignIn(New AuthenticationProperties With {.IsPersistent = True}, identity)
                Return View("AccountDetails")
            End If
        End If
    End If

I found that the Return View("AccountDetails") was giving me the token exception, I'm guessing because the ConfirmEmail function was decorated with AllowAnonymous but the AccountDetails function had ValidateAntiForgeryToken.

Changing the Return to Return RedirectToAction("AccountDetails") solved the problem for me.

Liam
  • 5,033
  • 2
  • 30
  • 39
1
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]

public ActionResult Login(string returnUrl)

You can test this by putting a break point on the first line of your Login (Get) action. Before adding the OutputCache directive the breakpoint would be hit on the first load, but after clicking the browser back button it wouldn’t. After adding the directive you should end up with the breakpoint being hit every time, so the AntiForgeryToken will be the corect one, not the empty one.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
0

I had the same issue with a single-page ASP.NET MVC Core application. I resolved it by setting HttpContext.User in all controller actions which change the current identity claims (since MVC only does this for subsequent requests, as discussed here). I used a result filter instead of middleware to append the antiforgery cookies to my responses, which made sure that they were only generated after the MVC action had returned.

Controller (NB. I'm managing users with ASP.NET Core Identity):

[Authorize]
[ValidateAntiForgeryToken]
public class AccountController : Controller
{
    private SignInManager<IdentityUser> signInManager;
    private UserManager<IdentityUser> userManager;
    private IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory;

    public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
    {
        this.signInManager = signInManager;
        this.userManager = userManager;
        this.userClaimsPrincipalFactory = userClaimsPrincipalFactory;
    }

    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Login(string username, string password)
    {
        if (username == null || password == null)
        {
            return BadRequest(); // Alias of 400 response
        }

        var result = await signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            var user = await userManager.FindByNameAsync(username);

            // Must manually set the HttpContext user claims to those of the logged
            // in user. Otherwise MVC will still include a XSRF token for the "null"
            // user and token validation will fail. (MVC appends the correct token for
            // all subsequent reponses but this isn't good enough for a single page
            // app.)
            var principal = await userClaimsPrincipalFactory.CreateAsync(user);
            HttpContext.User = principal;

            return Json(new { username = user.UserName });
        }
        else
        {
            return Unauthorized();
        }
    }

    [HttpPost]
    public async Task<IActionResult> Logout()
    {
        await signInManager.SignOutAsync();

        // Removing identity claims manually from the HttpContext (same reason
        // as why we add them manually in the "login" action).
        HttpContext.User = null;

        return Json(new { result = "success" });
    }
}

Result filter to append antiforgery cookies:

public class XSRFCookieFilter : IResultFilter
{
    IAntiforgery antiforgery;

    public XSRFCookieFilter(IAntiforgery antiforgery)
    {
        this.antiforgery = antiforgery;
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var HttpContext = context.HttpContext;
        AntiforgeryTokenSet tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
        HttpContext.Response.Cookies.Append(
            "MyXSRFFieldTokenCookieName",
            tokenSet.RequestToken,
            new CookieOptions() {
                // Cookie needs to be accessible to Javascript so we
                // can append it to request headers in the browser
                HttpOnly = false
            } 
        );
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {

    }
}

Startup.cs extract:

public partial class Startup
{
    public Startup(IHostingEnvironment env)
    {
        //...
    }

    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {

        //...

        services.AddAntiforgery(options =>
        {
            options.HeaderName = "MyXSRFFieldTokenHeaderName";
        });


        services.AddMvc(options =>
        {
            options.Filters.Add(typeof(XSRFCookieFilter));
        });

        services.AddScoped<XSRFCookieFilter>();

        //...
    }

    public void Configure(
        IApplicationBuilder app,
        IHostingEnvironment env,
        ILoggerFactory loggerFactory)
    {
        //...
    }
}
Ned Howley
  • 828
  • 11
  • 19
-3

Has a problem with anti-forgery-token validation in internet-shop: users open many tabs (with goods) and after logged in on one try to log in on another and got such AntiForgeryException. So, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true didn't help for me, so I used such ugly hackfix, maybe it will be helpful for someone:

   public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext exceptionContext)
    {
        var exception = exceptionContext.Exception;

        var request = HttpContext.Current.Request;
        if (request != null)
        {
            if (exception is HttpAntiForgeryException &&
                exception.Message.ToLower().StartsWith("the provided anti-forgery token was meant for user \"\", but the current user is"))
            {
                var isAjaxCall = string.Equals("XMLHttpRequest", request.Headers["x-requested-with"], StringComparison.OrdinalIgnoreCase);
                var returnUrl = !string.IsNullOrWhiteSpace(request["returnUrl"]) ? request["returnUrl"] : "/";
                var response = HttpContext.Current.Response;

                if (isAjaxCall)
                {
                    response.Clear();
                    response.StatusCode = 200;
                    response.ContentType = "application/json; charset=utf-8";
                    response.Write(JsonConvert.SerializeObject(new { success = 1, returnUrl = returnUrl }));
                    response.End();
                }
                else
                {
                    response.StatusCode = 200;
                    response.Redirect(returnUrl);
                }
            }
        }


        ExceptionHandler.HandleException(exception);
    }
}

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ExceptionPublisherExceptionFilter());
        filters.Add(new HandleErrorAttribute());
    }
}

Think it will be great if anti-forgery-token generation options can be set, to exclude username or something like that.

  • 12
    This is a terrible example of dealing with the problem in the question. Don't use this. – xxbbcc Mar 04 '14 at 16:36
  • OK, use case: login form with anti forgery token. Open it in 2 browser tabs. Log in in first. You _cant_ refresh second tab. What solution do you suggest to have correct behaviour for user who try to log in from second tab? – user3364244 Mar 06 '14 at 13:33
  • @user3364244: correct behaviour can the following: detect an external login by using websockets or signalR. This is the same session so you could make it work i guess :-) – dampee Sep 30 '14 at 22:45