AntiForgeryToken invalid after sign in

2019-03-18 05:35发布

问题:

I have a form which the user can post without loging in. If however his email is recognized a password is required. The password form is validated over Ajax and if successfull the main form is submitted. Both forms require a valid AntiForgeryToken.

The catch is, the password check as a biproduct also signs the user in (a requirement from the client). This invalidates the token and the main form cannot be sent.

I have tried programmatically generating a new token but I can't get it to work.

Any ideas on how to resolve this?

Final solution

I found this question to be helpful in type up the reflection. However, and this is the main reason why under normal circumstances you would avoid hacking internal types, is that the types are juggled between assemblies alot between releases. As Betty suggests, use ILSpy to find things.

This is the final code.

if (signIn)
    FormsAuth.SignIn(user.Email, false);


var mvcAssembly = typeof(AntiForgery).Assembly;
var afdType = mvcAssembly.GetType("System.Web.Helpers.AntiForgeryData");
string fieldName = Convert.ToString(afdType.InvokeMember(
    "GetAntiForgeryTokenName",
    BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod,
    null,
    null,
    new object[] { null }));

var serializerType = mvcAssembly.GetType("System.Web.Helpers.AntiForgeryDataSerializer");
var serializerCtor = serializerType.GetConstructor(new Type[0]);
object serializer = serializerCtor.Invoke(new object[0]);


string text = HttpContext.Request.Form[fieldName];
object antiForgeryData = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null, serializer, new object[] { text });

afdType.GetProperty("Username").SetValue(antiForgeryData, 
    signIn ? user.Email : string.Empty, 
    null);

string newToken = Convert.ToString(serializerType.InvokeMember(
    "Serialize",
    BindingFlags.InvokeMethod,
    null,
    serializer,
    new object[] { antiForgeryData }));

return Content(JsonConvert.SerializeObject(new
                                                {
                                                    success = true,
                                                    newAntiForgeryToken = newToken
                                                }), Constant.JsonContentType);

Upgrade for WebPages 2.0

  var mvcAssembly = typeof(AntiForgery).Assembly;
        var afdType = mvcAssembly.GetType("System.Web.Helpers.AntiXsrf.AntiForgeryToken");
        //string fieldName = Convert.ToString(afdType.InvokeMember(
        //    "GetAntiForgeryTokenName",
        //    BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod,
        //    null,
        //    null,
        //    new object[] { null }));

        string fieldName = "__RequestVerificationToken";

        var serializerType = mvcAssembly.GetType("System.Web.Helpers.AntiXsrf.AntiForgeryTokenSerializer");
        var serializerCtor = serializerType.GetConstructor(new Type[0]);
        object serializer = serializerCtor.Invoke(new object[0]);


        string text = HttpContext.Request.Form[fieldName];
        string newToken = String.Empty;

        if (!String.IsNullOrEmpty(text))
        {
            object antiForgeryToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null,
                                                                 serializer, new object[] { text });

            afdType.GetProperty("Username").SetValue(antiForgeryToken,
                                                     signIn ? user.Email : string.Empty,
                                                     null);

            newToken = Convert.ToString(serializerType.InvokeMember(
                "Serialize",
                BindingFlags.InvokeMethod,
                null,
                serializer,
                new[] { antiForgeryToken }));
        }

回答1:

The current user is stored in the anti-forgery token in the form data and compared with the current user on postback.

You should be able to pull out the form token on postback in the same way Phil Haack does it in this post.

Then use the AntiForgeryDataSerializer class to deserialize the token, update the current user, serialize it again and put it back in the form before it's checked. Or replace the validate method entirely using your own attribute.

Alternatively, instead of updating it on the main forms postback, you could try send the updated token back with the password ajax request and update the form. Either way the basic approach is the same, deserialize, update user, serialize, replace token.

string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string text = context.Request.Form[antiForgeryTokenName];
AntiForgeryDataSerializer serializer = new AntiForgeryDataSerializer();

AntiForgeryData antiForgeryData = serializer.Deserialize(text); 
antiForgeryData.Username = AntiForgeryData.GetUsername(context.User);
string newToken = serializer.Serialize(antiForgeryData);    

AntiForgeryDataSerializer and AntiForgeryData are internal classes, so you will have to use some basic reflection to call methods on them.



回答2:

Updated final answer to account for changes to AntiForgeryTokenSerializer constructor:

    const string serializerAssembly = "System.Web.Helpers.AntiXsrf.AntiForgeryTokenSerializer";
    const string cryptoAssembly = "System.Web.Helpers.AntiXsrf.MachineKey40CryptoSystem";
    const string token = "System.Web.Helpers.AntiXsrf.AntiForgeryToken";
    const string fieldName = "__RequestVerificationToken";

    Assembly mvcAssembly = typeof (AntiForgery).Assembly;
    Type afdType = mvcAssembly.GetType(token);

    Type serializerType = mvcAssembly.GetType(serializerAssembly);
    Type cryptoType = mvcAssembly.GetType(cryptoAssembly);
    var constructors = serializerType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    ConstructorInfo cryptoConstructor = cryptoType.GetConstructor(new Type[0]);
    var crypto = cryptoConstructor.Invoke(new object[0]);
    object serializer = constructors[0].Invoke(new object[] { crypto });

    string text = currentContext.Request.Form[fieldName];
    string newToken = String.Empty;

    if (!String.IsNullOrEmpty(text))
    {
        object antiForgeryToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null,
                                                              serializer, new object[] {text});

        afdType.GetProperty("Username").SetValue(antiForgeryToken,
                                                 signIn ? user.Email : string.Empty,
                                                 null);

        newToken = Convert.ToString(serializerType.InvokeMember(
            "Serialize",
            BindingFlags.InvokeMethod,
            null,
            serializer,
            new[] {antiForgeryToken}));
    }

    return newToken;