AuthenticationManager.GetExternalLoginInfoAsync() on google aspnet mvc5 returns null

Marc picture Marc · Oct 26, 2016 · Viewed 8.7k times · Source

I've developed an ASPNET MVC 5 application using default Visual Studio 2015 Template and Google authentication. All works fine in the development environment but on real the call after external authentication AuthenticationManager.GetExternalLoginInfoAsync() sometimes returns null.

Normally it returns null on the central hours of the day (from 08:00 to 20:00) but I haven't found a pattern because sometimes works at that time. I've watched the developer console, but there are not a lot of requests (22 in the last 12 hours) and all are successful.

I've tried some solutions from other StackOverflow threads but they didn't work. Also, I can try them only on the night because is a personal project and then the connection is successful and I can't reproduce the issue.

The code is standard:

  • On Startup

    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
    
        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });            
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
    
        // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
    
        // Enables the application to remember the second login verification factor such as phone or email.
        // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        // This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
    
        var google = new GoogleOAuth2AuthenticationOptions()
        {
            ClientId = "xxxx",
            ClientSecret = "xxxx",
            Provider = new GoogleOAuth2AuthenticationProvider()
        };
        google.Scope.Add("email");
        app.UseGoogleAuthentication(google);
    }
    
  • On ExternalLoginCallback

    //
    // GET: /Account/ExternalLoginCallback
    [AllowAnonymous]
    public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
    {
        Log.Debug("AuthenticationManager.GetExternalLoginInfoAsync()");
        var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
        if (loginInfo == null)
        {
            Log.Error("AuthenticationManager.GetExternalLoginInfoAsync(): null");
            return RedirectToAction("Login");
        }
    ...
    

More info
I've created new Google credentials with another user and when I change the clientId and clientSecret, it works again... I don't know even when...

Yet more info
The problem is not on credentials, I "only" need to restart ASP.NET application to solve the issue, perhaps this new clue helps somebody to help me.

Not replicated
I've post the answer and it isn't in that OWIN's GetExternalLoginInfoAsync Always Returns null post, I've mentioned there the thread where I found the solution: ASP.NET_SessionId + OWIN Cookies do not send to browser

Answer

Marc picture Marc · Nov 25, 2016

Finally (I think that) I've found the solution after a week with no failed login. All thanks to this StackOverflow thread. My solution has been inserting the following line on AccountController.ExternalLogin action:

Session["Workaround"] = 0;

In the thread above (and links provided there) found a better explanation of the bug when mixing sessions and cookies of ASPNET MVC and OWIN component.

Full controller service code:

    //
    // POST: /Account/ExternalLogin
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult ExternalLogin(string provider, string returnUrl)
    {
        // https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser
        Session["Workaround"] = 0;
        // Request a redirect to the external login provider
        return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
    }