views:

39

answers:

2

Hello,

We'd been getting "A required anti-forgery token was not supplied or was invalid." errors, and on some further investigation, I've managed to recreate the problem in its simplest form - i'm either doing something completely wrong, or this is a limitation of the anti-forgery token system.

Either way, I'd appreciate some advice!

Empty MVC 2 project: one view page, one controller

view:

<%--Sign in form:--%>
<% using(Html.BeginForm("SignIn", "Home", FormMethod.Post)) {%>
    <%= Html.AntiForgeryToken()%>
    <input type="submit" value="Sign in" />
<%}%>

Controller:

public ActionResult Index()
{
    ViewData["status"] = "Index";
    return View();
}

[ValidateAntiForgeryToken]
public ActionResult SignIn()
{
    ViewData["status"] = "Signed In!";
    FormsAuthentication.SetAuthCookie("username", false);
    return View("Index");
}

[EDIT: simplified code example]

In order to recreate the exception, open two non-signed-in tabs - sign-in on the first tab, and then sign-in on the second tab.

The second tab will always throw an anti-forgery exception, when I guess correct behaviour would be to redirect to the signed-in page (sharing the session/authentication of the original signed-in tab)

Any advice would be appreciated!

Cheers, Dave

A: 

You could use the same salt:

<% using(Html.BeginForm("SignIn", "Home", FormMethod.Post)) {%>
    <%= Html.AntiForgeryToken("123")%>
    <input type="submit" value="Sign in" />
<%}%>

<% using(Html.BeginForm("Protected", "Home", FormMethod.Post)) {%>
    <%= Html.AntiForgeryToken("456")%>
    <input type="submit" value="Do secret stuff" />
<%}%>

And in your controller:

[ValidateAntiForgeryToken(Salt = "123")]
public ActionResult SignIn()
{
    ViewData["status"] = "Signed In!";
    FormsAuthentication.SetAuthCookie("username", false);
    return View("Index");
}

[Authorize]
[ValidateAntiForgeryToken(Salt = "456")]
public ActionResult Protected()
{
    ViewData["status"] = "Authed";
    return View("Index");
}

Do the same with the other token but make sure to pick a different salt.

Darin Dimitrov
Thanks for the reply - but that doesn't solve the problem, cheers, Dave.
Dave
It solved it for me. Make sure you use the same salt in the controller and the view for the signin form and action and a different salt for the protected action and view. Also don't forget to recompile and close the browser window to clear any stale cookies.
Darin Dimitrov
Make sure you close your browser to start a new session - You actually don't need the 2nd form/controller action at all in order to recreate this issue.
Dave
In your original question you talked about different browser tabs. If I close the browser and start a new there's no problem at all.
Darin Dimitrov
No - I mean, open a fresh browser - then open two tabs.Sign-in on tab 1, then tab 2 will always fail with the exception.
Dave
Yes, that's exactly what I did. Adding the salt solved the problem.
Darin Dimitrov
This def' doesn't work. I'm happy to send you the code, if you want (thanks for your help)! :)
Dave
OK, I see. Try redirecting in the POST action instead of returning a view: `return RedirectToAction("Index")`.
Darin Dimitrov
Nope, no good :(
Dave
+2  A: 

Looking at the MVC 2 source code it looks like the AntiForgeryToken hidden field includes the User.Identity.Name serialized, if your signed in. In line 69 of the ValidateAntiForgeryTokenAttribute it seems to then check your token with the current User.Identity.Name.

    string currentUsername = AntiForgeryData.GetUsername(filterContext.HttpContext.User);
    if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) {
        // error: form token is not valid for this user
        // (don't care about cookie token)
        throw CreateValidationException();
    }

Because in your other tab you are now signed in the code above invalidates the existing token which doesn't contain the User.Identity.Name.

This could be fixed by adding a !string.IsNullOrEmpty(formToken.Username) around that check but I don't know if that will open up security issues plus it means having a custom MVC 2 Build.

Naz