Search code examples
c#asp.netasp.net-coreasp.net-identitygoogle-oauth

Google Authentication get user profile picture


If user is authenticated via google, I need to get his profile picture.

If user is authenticated via facebook I get his profile picture with this code:

var info = await _signInManager.GetExternalLoginInfoAsync();
var identifier = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier); 
var picture = $"https://graph.facebook.com/{identifier}/picture"; // 3

So, which code I need to use in 3 line for getting user's profile picture in case user is authenticated via google?


Solution

  • Accessing user profile picture, full solution.

    Stack:
    Asp Net Identity 4 v4 + Asp Net Identity + Google People Api

    Steps

    1. Preparation

    1.1 I'm using default HttpClient to generate HTTP requests to Google API
    1.2 Install NUGET System.Net.Http.Json to ease serialization. 1.3 Use a [json to c#] tool, to create a DTO like this:

    
    /// <summary>
        /// 🟡 official docs.
        /// https://developers.google.com/people/api/rest/v1/people#Person.Photo
        /// </summary>
        public class PeopleApiPhotos    {
            
            public string resourceName { get; set; } 
            public string etag { get; set; } 
            public List<Photo> photos { get; set; }
            
            public class Source    {
                public string type { get; set; } 
                public string id { get; set; } 
            }
    
            public class Metadata    {
                public bool primary { get; set; } 
                public Source source { get; set; } 
            }
    
            public class Photo    {
                public Metadata metadata { get; set; } 
                public string url { get; set; } 
            }
        }
    
    

    2. Identity Server: when users confirms external info data, send http get request, to retrieve avatar picture url:

    Identity Server with Asp Net Identity > Login > OnPostConfirmationAsync: FULL METHOD

    
    public async Task < IActionResult > OnPostConfirmationAsync(string returnUrl = null) {
      returnUrl = returnUrl ? ?Url.Content("~/");
      // Get the information about the user from the external login provider
      var info = await _signInManager.GetExternalLoginInfoAsync();
      if (info == null) {
        ErrorMessage = "Error loading external login information during confirmation.";
        return RedirectToPage("./Login", new {
          ReturnUrl = returnUrl
        });
      }
    
      // try to get profile picture
      string pictureUri = string.Empty;
    
      if (info.LoginProvider.ToLower() == "google") {
        var httpClient = _httpClientFactory.CreateClient();
        string peopleApiKey = _configuration["GoogleApiKey:PeopleApiKey"];;
        var googleAccountId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
        var photosResponse = await httpClient.GetFromJsonAsync < PeopleApiPhotos > (
        $ "https://people.googleapis.com/v1/people/{googleAccountId}?personFields=photos&key={peopleApiKey}");
        pictureUri = photosResponse ? .photos.FirstOrDefault() ? .url;
      }
    
      if (ModelState.IsValid) {
        // Cria usuário
        var user = new AppUser {
          UserName = Input.Email,
          Email = Input.Email,
          FirstName = Input.FirstName,
          LastName = Input.LastName,
          ProfilePictureUrl = pictureUri
        };
    
        var result = await _userManager.CreateAsync(user);
        if (result.Succeeded) {
          result = await _userManager.AddLoginAsync(user, info);
          if (result.Succeeded) {
            _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
    
            var userId = await _userManager.GetUserIdAsync(user);
            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            var callbackUrl = Url.Page("/Account/ConfirmEmail", pageHandler: null, values: new {
              area = "Identity",
              userId = userId,
              code = code
            },
            protocol: Request.Scheme);
    
            await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $ "Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
    
            // If account confirmation is required, we need to show the link if we don't have a real email sender
            if (_userManager.Options.SignIn.RequireConfirmedAccount) {
              return RedirectToPage("./RegisterConfirmation", new {
                Email = Input.Email
              });
            }
    
            await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
    
            return LocalRedirect(returnUrl);
          }
        }
        foreach(var error in result.Errors) {
          ModelState.AddModelError(string.Empty, error.Description);
        }
      }
    
      ProviderDisplayName = info.ProviderDisplayName;
      ReturnUrl = returnUrl;
      return Page();
    }
    
    

    2.1 Identity Server with Asp Net Identity > Login > OnPostConfirmationAsync: HIGHLIGHT:

    ❗️ this is only a little part from full method above.

    Checks if external provider is Google, and use [NameIdentifier] and [Google Api Key] to reach People Endpoint.

    
    // try to get profile picture
    string pictureUri = string.Empty;
    
    if (info.LoginProvider.ToLower() == "google") {
      var httpClient = _httpClientFactory.CreateClient();
    // ApiKey can get generated in [Google Developers Console](https://console.developers.google.com/apis/credentials).
      string peopleApiKey = _configuration["GoogleApiKey:PeopleApiKey"];;
      var googleAccountId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
      var photosResponse = await httpClient.GetFromJsonAsync < PeopleApiPhotos > (
      $ "https://people.googleapis.com/v1/people/{googleAccountId}?personFields=photos&key={peopleApiKey}");
      pictureUri = photosResponse ? .photos.FirstOrDefault() ? .url;
    }
    
    

    3. Identity Server > Profile Service Implementation:

    
    public async Task GetProfileDataAsync(ProfileDataRequestContext context) {
      var sub = context.Subject.GetSubjectId();
      var user = await _userManager.FindByIdAsync(sub);
      var principal = await _claimsFactory.CreateAsync(user);
    
      var claims = principal.Claims.ToList();
    
      claims.Add(new Claim(JwtClaimTypes.GivenName, user.FirstName));
      claims.Add(new Claim(JwtClaimTypes.FamilyName, user.LastName));
    
      // Insert a new claim, that gets ProfilePictureUrl persisted in my app user in database.
      claims.Add(new Claim(JwtClaimTypes.Picture, user.ProfilePictureUrl));
    
      context.IssuedClaims = claims;
    }
    
    

    4. Acessing profile user in ReactJS Frontend:

    To get user profile data in frontend, i'm using OidcClient-js

    
    // here i'm using oidc-client.js
    // user object is loaded with user full data.
    let profilePictureUrl = user.profile.picture;
    
    

    showing profile picture successfully loaded from Google People API


    Thanks to @DaImTo response.