简体   繁体   中英

How to retrieve Google profile picture from logged in user with ASP.Net Core Identity?

Ok... I'm currently using ASP.Net Core 1.1.2 with ASP.NET Core Identity 1.1.2.

The important part in Startup.cs looks like this:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        //...
        app.UseGoogleAuthentication(new GoogleOptions
        {
            AuthenticationScheme = "Google",
            SignInScheme = "Identity.External", // this is the name of the cookie middleware registered by UseIdentity()
            ClientId = Configuration["ExternalLoginProviders:Google:ClientId"],
            ClientSecret = Configuration["ExternalLoginProviders:Google:ClientSecret"]
        });
    }

GoogleOptions comes with Microsoft.AspNetCore.Authentication.Google nuget package.

The callback function in AccountController.cs looks like this:

    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
    {
        //... SignInManager<User> _signInManager; declared before
        ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync();
        SignInResult signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
        string email = info.Principal.FindFirstValue(ClaimTypes.Email);
        string firstName = info.Principal.FindFirstValue(ClaimTypes.GivenName);
        string lastName = info.Principal.FindFirstValue(ClaimTypes.Surname);
        //
    }

So, everything works fine until this point. And here I'm stuck. I read a lot of articles about accesstokens and claims called pictureUrl and so on. But the Principal doesn't contain any of those.

So the question is: How to retrieve the profile image once in the ExternalLoginCallback function?

I struggled with the same problem but on ASP.NET Core 2.0. There is a better aproach to retrieve the picture from the OnCreatingTicket event in your startup.cs . In your case you have to add the specific claim "picture" to the identity.

    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddAuthentication()
            .AddCookie()
            .AddGoogle(options =>
            {
                options.ClientId = Configuration["Google.LoginProvider.ClientId"];
                options.ClientSecret = Configuration["Google.LoginProvider.ClientKey"];
                options.Scope.Add("profile");
                options.Events.OnCreatingTicket = (context) =>
                {
                    context.Identity.AddClaim(new Claim("image", context.User.GetValue("image").SelectToken("url").ToString()));

                    return Task.CompletedTask;
                };
            });
    }

Then in your AccountController you can select the image from the external login info method.

var info = await _signInManager.GetExternalLoginInfoAsync();

var picture = info.Principal.FindFirstValue("image");

For .net core 3.0+, Microsoft uses System.Text.Json for handling returned object from google, so we need to use GetProperty method of this new API to get picture.

https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

services.AddAuthentication()
                .AddGoogle(options =>
                {
                    IConfigurationSection googleAuthNSection = Configuration.GetSection("Authentication:Google");

                    options.ClientId = googleAuthNSection["ClientId"];
                    options.ClientSecret = googleAuthNSection["ClientSecret"];
                    options.Scope.Add("profile");
                    options.Events.OnCreatingTicket = (context) =>
                    {                      
                        var picture = context.User.GetProperty("picture").GetString();

                        context.Identity.AddClaim(new Claim("picture", picture));

                        return Task.CompletedTask;
                    };
                });

Google is starting to shut down Google+ Signin for applications as early as January 28th 2019.

https://github.com/aspnet/AspNetCore/issues/6486

So we have to do following changes to accepted answer by @mtrax :

.AddGoogle(o =>
            {
                o.ClientId = Configuration["Authentication:Google:ClientId"];
                o.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
                o.UserInformationEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo";
                o.ClaimActions.Clear();
                o.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
                o.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
                o.ClaimActions.MapJsonKey(ClaimTypes.GivenName, "given_name");
                o.ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name");
                o.ClaimActions.MapJsonKey("urn:google:profile", "link");
                o.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
                o.ClaimActions.MapJsonKey("image", "picture");
            });

I found no way to get the picture url from the claims. Finally I found a solution using the nameidentifier, which comes with the claims.

string googleApiKey = "{your google api key}";
ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync();
string nameIdentifier = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
string jsonUrl = $"https://www.googleapis.com/plus/v1/people/{nameIdentifier}?fields=image&key={googleApiKey}";
using (HttpClient httpClient = new HttpClient())
{
    string s = await httpClient.GetStringAsync(jsonUrl);
    dynamic deserializeObject = JsonConvert.DeserializeObject(s);
    string thumbnailUrl = (string)deserializeObject.image.url;
    byte[] thumbnail = await httpClient.GetByteArrayAsync(thumbnailUrl);
}

All you need is a Google API key.

To create an API key:

  1. Go to the Google API Console.
  2. From the project drop-down, select a project , or create a new one.
  3. Enable the Google+ API service:

a. In the list of Google APIs, search for the Google+ API service.

b.Select Google+ API from the results list.

c. Press the Enable API button.

When the process completes, Google+ API appears in the list of enabled APIs. To access, select APIs & Services on the left sidebar menu, then select the Enabled APIs tab.

  1. In the sidebar under "APIs & Services", select Credentials.
  2. In the Credentials tab, select the New credentials drop-down list, and choose API key.
  3. From the Create a new key pop-up, choose the appropriate kind of key for your project: Server key, Browser key, Android key, or iOS key.
  4. Enter a key Name, fill in any other fields as instructed, then select Create.

https://developers.google.com/+/web/api/rest/oauth

You can provide additional scopes you want to request in die GoogleOptions setup. The scope you are looking for is the profile scope, grating access to the firstname, lastname and profile picture via additional claims: https://developers.google.com/identity/protocols/OpenIDConnect#discovery

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        //...
        app.UseGoogleAuthentication(new GoogleOptions
        {
            AuthenticationScheme = "Google",
            SignInScheme = "Identity.External", // this is the name of the cookie middleware registered by UseIdentity()
            ClientId = Configuration["ExternalLoginProviders:Google:ClientId"],
            ClientSecret = Configuration["ExternalLoginProviders:Google:ClientSecret"]});
            Scopes = { "profile" };
    }

Then you can retrieve the picture claim in the controller after the authentication succeeds:

    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
    {
        //... SignInManager<User> _signInManager; declared before
        ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync();
        SignInResult signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
        string email = info.Principal.FindFirstValue(ClaimTypes.Email);
        string firstName = info.Principal.FindFirstValue(ClaimTypes.GivenName);
        string lastName = info.Principal.FindFirstValue(ClaimTypes.Surname);

        // profile claims
        string picture = info.Principal.FindFirstValue("picture");
        string firstName = info.Principal.FindFirstValue("given_name");
        string lastName = info.Principal.FindFirstValue("family_name");
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM