Search code examples
c#asp.net-core-2.0identityserver4openid-connectclaims-based-identity

Provider claims in IProfileService


When I auth using oidc I get back a bunch of claims. If I do not add my custom IProfileService all of these claims are passed through in the id_token that identity server issues. If I provide my own ProfileService, the list of claims on the Subject is a subset of what comes back from the idp. Is there any way to get the full list in the profile service?

Here is the relevant info from Startup.cs:

var builder = services.AddIdentityServer(options =>
{
    options.Events.RaiseErrorEvents = true;
    options.Events.RaiseInformationEvents = true;
    options.Events.RaiseFailureEvents = true;
    options.Events.RaiseSuccessEvents = true;
}).AddProfileService<ProfileService>();

services.AddAuthentication()
.AddOpenIdConnect("Name", "Name", o =>
{
    o.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
    o.SignOutScheme = IdentityServerConstants.SignoutScheme;
    o.Authority = "https://sub.domain.com/adfs/";
    o.ClientId = "00000000-0000-0000-0000-000000000000";
    o.ClientSecret = "secret";
    o.ResponseType = "id_token";
    o.SaveTokens = true;
    o.CallbackPath = "/signin-adfs";
    o.SignedOutCallbackPath = "/signout-callback-adfs";
    o.RemoteSignOutPath = "/signout-adfs";
    o.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    };
});

and my ProfileService:

public class ProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var objectGuidClaim = context.Subject.Claims.FirstOrDefault(x => x.Type == "ObjectGUID");
        if (objectGuidClaim != null)
        {
            var userId = new Guid(Convert.FromBase64String(objectGuidClaim.Value));
            context.IssuedClaims.Add(new Claim("UserId", userId.ToString()));
        }
        return Task.CompletedTask;
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        context.IsActive = true;
        return Task.CompletedTask;
    }
}

So in my case, without the ProfileService then ObjectGUID is passed through, but using the ProfileService, it's not available in context.Subject.Claims list.

My goal is to take the "ObjectGUID" claim from the idp which is a base64 encoded guid and convert it to a hex string and pass that along as the "UserId" claim from identity server.

I'm not even sure this is the best way. I've also tried converting it through ClaimActions but my action never executes (I tested with a random guid to make sure it wasn't something with the conversion):

o.ClaimActions.MapCustomJson("UserId", obj => {
    return Guid.NewGuid().ToString();
});

Is this a better way? Why is it not executing?


Solution

  • Try to:

    • ensure your Subject does not contain http://schemas.company.com/identity/claims/objectguid instead of just ObjectGUID
    • extend your OpenIdConnect configuration with: o.GetClaimsFromUserInfoEndpoint = true; together with o.ClaimActions.MapUniqueJsonKey("ObjectGUID", "ObjectGUID"); or o.ClaimActions.MapUniqueJsonKey("http://schemas.company.com/identity/claims/objectguid", "ObjectGUID");
    • if nothing before helped, try:

              o.Events = new OpenIdConnectEvents
              {
                  OnTicketReceived = context =>
                  {
                  var identity = context.Principal.Identity as ClaimsIdentity;
      
                  StringBuilder builder = new StringBuilder();
                  var claims = identity?.Claims.Select(x => $"{x.Type}:{x.Value};");
                  if (claims != null)
                  builder.AppendJoin(", ", claims);
                  Logger.LogInformation($"Ticket received: [Claims:{builder}]");
                  identity?.AddClaim(new Claim("userId", Guid.NewGuid().ToString()));
                  //you can embed your transformer here if you like
                  return Task.CompletedTask;
              }};
      

    (you can examine the exact incoming ticket here and leave the logging anyway for future purposes)