2

I am implementing a custom user store where my users provider lives in a separate service which I am reaching out using messaging.

The thing is GetProfileDataAsync and IsActiveAsync are getting called too many times (about 3 times each) causing messaging process to be launched each time.

Here is a simple implementation of IProfileService.

public class IdentityProfileService : IProfileService
{
    private readonly UserManager<ApplicationUser> _userManager;

    public IdentityProfileService (UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {

        var user = await _userManager.FindBySubjectIdAsync(context.Subject.GetSubjectId());
        if (user != null)
        {
            context.IssuedClaims = user.Claims;
        }
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var sub = context.Subject.GetSubjectId();
        var user = await _userManager.FindByIdAsync(sub);
        context.IsActive = user != null;
    }
}

My question is:

Is there a way to minimize the number of these calls? or can I check for some info that's existence means that there is no need to call _userManager.FindBySubjectIdAsync again?

Yahya Hussein
  • 8,767
  • 15
  • 58
  • 114

1 Answers1

7

The Profile Service is not called too many times, but it is called multiple times with a different context:

  • For the access token Context.Caller = ClaimsProviderAccessToken.
  • For the identity token Context.Caller = UserInfoEndpoint.

Each caller expects a different result. That is why you should filter the claims by the requested claim types:

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.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

    context.IssuedClaims = claims;
}

That is why the access_token contains different claims than the identity_token.

You could minimize the calls by not requesting info from other endpoints than the access endpoint. But you can also check the caller and act differently for each caller. You can also give the token a longer lifetime so you won't need to refresh the token that ofter.

  • thank you for the clarification, what do you mean by access and userinfo endpoints? – Yahya Hussein Dec 28 '17 at 12:11
  • There are multiple endpoints. Check /.well-known/openid-configuration to see which are available. Typically the token_endpoint is called when requesting a token. And the userinfo_endpoint is called when requesting identity information. –  Dec 28 '17 at 12:19
  • 1
    Since the client is requesting it you should check the openIdConnect configuration of the client. You have probably options.ResponseType = "code id_token"; You could try and remove id_token. ANd perhaps you can remove options like GetClaimsFromUserInfoEndpoint and scope = profile. –  Dec 28 '17 at 12:27
  • What's the benefit of using `IProfileService`? Is it something nice to have s.a. for checking which users are active? – dragonfly02 Dec 28 '17 at 16:48
  • `context.Subject.Claims` have the user claims , avoid an additional call to the database – Jay Oct 18 '19 at 05:16