OWIN's GetExternalLoginInfoAsync Always Returns null
C#.Netasp.net MvcOwinGoogle OpenidC# 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:
-
Setup a Google OpenId account at https://console.developers.google.com/project
-
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.
- 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.
-
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
Step 2: Search for Google plus and select it
if you return to dashboard for that project you can see the list of enabled API's for that project at the bottom
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.
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());
}