OWIN OpenID provider - GetExternalLoginInfo() retu

2019-01-15 02:59发布

问题:

I'm having problem with OWIN OpenId providers in an ASP.NET MVC5 application which uses ASP.NET Identity and is based on the VS2013 template with Individual user account authentication. OWIN OpenID providers for Google and LinkedIn are used for login authentication.

The problem is that what seems to be very randomly; GetExternalLoginInfo() returns null at the LoginConfirmation callback even though the login authentication was successful.

var authManager = HttpContext.Current.GetOwinContext().Authentication;
var login = authManager.GetExternalLoginInfo();

The providers in use are Google (Microsoft.Owin.Security.Google 2.1.0) and LinkedIn (from Owin.Security.Providers 1.3) and both providers causes the same problem.

Sometimes it fails once and then works again, but sometimes it just continues to fail until the AppPool is recycled.

Currently two instances of the application is hosted in IIS on the same Windows Azure virtual machine. Each instance has its own AppPool but identical setups (different subdomains). Sometimes the login stops working on one instance but still works on the other instance.

The problem has been reproduced locally as well (IIS Express - VS2013).

Anyone experienced similar problems with OWIN OpenID authentication?

Startup.Auth.cs looks like this:

public void ConfigureAuth(IAppBuilder app)
{
    // Enable the application to use a cookie to store information for the signed in user
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
    });
    // Use a cookie to temporarily store information about a user logging in with a third       party login provider
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    app.UseGoogleAuthentication();

    app.UseLinkedInAuthentication("clientId", "clientSecret");
}

The following OWIN nuget packages are in use:

  <package id="Microsoft.AspNet.Identity.Core" version="1.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Identity.Owin" version="1.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.ActiveDirectory" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Cookies" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Facebook" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Google" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Jwt" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.MicrosoftAccount" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.OAuth" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Twitter" version="2.1.0" targetFramework="net45" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
  <package id="Owin" version="1.0" targetFramework="net45" />
  <package id="Owin.Security.Providers" version="1.3" targetFramework="net45" />
  <package id="System.IdentityModel.Tokens.Jwt" version="3.0.2" targetFramework="net45" />

回答1:

The problem occurs when ASP.NET_SessionId cookie is missing.

Setting a dummy value in session before redirecting to the OpenID provider for credentials seems to solve the problem:

[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
    Session["dummy"] = "dummy"; // Create ASP.NET_SessionId cookie

    return View();
}

More details in this answer: https://stackoverflow.com/a/21234614/205023



回答2:

For me, putting ControllerContext.HttpContext.Session.RemoveAll(); in AccountController and ManageController, solved the Problem:

    public ActionResult ExternalLogin(string provider, string returnUrl)
    {
        ControllerContext.HttpContext.Session.RemoveAll();

        // Request a redirect to the external login provider
        return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
    }

and

    public ActionResult LinkLogin(string provider)
    {
        ControllerContext.HttpContext.Session.RemoveAll();

        // Request a redirect to the external login provider to link a login for the current user
        return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId());
    }


回答3:

The accepted answer did not solve the problem for me; what did work was to enable the "Google+ API" in the API Manager from the Google Developers Console.



回答4:

I'm using SecuritySwitch to setup the secure and non-secure pages and the reason for my problem was that the /signin-google path was redirected to a non secure request, instead of using the secure request.



回答5:

The right approach here its just to update all the owin components in the solution.



回答6:

I had this issue with all OpenIdConnect implementations - the key to getting it working was making sure that the authentication options included a ResponseType of OpenIdConnectResponseType.CodeIdToken. Here's an example of my Startup.Auth:

Public Sub ConfigureAuth(app As IAppBuilder)
    app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
    app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
    app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)
   app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
    app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
        .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        .Provider = New CookieAuthenticationProvider() With {
            .OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(
                validateInterval:=TimeSpan.FromMinutes(30),
                regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},
        .LoginPath = New PathString("/Account/Login")})
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)

    Dim facebookOptions = New FacebookAuthenticationOptions With {
        .AppId = ConfigurationManager.AppSettings("FacebookClientID"),
        .AppSecret = ConfigurationManager.AppSettings("FacebookClientSecret"),
        .Provider = New FacebookAuthenticationProvider With {
            .OnAuthenticated = Function(context)
                                   context.Identity.AddClaim(New Claim("Provider", "Facebook"))
                                   context.Identity.AddClaim(New Claim("provider:name", context.Identity.FindFirstValue(ClaimTypes.Name)))
                                   context.Identity.AddClaim(New Claim("provider:accesstoken", context.AccessToken, ClaimValueTypes.String, "Facebook"))
                                   context.Identity.AddClaim(New Claim("provider:picture", String.Format("//graph.facebook.com/{0}/picture?type=square", context.User.Value(Of String)("id"))))
                                   Dim email = context.Identity.FindFirstValue(ClaimTypes.Email)
                                   If email IsNot Nothing Then
                                       context.Identity.AddClaim(New Claim("provider:email", email))
                                   Else
                                       Dim fb = New Facebook.FacebookClient(context.AccessToken)
                                       Dim myInfo = fb.Get("/me?fields=email")
                                       email = myInfo("email")
                                       If email IsNot Nothing Then
                                           context.Identity.AddClaim(New Claim("provider:email", email))
                                       Else
                                           Throw New ArgumentNullException("myInfo.Email")
                                       End If
                                   End If

                                   Return Task.FromResult(0)
                               End Function}}
    facebookOptions.Scope.Add("email")
    app.UseFacebookAuthentication(facebookOptions)

    app.UseGoogleAuthentication(New GoogleOAuth2AuthenticationOptions() With {
       .ClientId = ConfigurationManager.AppSettings("GoogleClientID"),
       .ClientSecret = ConfigurationManager.AppSettings("GoogleClientSecret"),
       .Provider = New GoogleOAuth2AuthenticationProvider With {.OnAuthenticated = Function(context)
                                                                                       context.Identity.AddClaim(New Claim("Provider", "Google"))
                                                                                       context.Identity.AddClaim(New Claim("provider:name", context.Identity.FindFirstValue(ClaimTypes.Name)))
                                                                                       context.Identity.AddClaim(New Claim("provider:email", context.Identity.FindFirstValue(ClaimTypes.Email)))
                                                                                       context.Identity.AddClaim(New Claim("provider:accesstoken", context.AccessToken, ClaimValueTypes.String, "Google"))
                                                                                       context.Identity.AddClaim(New Claim("provider:picture", context.User.SelectToken("image")?.Value(Of String)("url")))

                                                                                       Return Task.FromResult(0)
                                                                                   End Function}})

    app.UseLinkedInAuthentication(New LinkedInAuthenticationOptions With {
        .ClientId = ConfigurationManager.AppSettings("LinkedInClientID"),
        .ClientSecret = ConfigurationManager.AppSettings("LinkedInClientSecret"),
        .Provider = New LinkedInAuthenticationProvider With {.OnAuthenticated = Function(context)
                                                                                    context.Identity.AddClaim(New Claim("Provider", "LinkedIn"))
                                                                                    context.Identity.AddClaim(New Claim("provider:name", context.Name))
                                                                                    context.Identity.AddClaim(New Claim("provider:email", context.Email))
                                                                                    context.Identity.AddClaim(New Claim("provider:accesstoken", context.AccessToken, ClaimValueTypes.String, "LinkedIn"))
                                                                                    context.Identity.AddClaim(New Claim("provider:picture", context.User.SelectToken("pictureUrl").ToString))

                                                                                    Return Task.FromResult(0)
                                                                                End Function}})

    Dim oktaOptions = New OpenIdConnect.OpenIdConnectAuthenticationOptions With {
        .AuthenticationType = "okta",
        .SignInAsAuthenticationType = "Cookies",
        .Authority = ConfigurationManager.AppSettings("okta:Authority"),
        .ResponseType = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectResponseType.CodeIdToken,
        .ClientId = ConfigurationManager.AppSettings("okta:ClientId"),
        .ClientSecret = ConfigurationManager.AppSettings("okta:ClientSecret"),
        .RedirectUri = ConfigurationManager.AppSettings("okta:RedirectUri"),
        .TokenValidationParameters = New Microsoft.IdentityModel.Tokens.TokenValidationParameters With {.ValidateIssuer = True},
        .Scope = "openid profile email"
    }
    app.UseOpenIdConnectAuthentication(oktaOptions)


    Dim auth0Options = New OpenIdConnect.OpenIdConnectAuthenticationOptions With {
        .AuthenticationType = "auth0",
        .SignInAsAuthenticationType = "Cookies",
        .Authority = ConfigurationManager.AppSettings("auth0:Authority"),
        .ResponseType = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectResponseType.CodeIdToken,
        .ClientId = ConfigurationManager.AppSettings("auth0:ClientId"),
        .ClientSecret = ConfigurationManager.AppSettings("auth0:ClientSecret"),
        .RedirectUri = ConfigurationManager.AppSettings("auth0:RedirectUri"),
        .TokenValidationParameters = New Microsoft.IdentityModel.Tokens.TokenValidationParameters With {.ValidateIssuer = True},
        .Scope = "openid profile email"
    }
    app.UseOpenIdConnectAuthentication(auth0Options)

    app.MapSignalR

    GlobalHost.DependencyResolver.Register(GetType(IUserIdProvider), Function() New MySignalRIdProvider())
End Sub