Intermittent ASP.NET oAuth issue with Google, AuthenticationManager.GetExternalIdentityAsync is returning null
Asked Answered
G

7

23

I am trying to fix an intermittent issue when using Google as an external login provider.

When attempting to login, the user is redirected back to the login page rather than being authenticated.

The problem occurs on this line (line 55 of link below), GetExternalIdentityAsync returns null.

var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

The full code is:

[Authorize]
public abstract class GoogleAccountController<TUser> : Controller where TUser : Microsoft.AspNet.Identity.IUser
{
    public IAuthenticationManager AuthenticationManager
    {
        get
        {
            return HttpContext.GetOwinContext().Authentication;
        }
    }

    public abstract UserManager<TUser> UserManager { get; set; }

    [AllowAnonymous]
    [HttpGet]
    [Route("login")]
    public ActionResult Login(string returnUrl)
    {
        ViewData.Model = new LoginModel()
        {
            Message = TempData["message"] as string,
            Providers = HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes(),
            ReturnUrl = returnUrl
        };

        return View();
    }

    [AllowAnonymous]
    [HttpPost]
    [ValidateAntiForgeryToken]
    [Route("login")]
    public ActionResult Login(string provider, string returnUrl)
    {
        return new ChallengeResult(provider, Url.Action("Callback", "Account", new { ReturnUrl = returnUrl }));
    }

    [AllowAnonymous]
    [Route("authenticate")]
    public async Task<ActionResult> Callback(string returnUrl)
    {
        var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

        if (externalIdentity == null)
        {
            return RedirectToAction("Login", new { ReturnUrl = returnUrl });
        }

        var emailAddress = externalIdentity.FindFirstValue(ClaimTypes.Email);
        var user = await UserManager.FindByNameAsync(emailAddress);

        if (user != null)
        {
            await SignInAsync(user, false);

            return RedirectToLocal(returnUrl);
        }
        else
        {
            TempData.Add("message", string.Format("The account {0} is not approved.", emailAddress));

            return RedirectToAction("Login", new { ReturnUrl = returnUrl });
        }
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    [Route("logout")]
    public ActionResult Logout(string returnUrl)
    {
        AuthenticationManager.SignOut();

        return RedirectToLocal(returnUrl);
    }

    private async Task SignInAsync(TUser user, bool isPersistent)
    {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

        var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
        var authenticationProperties = new AuthenticationProperties()
        {
            IsPersistent = isPersistent
        };

        AuthenticationManager.SignIn(authenticationProperties, identity);
    }

    private ActionResult RedirectToLocal(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("Index", "Home");
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && UserManager != null)
        {
            UserManager.Dispose();
            UserManager = null;
        }

        base.Dispose(disposing);
    }
}

Which is also here.

This is very much an intermittent problem, and redeploying the app will often get it to work temporarily.

Looking in Fiddler I can see a call is made to sign-google just previous to the authenticate method in which it can't find the cookie.

Fiddler screenshot

The app uses the following code to initialize the google login

app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/login")
    });
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseGoogleAuthentication();

I have set the authentication mode to non in the web.config, and removed the forms authentication module.

<system.web>
    <authentication mode="None" />
</system.web>    
<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />    
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="FormsAuthenticationModule" />
    </modules>
</system.webServer>

The sites are hosted on Azure, some running on 1 instance, some 2. They have custom domains, although still fail on both custom domain and azurewebsites domain, and http / https.

Can anyone help with why this might be happening?

Update

Version 3.0 of Microsoft.Owin.Security.Google was released last night. Going to switch over and see if this fixes the issue.

https://www.nuget.org/packages/Microsoft.Owin.Security.Google

Getup answered 12/5, 2014 at 16:16 Comment(1)
I just had a similar intermittent oAuth problem that was being caused by load balancers on the API network I was connecting to. May give you something else to look into (beyond your code).Ehman
M
3

After getting the same issue for no reason, I managed to fix it by comparing 2 projects. One test project that worked no problem every time and another project I compared. I discovered that they had the exact same code, but different dll versions.

The referenced packages from Nuget were the failure point.

Make sure you have the latest packages and also check the runtime section in your web.config.


After I updated all Owin related packages and Microsoft.Owin and added:

<assemblyBinding>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
</assemblyBinding>

... it worked again ! They might vary based on your used packages, but that's how it worked for me.

Maid answered 10/7, 2015 at 11:15 Comment(0)
V
2

I forgot to enable "Google + API" in the google developer console. Google login appears to be fine, but GetExternalLoginInfoAsync returns null.

You can follow this link https://mcmap.net/q/586581/-asp-net-identity-owin-middleware-google-oauth2-authenticationmanager-signin-not-working

Vittle answered 16/2, 2016 at 11:16 Comment(0)
V
2

There is a bug in Microsoft's Owin implementation for System.Web. The one that is being used when running Owin applications on IIS. Which is what probably 99% of us do, if we're using the new Owin-based authentication handling with ASP.NET MVC5.

The bug makes cookies set by Owin mysteriously disappear on some occasions.

Put this nuget before https://github.com/KentorIT/owin-cookie-saver before app.UseGoogleAuthentication(...)

Venosity answered 31/12, 2016 at 20:55 Comment(0)
C
1

Tom I am using google-oauth in my asp.net application by using REST API. it is working fine and i am not facing any connection issues.

The following steps i am performing:

1.I have created one project in google developer console in that i have created settings "Client ID for web application" which will contains the following parameters.

a)Client ID => It will be automatically generated by google b)Email address=> It will be automatically generated by google c)Client secret=> It will be automatically generated by google d)Redirect URIs => Need to specify url of web page which will be used to handle authentication process. In this page we can authenticate and we can get user's basic information.

my url: "http://localhost:1822/WebForm1.aspx/code"

My Usage:

  1. I have created one sample project which will contains "Webpage1.aspx" and "Webpage2.aspx".

I have set "Webpage2.aspx" startup page and I am forming open auth url in the "Webpage2.aspx" and redirecting to google login page.

Google Open Auth url Formation

After login, it will redirect to "Webpage1.aspx" along with access code. By passing this access code to "https://accounts.google.com/o/oauth2/token" url, i am getting access token along with token type and token expiry time. After that by passing this access to the "https://www.googleapis.com/oauth2/v2/userinfo" url, i am getting user basic information like "email,Name, Gender, Photo, etc...)

Example Code

    public class GoogleAuthorizationData
    {
        public string access_token { get; set; }
        public int expires_in { get; set; }
        public string token_type { get; set; }

    }

  public class GoogleUserInfo
    {
        public string name { get; set; }
        public string family_name { get; set; }
        public string gender { get; set; }
        public string email { get; set; }
        public string given_name { get; set; }
        public string picture { get; set; }
        public string link { get; set; }
        public string id { get; set; }

    }

  Webpage1.aspx
  ============
 protected void Page_Load(object sender, EventArgs e)
        {
            string code = Request.QueryString["code"].ToString();
            string scope = Request.QueryString["scope"].ToString();
            string url = "https://accounts.google.com/o/oauth2/token";
            string postString = "code=" + code + "&client_id=" + ConfigurationManager.AppSettings["GoogleClientID"].ToString() + "&client_secret=" + ConfigurationManager.AppSettings["GoogleSecretKey"].ToString() + "&redirect_uri=" + ConfigurationManager.AppSettings["ResponseUrl"].ToString() + "&grant_type=authorization_code";

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url.ToString());
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            UTF8Encoding utfenc = new UTF8Encoding();
            byte[] bytes = utfenc.GetBytes(postString);
            Stream os = null;
            try
            {
                request.ContentLength = bytes.Length;
                os = request.GetRequestStream();
                os.Write(bytes, 0, bytes.Length);
            }
            catch
            { }

            try
            {
                HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader responseStreamReader = new StreamReader(responseStream);
                var result = responseStreamReader.ReadToEnd();//
                var json = new JavaScriptSerializer();

                GoogleAuthorizationData authData = json.Deserialize<GoogleAuthorizationData>(result);

                HttpWebRequest request1 = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/oauth2/v2/userinfo");
                request1.Method = "GET";
                request1.ContentLength = 0;
                request1.Headers.Add("Authorization", string.Format("{0} {1}", authData.token_type, authData.access_token));
                HttpWebResponse webResponse1 = (HttpWebResponse)request1.GetResponse();
                Stream responseStream1 = webResponse1.GetResponseStream();
                StreamReader responseStreamReader1 = new StreamReader(responseStream1);
                GoogleUserInfo userinfo = json.Deserialize<GoogleUserInfo>(responseStreamReader1.ReadToEnd());
               Response.Write(userinfo.email);

            }
            catch (Exception eX)
            {
                throw eX;
            }





        }
Coquet answered 15/5, 2014 at 17:39 Comment(0)
E
1

I believe you should't be using app.UseGoogleAuthentication(); as that's a call which will try to use OpenID 2.0, which has been deprecated.
What you should be using instead is OAuth 2.0 for Login (OpenID Connect).
So:

  1. register your app in Google Developers Console
  2. enable it to access Google+ API (even though you do not intend to use Google+ directly - it's now used as a mean of authentication)
  3. enable ASP.NET Identity's Google authentication this way
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()  
{  
    ClientId = "YOUR_CLIENT_ID",  
    ClientSecret = "YOUR_CLIENT_SECRET",  
});
Ephor answered 9/9, 2014 at 13:1 Comment(1)
We did try this, as this is now required with more recent versions of Microsoft.Owin.Security.Google, however we still experienced the same issueGetup
F
1

I have the same problem. I am using Visual Studio 2013 and website is on Azure. The social log in that had been working without issue stopping working and LinkLoginCallback was receiving null in loginInfo. I republished the project without code change or rebuilding and then loginInfo received correct data and all works fine. Does not make sense but there you go.

Farmer answered 21/9, 2014 at 23:33 Comment(0)
L
0

Make sure that 3rd party cookies are enabled. 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.

Londoner answered 28/5, 2014 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.