Roomey
Roomey

Reputation: 716

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?

Upvotes: 3

Views: 9218

Answers (2)

Gabriel
Gabriel

Reputation: 384

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.

Upvotes: 2

Linda Lawton - DaImTo
Linda Lawton - DaImTo

Reputation: 116958

People.get method returns a person object which contains

Your user needs to be authenticated with the profile scope.

enter image description here

Raw http request

GET https://people.googleapis.com/v1/people/me?personFields=photos HTTP/1.1

Authorization: Bearer [YOUR_ACCESS_TOKEN]
Accept: application/json

response

{
  "resourceName": "people/117200475532672775346",
  "etag": "%EgQBAzcuGgQBAgUHIgxHcHNCRHZycjVkZz0=",
  "photos": [
    {
      "metadata": {
        "primary": true,
        "source": {
          "type": "PROFILE",
          "id": "1172004755672775346"
        }
      },
      "url": "https://lh3.googleusercontent.com/a-/AOh14GhroCYJp2P9xeYeYk1npchBPK-zbtTxzNQo0WAHI20=s100"
    },
    {
      "metadata": {
        "source": {
          "type": "CONTACT",
          "id": "3faa96eb0baa4be"
        }
      },
      "url": "https://lh6.googleusercontent.com/-vuhaM1mUvwE/VFOBzFDW-TI/AAAAAAAAAAA/izR9rgfDIyoVoHd7Mq_OJmdbwjhEnfhEQCOQCEAE/s100/photo.jpg"
    }
  ]
}

Note: You can also get this information from the userinfo endpoint however Google does not guarantee that they will send the claims everytime you make the request so IMO its best to go though the people api.

Upvotes: 1

Related Questions