OWIN's GetExternalLoginInfoAsync Always Returns null

C#.Netasp.net MvcOwinGoogle Openid

C# Problem Overview


I've created a new MVC5 Web Application, and when I try to login with Google or Facebook, the ExternalLoginCallback Action in the AccountController is called, but GetExternalLoginInfoAsync() always returns null:

var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
    return RedirectToAction("Login");
}

Because it's always null, it just redirects back to the login page and the process starts over. How can I fix this?

C# Solutions


Solution 1 - C#

To get OWIN Google login to work properly on a standard Visual Studio 2013, ASP.Net MVC5 site, I had to:

  1. Setup a Google OpenId account at https://console.developers.google.com/project

  2. Set the callback URL there to blah/signin-google.
    Important notes on things you don't need to do:

  • You don't need to use HTTPS for Google to redirect back; you can even redirect back to plain http://localhost, no problem.

  • You don't need to setup anything for the redirect URL - no routes, Controller Actions or special permissions in Web.Config. The redirect URL is always /signin-google and OWIN handles this behind the scenes for you.

As an example, if your site was me.com, you might have these 3 callback URLs in the Google Developer Console:

http://localhost:53859/signin-google
http://test.me.com/signin-google
https://me.com/signin-google

The first one including whatever port number VS gave you for your project.

  1. Enable the Google+ API. This is one hidden b**** of a gotcha and is the root cause of the problem in the question here - if you don't do this, it's easy to miss that the Request to /account/ExternalLoginCallback includes &error=access_denied, and that's because Google said no to a permissions request OWIN made for the user's Google+ basic profile. I can't tell whose fault this is, Google's or Microsoft's.

To enable the Google+ API in the Developers Console, click APIs on the left, hunt for Google+, click that and hit Enable. Yes you really do need to do that. You're hosed if you don't do that.

  1. Add the ClientId and ClientSecret Google gave you in the Developers Console to Startup.Auth, but improve the code in the process to explicitly use OAuth2, and explicitly ask for the user's email address:

    var google = new GoogleOAuth2AuthenticationOptions()
    {
        ClientId = "123abc.apps.googleusercontent.com",
        ClientSecret = "456xyz",
        Provider = new GoogleOAuth2AuthenticationProvider()
    };
    google.Scope.Add("email");
    app.UseGoogleAuthentication(google);
    

That's it. That finally got it working.

Just want to reiterate one more time, there are a LOT of answers about this and issues like it where OWIN/Google isn't working, and nearly all of them are wrong for the current VS2013/MVC5/OWIN template.
You don't need to modify Web.Config at all.
You don't need to create any special Routes whatsoever.
You should not attempt to point /signin-google to a different place, or use a different callback URL, and you definitely shouldn't attempt to tie it directly to /account/externallogincallback or externalloginconfirmation, because those are both separate from /signin-google and necessary steps in the OWIN/Google process.

Solution 2 - C#

OK, I found out why it's null. You have to enable Google + API in the Google console. Also make sure the secret key is not concatenated with a space at the end after you paste it to your code. Why can't they return a normal error? I don't know.

Solution 3 - C#

It seems that Nuget package Microsoft.Owin.Security.Facebook version 3.0.1 no longer works with Facebook Login.

Update this package to the pre-release 3.1.0 version, you can use the following:

> Install-Package Microsoft.Owin.Security.Facebook -Pre

Solution 4 - C#

As others correctly mentioned, most of the time that's because you do not have permission to the Google+ API so here is how to get permission for a project in Google API Manager to Google+ API

Step 1. Select You Project from the top combobox and go to Dashboard > Enable API enter image description here

Step 2: Search for Google plus and select it enter image description here

Step 3: Enable it! enter image description here

if you return to dashboard for that project you can see the list of enabled API's for that project at the bottom enter image description here

Solution 5 - C#

I got it to work by simply updating all the nugget package in the application and it worked.

Solution 6 - C#

I know it's silly, but after a long struggle, restarting IIS solved the issue for me.

Solution 7 - C#

This solved my problem:

Enable the Google+ API. This is a gotcha and is the root cause of the problem in the question here - if you don't do this, it's easy to miss that the Request to /account/ExternalLoginCallback includes &error=access_denied, and that's because Google said no to a permissions request OWIN made for the user's Google+ basic profile. I can't tell whose fault this is, Google's or Microsoft's.

To enable the Google+ API in the Developers Console, click APIs on the left, hunt for Google+, click that and hit Enable.

Solution 8 - C#

I did the following to get it working.

Logon to the developer portal, locate your application and do the following.

App details > App centered Listed Platforms > Select Yes for website

Solution 9 - C#

I ran in to this issue today and it turned out that I defined the remote cookie after I assigned the providers.

Make sure you place...

app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

before...

app.UseFacebookAuthentication(
                   appId: "",
                   appSecret: "");

Solution 10 - C#

For those who are experiencing this problem for Web Api. Other solutions doesnt help AuthenticationManager.GetExternalLoginInfoAsync(); returns always null even google plus api is enabled.

use this custom function to get logininfo. obviously Microsoft has a bug for GetExternalLoginInfoAsync when requesting over web api.

private async Task<ExternalLoginInfo> AuthenticationManager_GetExternalLoginInfoAsync_WithExternalBearer()
        {
            ExternalLoginInfo loginInfo = null;

            var result = await Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);

            if (result != null && result.Identity != null)
            {
                var idClaim = result.Identity.FindFirst(ClaimTypes.NameIdentifier);
                if (idClaim != null)
                {
                    loginInfo = new ExternalLoginInfo()
                    {
                        DefaultUserName = result.Identity.Name == null ? "" : result.Identity.Name.Replace(" ", ""),
                        Login = new UserLoginInfo(idClaim.Issuer, idClaim.Value)
                    };
                }
            }
            return loginInfo;
        }

Solution 11 - C#

Although the answers above are all good, in my instance none of these worked - I'd checked and double checked the Google settings and agree with Chris Moschini that there's a lot of misleading info.

For me it was a 'doh moment when I realised that my Out of Process state service was not started! No errors (as a login was the first thing I was attempting after a reboot where the state service is set to manual start-up on the machine) just a Null from GetExternalLoginInfoAsync

Hope this helps someone else out.

Solution 12 - C#

After much searching and head scratching as well as following numerous red herring answers here on Stackoverflow I eventually went through all my options on my Google dev console and discovered a little blue [Enable] button on the Google+API overview page. I clicked this and hey presto it worked. Forget all the baloney you read about callback url and route configs, OWIN overrides the google default /signin-google redirect uri in any case and sends you back to ExternalLoginCallback. Just stick with the default implementation all will be good so long as you enable your Google+API.

Solution 13 - C#

I wanted to contribute to this one also. I just recently got this working. I had the problem with the GetExternalLoginInfoAsync returning null but only in production.

After a lot of searching I finally found my answer it was simply a problem with my database. In production I had set the wrong connection string so it would not connect properly but it was basically silent about it. The only thing that happened was GetExternallLoginInfoAsync returned null. So check you database connection string if this happens!

Also on a sidenote, the only thing that was needed to get this working was:

  • Set up a project in the Google console
  • Enable Google+ API
  • Copy your client id and client secret to the Startup.Auth.cs file.

You do not have to enable HTTPS, you do not have to create custom routes. But make sure your database is working properly!

Solution 14 - C#

It is true that you are going to need the Google plus Enabled. The big thing for me was the project URL. Open the properties window (View -> Properties Window) in VS and then right click the project and select properties. In small properties window copy your SSL URL, and then in the larger properties window select the Web tab and paste that URL in the Project URL.

Fixed the issue for me.

See in greater detail: https://docs.microsoft.com/en-us/aspnet/mvc/overview/security/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on

Solution 15 - C#

For me I was migrating and old but working .NET 4.6.1 MVC website to core 1.1. Work stopped before I could get it working, when I picked it back up, I was then migrating to 2.x.

My problem was that the callback from Google was met with a 404 from my site. I thought it was supposed to hit AccountController.ExternalLoginCallback so I added a [Route(...)] to it and sure enough, Google's callback hit the action.

This then hit the null returned in this line (what kind of maniac returns a null?)

var externalLoginInfo = await this.SignInManager.GetExternalLoginInfoAsync();

I reverse engineered it to find under the hood its ultimately getting the handler for ExternalScheme which for my was the cookies handler!

It all seemed wrong and I felt somehow that the middleware was supposed to just intercept the callback URI so I removed my [Route(...)] and the 404 problem came back.

I then found that I need to add this during startup.

applicationBuilder.UseAuthentication();

This solves the 404 but gives another issue.

> No authenticationScheme was specified, and there was no DefaultSignInScheme found.

By adding a default scheme here, I resolve the error above.

serviceCollection.AddAuthentication(IdentityConstants.ExternalScheme)
    .AddGoogle(googleOptions => Configuration.Bind("OAuth2:Providers:Google", googleOptions))
    .AddExternalCookie();

Now AccountController.ExternalLoginCallback is invoked again by some magic but I am back to the null return value.

I added this code above the offending line, which is essentially what is happening under the hood (looking at Microsoft's code on GitHub). Interestingly, h is of type CookieAuthenticationHandler and a has all my claims and information from Google inside!

var authHandler = this.HttpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
var h = await authHandler.GetHandlerAsync(this.HttpContext, IdentityConstants.ExternalScheme);
var a = await h.AuthenticateAsync();

var externalLoginInfo = await this.SignInManager.GetExternalLoginInfoAsync();

Digging into GitHub and copy pasting internal code its running into my controller, I can see that it's failing to find ClaimTypes.NameIdentifier in my claims, this is the ProviderKey used later.

Hmm....

Concerned I was using old 1.x AccountController code with newer 2.x identity bits I did find some samples that still use this stuff, and some samples that use Razor Pages for it all, too. I'll continue with what I have.

So I'm next going to investigate mapping additional Google user JSON payload items into the claims. I think if my Google account ID (numeric, I guess) was mapped then everything would work.

https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/additional-claims?view=aspnetcore-2.2

Final Fix

I finally resolved the issue by adding a "claim action" to pull my Google identifier out of the JSON coming back from Google!

serviceCollection.AddAuthentication(IdentityConstants.ExternalScheme)
    .AddGoogle(googleOptions =>
    {
        Configuration.Bind("OAuth2:Providers:Google", googleOptions);

        googleOptions.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub", "string");
    })
    .AddExternalCookie();

The sub field contains what eventually ends up in the nameidentifier claim and then into the ProviderKey that the AccountController wants.

Solution 16 - C#

In my case the solution was update Nuget package Microsoft.Owin.Security and Microsoft.Owin.Security.Google

Solution 17 - C#

All of the other answers didn't solve this for me, so if your in the same boat then make sure your registration controller action has the RequireHttps attribute:

    // GET: /Account/LoginRegister
    [AllowAnonymous]
    [RequireHttps]
    public ActionResult LoginRegister()
    {
        return View(new RegisterLoginViewModel());
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVineetYadavView Question on Stackoverflow
Solution 1 - C#Chris MoschiniView Answer on Stackoverflow
Solution 2 - C#Ronen FestingerView Answer on Stackoverflow
Solution 3 - C#LukeView Answer on Stackoverflow
Solution 4 - C#Hossein Narimani RadView Answer on Stackoverflow
Solution 5 - C#Salem KosemaniView Answer on Stackoverflow
Solution 6 - C#OmriView Answer on Stackoverflow
Solution 7 - C#Hugh ProctorView Answer on Stackoverflow
Solution 8 - C#tonyView Answer on Stackoverflow
Solution 9 - C#HaggeView Answer on Stackoverflow
Solution 10 - C#EmilView Answer on Stackoverflow
Solution 11 - C#JJonesView Answer on Stackoverflow
Solution 12 - C#Graham WalkerView Answer on Stackoverflow
Solution 13 - C#Johan OView Answer on Stackoverflow
Solution 14 - C#AtLeastTheresToastView Answer on Stackoverflow
Solution 15 - C#Luke PuplettView Answer on Stackoverflow
Solution 16 - C#Ms.FatimaView Answer on Stackoverflow
Solution 17 - C#rmanView Answer on Stackoverflow