1

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?

Rush Frisby
  • 11,388
  • 19
  • 63
  • 83
  • Please note that the service can be called by multiple endpoints. You can determine the endpoint by checking the context.caller. Also note that the configured claims should be included (using context.RequestedClaimTypes) and then the additional claims should be added (claims.Add()). For an example check my answer here: https://stackoverflow.com/questions/48006060/identityserver4-too-many-calls-cause-performance-issue/48007146#48007146 –  Aug 18 '18 at 10:22

1 Answers1

2

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)

d_f
  • 4,599
  • 2
  • 23
  • 34
  • I logged all of the claims using StringBuilder similar to this. That's how I found out that the ProfileService was getting a filtered list. In ExternalController.ProcessLoginCallbackForOidc method I get the full list of claims – Rush Frisby Aug 23 '18 at 01:20
  • Yes, that's just the same. And that's the place where you persist (after some transformations if needed) your Identity to be used later as a Subject on the context. Could you add the essentials of that method to the question? And additionally, which ProfileService do you use by default? There are at least two implementations: one comes with IdSrv itself and the other with extension for ASP.NET Identity. There should not be any magic, just something obvious and simple... – d_f Aug 23 '18 at 09:50