Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 429
Tom Avatar asked May 12 '14 16:05

Tom


4 Answers

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.

like image 160
Prisecaru Alin Avatar answered Oct 15 '22 07:10

Prisecaru Alin


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://stackoverflow.com/a/27631109/657926

like image 24
Adamy Avatar answered Oct 15 '22 07:10

Adamy


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(...)

like image 2
user7358693 Avatar answered Oct 15 '22 06:10

user7358693


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;
            }





        }
like image 1
Rama Subba Reddy M Avatar answered Oct 15 '22 06:10

Rama Subba Reddy M