开发者

MVC3 AntiForgeryToken Issue

I am trying to implement AntiForgeryToken for my MVC3 Application. I am having a problem with AntiForgeryToken after setting FormAuthentication cookie. Here is a simple example which explains my problem.

I have home controller with following action methods:

public class HomeController : Controller
{
    public ActionResult Logon()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Logon(string userName, string password)
    {
        FormsAuthentication.SetAuthCookie(userName, false);
        return View("About");
    }


    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult About(FormCollection form)
    {
        return View("PageA");
    }
 }

And here is my Logon and About views:

Logon.cshtml:

   @using (Html.BeginForm("Logon", "Home"))
   {

    @Html.AntiForgeryToken()

    <label> UserName :</label>
    <input  name = "userName" type="text"/>
    <br />
    <label> Password :</label>
    <input name 开发者_如何学Python= "password" type="password"/>
    <br />
    <br />
    <input type="submit" value="LogOn" />

   }

About.cshtml

@using (Html.BeginForm("About", "Home"))
{

    @Html.AntiForgeryToken()
    <p> This is conent of page About</p>
    <input  name = "moreInfo" type="text"/>

    <input type="submit" value="SubmitAbout" />
}

I have no problem on "Logon" post method. It is validating the antiforgerytoken and rendering About view. Interestingly, when I do post on "About" view I am getting error "A required anti-forgery token was not supplied or was invalid"

could some one point out what I am doing wrong here?

Appreciate your help.


I did some tests, and determined that even after you call FormsAuthentication.SetAuthCookie(...), the problem is that httpContext.User.Identity.Name will still be empty for the duration of the request.

Therefore, to solve this issue, you need to manually set the current User as so:

FormsAuthentication.SetAuthCookie(email, true);
this.HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null);

This will set the correct User that is used when Html.AntiForgeryToken() is called.

Please note that this code isn't necessary for normal PRG-pattern websites, because after the redirect, the correct User will be loaded.

Also, since your Logon method requires a valid user name and password, it isn't really susceptible to CSRF attacks, so you probably don't need to use ValidateAntiForgeryToken on that method. Maybe that's why the AntiForgeryToken is dependent on the user name. CSRF attacks usually only exploit already-authenticated users.


I seem to recall once you login your token is now different as your username I believe changes this token, hence would no longer be valid. I'll try to double check this but almost certain I ran into this in the past.

However in your code above you will run up against other issues if you use this pattern. Post actions aren't generally meant to display a view unless there has been an exception/validation error and you are redisplaying the page. Generally you would redirect. I see someone touched upon this in a comment above and they are correct.

This doesn't mean you shouldn't use those actions though but beware of crossing this over a login. This prior post alludes to the use of username with tokens:

Troubleshooting anti-forgery token problems


public void Validate(HttpContextBase context, string salt) {
        Debug.Assert(context != null);

        string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
        string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);

        HttpCookie cookie = context.Request.Cookies[cookieName];
        if (cookie == null || String.IsNullOrEmpty(cookie.Value)) {
            // error: cookie token is missing
            throw CreateValidationException();
        }
        AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

        string formValue = context.Request.Form[fieldName];
        if (String.IsNullOrEmpty(formValue)) {
            // error: form token is missing
            throw CreateValidationException();
        }
        AntiForgeryData formToken = Serializer.Deserialize(formValue);

        if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) {
            // error: form token does not match cookie token
            throw CreateValidationException();
        }

        string currentUsername = AntiForgeryData.GetUsername(context.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();
        }

        if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) {
            // error: custom validation failed
            throw CreateValidationException();
        }
    }



The AntiForgeryToken Helper does not add any cookie to response, if a cookie with same name exist in the request. Also the AntiForgeryToken Helper uses Principal.Identity.Name to return a value for hidden field.

            AntiForgeryData formToken = new AntiForgeryData(cookieToken) {
               Salt = salt,
               Username = AntiForgeryData.GetUsername(httpContext.User)
            };

So when your Login view uses Html.AntiForgeryToken, a new cookie is set on response and a hidden field with same value. When your Login view post this cookie with hidden field, no exception will be thrown because both request cookie and hidden field value matches. But in the case of About view, no additional cookie will be added to response, but due to IIdentty, a new hidden value will be return for helper. So when you post About action, an exception will raise because cookie and hidden value does not match.

This may be a bug in AntiForgeryToken implementation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜