GetExternalLoginInfoAsync null unless already logged in.

May 19, 2014 at 3:39 PM
Edited May 19, 2014 at 3:42 PM
I've been trying to implement external logins using a google account. If I'm already logged into google everything's fine for registering, however if I'm not, I get prompted to login with Google as expected but the call back receiver doesn't seem to see that I've logged in as logininfo is always is always null in this scenario in the callback as below...
    [AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
    {
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();

         if (loginInfo == null)
        {
            return RedirectToAction("Login");
        }

        // Code omitted for brevity.
        }
    }
Does anyone have a workaround or explanation? It's almost like the external cookie isn't made available to OWIN until the request after logging into google.
May 19, 2014 at 6:35 PM
@hoots: The AuthenticationManager.GetExternalLoginInfoAsync is an extension method in Microsoft.AspNet.Identity.Owin assembly. You can debug it by loading symbols in VS. Follow the instructions at the link http://www.symbolsource.org/Public/Home/VisualStudio and debug into the code to see why the logininfo is null. Let me know and I can follow up on that
May 21, 2014 at 3:48 PM
Thanks, I have done this and I think I have discovered the problem which I believe is a bug. The program flow seems to work as follows...

User Clicks the Google button to Register/Login. Concentrating on the important points of code...

In Microsoft.Owin.Security.Google
  • GoogleAuthenticationHandler.ApplyResponseChallendeAsync to get the OpenId information.
  • GoogleAuthenticationHandler.InvokeReturnPathAsync is then called to get the AuthenticationTicket (via a call to AuthenticateAsync)
  • GoogleAuthenticationHandler.AuthenticateCoreAsync unpacks the Request.Query into a properties variable which contains a key value pair with the key .AspNet.Correlation.Google. These properties are then passed to ValidateCorrelationId
In Microsoft.Owin.Security.Infrastructure.AuthenticationHandler
  • AuthenticationHandler.ValidateCorrelationId looks at Request.Cookies[".AspNet.Correlation.Google"] to check for the cookies existence and continues if it's there returning true.
This last point seems to be the problem as Request.Cookie[".AspNet.Correlation.Google"] is there if already logged into google but not if logging in as part of the request. Maybe the bug is that it should be there but I believe that changing...
string correlationCookie = Request.Cookies[correlationKey];
to
string correlationCookie = properties.Dictionary[correlationKey];
should sort it out after a preliminary check for the existence of correlationKey.

Do you agree, if so how would I request this change? Can you see a workaround until this is fixed?
Coordinator
May 21, 2014 at 4:29 PM
One side note: Google has deprecated their OpenId login flow, you should be using the OAuth flow now (and even that has been updated in Katana v3 due to Google changes).

The correlation cookie is to make sure that login requests were initiated by this server. It should go like this:
  • Challenge, set a correlation cookie, redirect to google
  • Sign-in to google and redirect back to us
  • Validate the google user ID and generate a ClaimsIdentity. Validate the correlation key and delete it. The correlation cookie must always be present for this to succeed.
  • Redirect back to the page that issued the challenge
The real question is why your correlation cookie is missing?
May 21, 2014 at 4:32 PM
Update...

I've also noticed that When I login to google the first time in a fresh browser I get the problem (IE10). Just before logging in I have a single __RequestVerificationToken in Request.Cookies after logging into Google with a break point in InvokeReturnPathAsync the Request.Cookies has nothing in.

whereas after logging out of google, then going through the same again without closing the browser, there are 2 cookies in Request.Cookies... __RequestVerificationToken, .AspNet.ExternalCookie before google login this time, and then a 3rd .AspNet.Correlation.Google afterwards, so the persistence works properly second time around. It maybe just a persistence issue somewhere else in code.
Coordinator
May 21, 2014 at 4:34 PM
I did just fix a bug in v3 where the correlation cookies weren't being properly deleted after the login flow, so that may affect your observations here.
May 22, 2014 at 3:51 PM
v3 is still in pre release isn't it? I need a production release, any idea when v3 will be ready for it's first release? From what you're saying, is there no chance of having this bug fixed in a 2.1.1?
Coordinator
May 22, 2014 at 4:33 PM
First you should try the v3 packages to see if they work for you.

We are hoping to have v3 out in the next month or two, so there are not currently plans to release any minor or patch versions.
May 23, 2014 at 11:06 AM
Edited May 23, 2014 at 11:28 AM
I have discovered the root cause. The problem seems to be that after logging into google, it redirects back to the site and doesn't have permission to signin-google so is redirected back to the login page without doing what it needs to with cookies. Not sure why this works if already signed into google though, I would guess it has something to do with an extra cookie already being available and so even though signin-google fails the cookie's there anyway!?

I discovered this after finding the article...

http://blog.technovert.com/2014/01/google-openid-integration-issues-asp-net-identity/

I added the following to my config file.
<location path="signin-google">
 <system.web>
   <authorization>
     <allow users="*" />
   </authorization>
 </system.web>
</location>
It now works...
May 28, 2014 at 11:38 AM
Edited May 28, 2014 at 11:49 AM
@Tratcher ... Actually, I think it's got to do with 3rd party cookies being enabled as I also did this.

I have found out that if you're not logged into google when you try to 'Register' a user with your application, it redirects to the login page as it looks for this cookie that isn't there, but still manages to do what it needs with the external provider. The next time you try to 'Register', because it's done part of the process it doesn't need to look for the external cookie anymore and so succeeds second time around fully.
May 28, 2014 at 11:51 AM
Tratcher wrote:
I did just fix a bug in v3 where the correlation cookies weren't being properly deleted after the login flow, so that may affect your observations here.
If 3rd party cookies are disabled, wouldn't this be irrelevant, so wouldn't checking properties.Dictionary[correlationKey]; be better than checking Request.Cookies[correlationKey]?
Jul 29, 2014 at 11:56 AM
Edited Jul 29, 2014 at 11:57 AM
Tratcher wrote:
First you should try the v3 packages to see if they work for you.

We are hoping to have v3 out in the next month or two, so there are not currently plans to release any minor or patch versions.
Hello. I have the same issue. I have upgraded package to v3 rc but it still doesn't work.

P.S. I noticed that when Fiddler is runned loginInfo initialized by returning data and it works perfect. In other ways it always if null.

Any idea?
Jul 29, 2014 at 1:11 PM
It's a while ago now but I seem to remember that I needed to make sure third-party cookies as well as first party cookies are always enabled in advanced privacy settings of Internet explorer. Just make sure you're allowing everything and try and I think it works. I think this then allows the google cookie first off and it won't then be null. I think if it was coded slightly differently this might not be needed.

Let me know if that solves the symptoms.
Jul 29, 2014 at 1:29 PM
Edited Jul 29, 2014 at 1:29 PM
Thank you for answer. I just tried allow third-party cookie but unfortunately it didn't help me.

Tested in IE, Chrome and Firefox
Aug 18, 2014 at 6:39 PM
I have spent hours googling on this issue and nothing helps. I tried every solution mentioned on the web, both locally and on a real host. Google auth just doesn't work, as well as GooglePlus auth from the GitHub project. I have noticed that a call to ExternalLoginCallback contains a query string with an error:
https://localhost:44300/account/externallogincallback?ReturnUrl=%2F&error=access_denied
I have tracked with dotPeek two places inside Microsoft.OWIN.Security.Assembly where such an error is set, but wasn't able to debug any further. I have also tried nightly builds (RC3) from MyGet and they fail as well. Facebook and Twitter work perfectly with the same setup.

I hope that you fix it before v3 release, or you you are sure that it is not MSFT's bug then suggest how to fix it, please!
Coordinator
Aug 18, 2014 at 7:27 PM
Have you read the following?
http://blogs.msdn.com/b/webdev/archive/2014/07/02/changes-to-google-oauth-2-0-and-updates-in-google-middleware-for-3-0-0-rc-release.aspx

Especially the part at the bottom about enabling G+ login for your app.
Aug 18, 2014 at 7:36 PM
OMG, thank you so much! For some reason I wasn't able to find this page. Now it works!
Aug 25, 2014 at 2:48 PM
Thank you so much for this link!!! I've spent hours trying to figure this out.
Aug 27, 2014 at 9:45 PM
Edited Aug 27, 2014 at 9:46 PM
It appears there is something else as I am experiencing the same symptom that is loginInfo == null but none of the reasons above are applicable nor suggestions help. I have went through debugging in VS using public symbols and ended up at the the following AutheticateAsync method in OwinRequestExtensions.cs:
            public async Task AuthenticateAsync(
                string[] authenticationTypes,
                AuthenticateCallback callback,
                object state)
            {
                if (authenticationTypes == null)
                {
                    callback(null, null, _handler.BaseOptions.Description.Properties, state);
                }
/// --->>> check fails here <<<------
                else if (authenticationTypes.Contains(_handler.BaseOptions.AuthenticationType, StringComparer.Ordinal))__
                {
                    AuthenticationTicket ticket = await _handler.AuthenticateAsync();
                    if (ticket != null && ticket.Identity != null)
                    {
                        callback(ticket.Identity, ticket.Properties.Dictionary, _handler.BaseOptions.Description.Properties, state);
                    }
                }
                if (Chained != null)
                {
                    await Chained(authenticationTypes, callback, state);
                }
            }
on the else if check (--->>>) the _handler is GoogleOauth2AuthenticationHandler and its BaseOptions.AuthenticationType is "Google" but the authenticationTypes parameter only contains a single value "ExternalCookie" so that check fails and everything fails. I cannot figure out how why "Google" is not present in the authenticationTypes parameter.

I have configured all authentication types accordingly and it used to work some time before and than suddenly stopped without any changes from my side. Need urgent help with this.
       public void ConfigureAuthentication(IAppBuilder app)
        {
            app.CreatePerOwinContext<IdentityUserManager>(Create);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/account/login"),
                ReturnUrlParameter = "returnUrl",
                ExpireTimeSpan = TimeSpan.FromMinutes(120),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator
                        .OnValidateIdentity<IdentityUserManager, IdentityUser, long>(
                            validateInterval: TimeSpan.FromMinutes(30),
                            regenerateIdentityCallback: (manager, user) => manager.GenerateUserIdentityAsync(user),
                            getUserIdCallback: id => (long.Parse(id.GetUserId())))
                } 
            });

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            app.UseFacebookAuthentication(
               appId: "omitted for security",
               appSecret: "omitted for security";

            app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
            {
                ClientId ="omitted for security",
                ClientSecret ="omitted for security"
            });
        }
Aug 28, 2014 at 6:32 PM
Figured out that my problem is described here https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser and the fix is to kick start a session by doing this dummy thing in a Login method of an Account controller:
Session["dummy"] = "dummy";
http://blog.turlov.com/2014/08/external-social-loginauthentication.html