7

Why does the signinmanager getexternallogininfoasync method always returning null?

I am using VS2015 with the default asp.net core (not framework) project for MVC with individual user accounts (this is a requirement). The purpose of using third party login is to allow users to self register. Role based authorization will be handled by asp.net identity using the identity provided from registering through Azure AD.

Correct me if the following interpretation of the signin in manager is incorrect. This method should provide details on the external login and return a claimsprincipal object which contains the claims provided by the user by the identity provider.

I have used the following guide for setting up OpenIdConnectAuthentication in my Startup.cs (class section below)

https://azure.microsoft.com/en-us/documentation/samples/active-directory-dotnet-webapp-openidconnect/

When I launch the external login provider, it directs me to the organization login page and succeeds.

However the variable info which should be populated by the signinmanager method is null

if I put a breakpoint into the callback class the User is populated and the IsAuthenticated variable is true.

I could drive the functionality of allowing the user to register in the app myself, however, this is my first attempt at implementing third party login and I would like to understand what I am doing wrong as this point.

Startup.cs

  public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services.
        services.AddApplicationInsightsTelemetry(Configuration);

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
        // Add Authentication services.
        services.AddAuthentication(sharedOptions => {
            sharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

        });
        //services.AddDistributedMemoryCache();
        //services.AddSession();
        services.AddMvc();

        // Add application services.
        services.AddTransient<IEmailSender, AuthMessageSender>();
        services.AddTransient<ISmsSender, AuthMessageSender>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseApplicationInsightsRequestTelemetry();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseApplicationInsightsExceptionTelemetry();

        app.UseStaticFiles();

        app.UseIdentity();

        // Configure the OWIN pipeline to use cookie auth.
        app.UseCookieAuthentication( new CookieAuthenticationOptions());



        //Add external authentication middleware below.To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {

            SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
            CallbackPath = "/signin-oidc",
            ClientId = Configuration["AzureAD:ClientId"],
            Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAd:Tenant"]),
            ResponseType = OpenIdConnectResponseType.IdToken,
            PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
            Events = new OpenIdConnectEvents
            {
                //OnRemoteFailure = OnAuthenticationFailed,
            }
        });

        //app.UseSession();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

External Login

        public IActionResult ExternalLogin(string provider, string returnUrl = null)
    {

        // Request a redirect to the external login provider.
        var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
        var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
        return Challenge(properties, provider);
    }

enter image description here

Bill
  • 1,241
  • 17
  • 25

6 Answers6

5

There have been multiple reported issues with this method producing a null value in the past. This does not happen with any of the supported authentication methods that are supplied out of the box. This is a problem at least with using OAuth with azure AD and following the supplied method in the post. However, there is a workaround that still allows for the use of the default project type. Simply replace the method that produces the ExternalLoginInfo variable (info) with a roll your own ExternalLoginInfo class constructed using the User principle.

 ExternalLoginInfo info = new ExternalLoginInfo(User,
            "Microsoft",
            User.Claims.Where(x=>x.Type== "http://schemas.microsoft.com/identity/claims/objectidentifier").FirstOrDefault().Value.ToString(),
           "Microsoft" );

ASP.NET MVC 5 (VS2013 final): Facebook login with OWIN fails (loginInfo is null)

MVC 5 Owin Facebook Auth results in Null Reference Exception

http://blogs.msdn.com/b/webdev/archive/2013/10/16/get-more-information-from-social-providers-used-in-the-vs-2013-project-templates.aspx

Community
  • 1
  • 1
Bill
  • 1,241
  • 17
  • 25
3

In my case, I needed to explicitly pass null for cookieScheme when adding it on startup, as discussed in this github issue: https://github.com/AzureAD/microsoft-identity-web/issues/133

services.AddAuthentication(idp.LoginProvider).AddMicrosoftIdentityWebApp(
  o => {
    o.Instance = config.Instance;
    o.Domain = config.Domain;
    o.ClientId = config.ClientId;
    o.TenantId = config.TenantId;
    o.CallbackPath = config.CallbackPath;
    o.SignInScheme = IdentityConstants.ExternalScheme;
    o.SignOutScheme = IdentityConstants.ExternalScheme;
  },
  openIdConnectScheme: idp.LoginProvider,
  cookieScheme: null // YAR
);
JohnnyFun
  • 3,975
  • 2
  • 20
  • 20
2

I had a similar problem using the OpenIdConnect middleware - I finally fixed it by changing ResponseType to OpenIdConnectResponseType.CodeIdToken (it was being set to 'Code').

Here's a link to my Startup.Auth.vb source:

OWIN OpenID provider - GetExternalLoginInfo() returns null

Liam
  • 5,033
  • 2
  • 30
  • 39
1

Try after removing SignInScheme

0

In my case I'm using Github's OAuth to log in. I had to add two things in order for the _signinManager.GetExternalLoginInfoAsync() to not return null and for the [Authorize] attribute to work for users who log in using Github.

I had to add this for Authentication:

services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            // options.DefaultChallengeScheme = "oidc";
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
            options =>
            {
                options.Cookie.SameSite = SameSiteMode.None;
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                options.Cookie.IsEssential = true;
            });

And then inside the section where I'm actually adding the Github oauth, I had to add the following:

options.SignInScheme = "Identity.External";

So the whole section for Github oauth for me looks like this:

services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            // options.DefaultChallengeScheme = "oidc";
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        }).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
            options =>
            {
                options.Cookie.SameSite = SameSiteMode.None;
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                options.Cookie.IsEssential = true;
            });

        services.AddAuthentication().AddOAuth("GitHub", options =>
        {
            var key = _env.EnvironmentName == Production ? "GitHubProd" : "GitHub";
            options.ClientId = Configuration[$"{key}:ClientId"];
            options.ClientSecret = Configuration[$"{key}:ClientSecret"];
            options.CallbackPath = new PathString("/github-oauth");
            options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
            options.TokenEndpoint = "https://github.com/login/oauth/access_token";
            options.UserInformationEndpoint = "https://api.github.com/user";
            options.SaveTokens = true;
            options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
            options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
            options.ClaimActions.MapJsonKey("urn:github:login", "login");
            options.ClaimActions.MapJsonKey("urn:github:url", "html_url");
            options.ClaimActions.MapJsonKey("urn:github:avatar", "avatar_url");
            options.Scope.Add("repo");
            options.Scope.Add("repo_deployment");
            options.Scope.Add("repo:status");
            options.Scope.Add("write:repo_hook");
            options.Scope.Add("read:repo_hook");
            options.Scope.Add("notifications");
            options.Scope.Add("read:repo_hook");
            options.Scope.Add("user");
            
            // NEW LINE HERE
            options.SignInScheme = "Identity.External";

            // save oauth token to cookie
            options.Events = new OAuthEvents
            {
                OnCreatingTicket = async context =>
                {
                    var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
                    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
                    var response = await context.Backchannel.SendAsync(request,
                        HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
                    response.EnsureSuccessStatusCode();
                    var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
                    context.RunClaimActions(json.RootElement);
                }
            };
        });
Steve
  • 11
  • 1
  • 1
  • 3
-6

why aren't you using the built in microsoftaccountauthentication. There is a tutorial on that here.

https://www.benday.com/2016/05/14/walkthrough-asp-net-core-1-0-mvc-identity-with-microsoft-account-authentication/

This is similar to the google and Facebook authorization classes.

Brandi
  • 53
  • 5
  • 1
    This would let anyone with a Microsoft login register and not restrict users to my azure ad tenant. Thank you for this suggestion but I do not believe that this answers the question that I asked or solve the problem. – Bill Oct 25 '16 at 10:36