MVC3 AntiForgeryToken Issue
Asked Answered
O

3

1

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 = "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.

Octavia answered 29/8, 2011 at 18:3 Comment(7)
WAG: in Logon return a RedirectToAction instead of returning a View and add a HttpGet version of About. edit Actually, I think the issue is that you don't have a Get version of About without the Validate attribute.Conducive
I see nothing wrong with your code. Is there anything else to consider? Can you ensure no AJAX is being used? Can you verify the POST values by debugging or using Firebug?Gironde
@Will: Thanks for your comment. providing Get version of About will enable the user to get the page via url after authentication (www.mydomain.com\About) My application have business rules which states not to allow the page via url. (it works if I do that, but I cannot do the same for my application)Octavia
@Scott Rippey: Thanks for your comment. I built a sample application just to demo this problem and confirmed that there is no AJAX being used here. I used the firebug to check the cookie token and hidden form token both are different. In fact it is different for logon view as well!!Octavia
@matmat: The thing is, you are trying to Get the About page, but you're saying "this should be a post from the About page and that form should have the Validation token", which it does not and never will. I'm not familiar with how the token is implemented, but I'm not sure if it works without a Get version of the method. Regardless, your requirement has nothing to do with your question. You should consider asking a question such as "how can I redirect to a view yet block that view from users browsing via a URL?" BTW, that requirement is ... odd. I'm not even sure if it is possible.Conducive
@Will: you are asking me to follow PRG pattern, however for my application I have to force user to follow certain flow for security reason.Octavia
@matmat: You should ask a question detailing the design requirements you have and ask how to implement it (don't make it subjective or ask for opinions). You can point back to here as an example of how you are implementing it.Conducive
G
5

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.

Gironde answered 30/8, 2011 at 5:0 Comment(4)
Thanks for your response. even if I am not using ValidateAntiForgeryToken on my post of Logon method it fails on post of About method. and if I am not using ValidateAntiForgeryToken on my post of About method, It start working fine from Post of PageA. may the above answer from user571646 is right. I just want to confirm as per Adam Tuliper's response by debugging. This way I know what exactly going on.Octavia
Oh ... I wonder if it still fails because the "unauthenticated" AntiForgery cookie still exists, which will no longer match the "authenticated" AntiForgeryToken?Gironde
Disregard my last comment ... I created a new MVC app to do some research, and determined that the "cookie" doesn't depend on the User, but the Token does. I updated my answer to reflect my new findings.Gironde
Thank you so much! you are exactly right! that is what happening with my application.Octavia
O
2

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();
        }
    }


Omasum answered 29/8, 2011 at 18:37 Comment(6)
Thanks very much for your response. as per your blog and other thread, I am trying to debug ValidateAntiForgeryToken attribute. I can see symbols for System.Web.Mvc.dll is loaded in my module window. however I unable to keep break point on method ValidateAction(filterContext.HttpContext, Salt) in ValidateAntiForgeryTokenAttribute.cs file. any idea? (interestingly I can set break point just above the line where it check for filterContext == null before calling ValidateAction method) Appreciate your input.Octavia
Here is the code block I mentioned in above comment: public void OnAuthorization(AuthorizationContext filterContext) {if (filterContext == null) {throw new argumentNullException("filterContext");} ValidateAction(filterContext.HttpContext, Salt);}Octavia
Is 'just my code' unchecked and is the right version of the symbols loaded for the source you have?Omasum
thanks for your response. version is 3.0.20105.0 and here is the path value C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Web.Mvc\v4.0_3.0.0.0__31bf3856ad364e35\System.Web.Mvc.dll by the way when i try to set break point it say The CLR was unable to set the breakpoint.Octavia
when I looked the symbol load information, looks like first two attempts fails. it looks ok in third attempt. C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Web.Mvc\v4.0_3.0.0.0__31bf3856ad364e35\System.Web.Mvc.pdb: Cannot find or open the PDB file. c:\dd\Dev10\OffCycle\AspNet\MVC\Main\src\SystemWebMvc\obj\Release\System.Web.Mvc.pdb: Cannot find or open the PDB file. D:\Symbols\MicrosoftPublicSymbols\System.Web.Mvc.pdb\D2ADA39C790E4CF5B600D0E87CF0A4B11\System.Web.Mvc.pdb: Symbols loaded.Octavia
I got the problem solved. your input was key to determine the problem. Thanks very much!Octavia
B
2

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.

Bort answered 29/8, 2011 at 18:47 Comment(2)
Thanks for your response. I will confirm this after debugging the validate method of this action filter and let you know the answer.Octavia
Just comment FormsAuthentication.SetAuthCookie(userName, false) line and see.Bort

© 2022 - 2024 — McMap. All rights reserved.