Users authentication with WebAPI, WebMatrix.WebData.WebSecurity and System.Web.Helpers.AntiForgery

Topics: ASP.NET Single Page Application, ASP.NET Web API, ASP.NET Web Pages
Mar 8, 2013 at 9:23 AM

In a single page app scenario I have decided to use a webapi method to authenticate my users. I'm using WebSecurity.Login() to get the job done and the anti forgery api to generate the initial token in the page. Then any subsequent ajax/rest requests will send the token that has gotten from the previous successful response. It's just a POC for now and I hope it will work out.

What I've noticed and apparently it's being confirmed by the documentation ( that WebSecurity.Login() call doesn't update the principal of the current http context.
Apparently -- although I'm not 100% sure -- FormsAuthentication.SetAuthCookie() doesn't do it as well.

That wouldn't be a big issue if the anti forgery token api wouldn't be using the user id from current http context to generate the next AFT that will be sent back in the response header. So I had to do it manually.

Does it make sense for you guys to add support for this use-case? Or am I doing something wrong?

Mar 8, 2013 at 2:35 PM
Instead of using cookies we are investigating providing an OAuth2 based endpoint that issues JWT tokens that can be sent to your Web APIs as a bearer token in a standard Authorization header. The nice thing about this approach is that it is not susceptible to request forgery.
Jun 5, 2013 at 3:23 PM
Edited Jun 5, 2013 at 3:36 PM
I think I ran into a similar use-case described (using Forms Authentication) when I was playing with the HotTowel SPA template. Basically, to make it "work"

0.) At this point, in time, you have the "not logged in" anti-forgery token on your page/header/cookie/variable....
1.) Make an ajax login call to a Web API method. Inside the method, try to verify submitted credentials. If successful call
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
and return a "success" status
2.) When the ajax call comes back (Promise), make another ajax call, to get the "logged in" anti-forgery token,
        /// <summary>
        /// clumsy way of getting a new token after logon
        /// because I cannot find a method to update the form token
        /// </summary>
        /// <returns></returns>
        public LoginResult LoggedInToken()
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            // set the new anti-forgery cookie from here
            HttpContext.Current.Response.Cookies[AntiForgeryConfig.CookieName].Value = cookieToken;
            return new LoginResult() { Result=true, Form=formToken};
When the #2 ajax call comes back, you now have at your hands, the "updated" anti-forgery tokens...

So, why not, at step #1, add the functionality/capability to modify the anti-forgery token right away, when a successful verification of credentials is achieved?
I tried this too, so that I don't have to make ajax call #2, just to get the updated tokens. Trials of this method worked for me, but I didn't like it, because it uses reflection. Check out this Stack Overflow thread :

The code that is used (it worked before, maybe it won't work for you, when bits are updated) is something like this:
            if (Membership.ValidateUser(model.UserName, model.Password))

                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                // update form token,so that we have an updated token right after a successful validation
                // otherwise, another round-trip is needed
                // heck, you could actually generate completely new tokens too
                // but the form token still needs to be updated
                const string serializerAssembly = "System.Web.Helpers.AntiXsrf.AntiForgeryTokenSerializer";
                const string cryptoAssembly = "System.Web.Helpers.AntiXsrf.MachineKey40CryptoSystem";
                const string token = "System.Web.Helpers.AntiXsrf.AntiForgeryToken";
                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 });

                object antiForgeryToken = serializerType.InvokeMember("Deserialize", BindingFlags.InvokeMethod, null,
                                                              serializer, new object[] { formToken });

                afdType.GetProperty("Username").SetValue(antiForgeryToken, model.UserName, null); // update it

                formToken = Convert.ToString(serializerType.InvokeMember(
                            new[] { antiForgeryToken }));
                return new LoginResult() { Result = true, Form = formToken };
I also submitted a ticket:
Jun 5, 2013 at 4:13 PM
I think a finally found a solution to this "issue". Instead of doing the "reflection hack", updating tokens can be done by doing:
            if (Membership.ValidateUser(model.UserName, model.Password))

                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(model.UserName), null); // this is the KEY!!! to updating the user part of the token
                string cookieToken;
                AntiForgery.GetTokens(null, out cookieToken, out formToken);
                // set the new anti-forgery cookie from here
                HttpContext.Current.Response.Cookies[AntiForgeryConfig.CookieName].Value = cookieToken;

                return new LoginResult() { Result = true, Form = formToken };