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

parliament picture parliament · Feb 20, 2013 · Viewed 85.9k times · Source

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.

Answer

epignosisx picture epignosisx · Mar 25, 2013

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.