Search code examples
asp.net-web-apiidentityserver4idp

How to access identity provider (idp) claim from .net Core web API in IdentityServer4?


In my .Net Core web API protected by IdentityServer4, I need to decide what identity provider (Google, Windows, or local, for instance) authenticated the user. So far, I am not sure how to do that.

If I search for idp claim from access_token in a controller, as shown below, I can see the claim value correctly

var accessToken = await HttpContext.GetTokenAsync("access_token");
var token = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var claim = token.Claims.First(c => c.Type == "idp").Value;

But if I try to find it using AuthorizationHandlerContext in a non-controller class in API as following, as shown in code below, it is not there

    var identity = context.User.Identity as ClaimsIdentity;
    if (identity != null)
    {
        IEnumerable<Claim> claims = identity.Claims;
        // var v = identity.FindFirst("idp").Value;
    }

So looks like that idp is indeed in the token, it just not accessible from the non-controller class where it is needed. How do I get idp from non-controller class in API?

UPDATE - 1

Here is my ConfigureService in my API

public void ConfigureServices(IServiceCollection services)
{
    IdentityModelEventSource.ShowPII = true; // test only
    services.AddControllers();

    services.AddControllers()
         .AddNewtonsoftJson(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );          
    });

    services.Configure<QLHostOptions>(Configuration.GetSection(QLHostOptions.Host));

    services.AddAuthentication("Bearer").AddJwtBearer("Bearer", options =>
    {
        options.Authority = Configuration.GetSection(QLHostOptions.Host).Get<QLHostOptions>().IdentityGateway;
        options.SaveToken = true; 

        // test only
        options.RequireHttpsMetadata = false;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false
        };
    }).AddOpenIdConnect(options =>
    {
        options.ClaimActions.Remove("aud");
    });

    services.AddTransient<IAuthorizationPolicyProvider, QLPolicyProvider>();
    services.AddTransient<IAuthorizationHandler, QLPermissionHandler>();

    services.AddTransient<gRPCServiceHelper>();

}

UPDATE-2

Changed ...Remove("idp") to inside AddJwtBearer, as Tory suggested, but it doesn't take it (see screenshot below):

enter image description here

and here is the access token from API

"eyJhbGciOiJSUzI1NiIsImtpZCI6IjBFM0Y2MkRGMTdFQUExQURFRTc1NDQzQzQ0M0YxRkU2IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NDAwMzExMDcsImV4cCI6MTY0MDAzNDcwNywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NjAwNSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjYwMDUvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoibXZjIiwic3ViIjoiZGM0YWI1OGMtNGVjMC00ZTAyLWIxM2YtYzEyYzk1MzJlNzcyIiwiYXV0aF90aW1lIjoxNjQwMDMxMTA2LCJpZHAiOiJHb29nbGUiLCJBc3BOZXQuSWRlbnRpdHkuU2VjdXJpdHlTdGFtcCI6IjJEQ0hXNVRER1E3NDNSUEpOWE43SVJIWlRIVllIUTRJIiwibmFtZSI6IkxpZmVuZyBYdSIsImVtYWlsIjoibGlmZW5neHUyNkBnbWFpbC5jb20iLCJyb2xlIjoiUUxBZG1pbiIsInByZWZlcnJlZF91c2VybmFtZSI6IjM1ZGJkMmY2LTlmNDUtNDJhYy04M2EzLTgzZmUyMTFjNTNiNSIsIklzRW5hYmxlZCI6IlRydWUiLCJRaWNMaW5rVUlEIjoiIiwianRpIjoiQzE3Qjc2QzQ0NjA4MzkxMDBENEExMEM4Q0YwQzA1NDEiLCJzaWQiOiIzMEY5NTA5NzQ3OUUxMzAyMUVBQTdDOTAzNzg4MDcxNiIsImlhdCI6MTY0MDAzMTEwNywic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiUWljTGlua0NJRCIsIlFpY0xpbmtVSUQiLCJyb2xlcyIsIklzRW5hYmxlZCIsIkxpZmVuZ0FQSSIsIlFpY0xpbmtBUEkiLCJvZmZsaW5lX2FjY2VzcyJdLCJhbXIiOlsiZXh0ZXJuYWwiXX0.boZCqYImWfkE48X5UgFOAAz9bR6CH2cwAYHGd4Ykg0vDH9qnYdje5Zmqov4HpINsu_rt16zxAX_JCEn0hvdznXK2NQyZSBGsjF0tcMgtOY0__kAfhpOT-fORakiIjeMWIKG7tPEHCxSib0wNuMNw6i3o1giAnPt0ch2DH0fBtaEYkq4MRKMCteFuqbX0cogXIuMewNywMvrHv4_MixhMy3L8_xIwFvTZ67jhUn4Fd5X58-jc-RPNudcP95XIjzHm9OzWfgegV1IAKjsv98XEYX1pUxm-nrOMgYWxEJSyxEpp0L_9RzKTr_LZ-ep-x5QRvVewgiozJV3mse0pHgTjbw"

Solution

  • By default many of the more internal claims in a token are removed from the User ClaimsPrinicpal claims.

    If you want to get a specific claim into your user, you can use in the client:

    }).AddOpenIDConnect(options =>
    {
        //Will result in that the aud claim is not removed.
        options.ClaimActions.Remove("idp");
    ...
    

    secondly, some of the claims are renamed and if you want to disable that renaming, you can add:

    // By default, Microsoft has some legacy claim mapping that converts
    // standard JWT claims into proprietary ones. This removes those mappings.
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
    JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
    

    For the API you should not need to do anything special to get the idp claim. I just ran a test with this setup in .NET 5:

    public void ConfigureServices(IServiceCollection services)
    {
    
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMyJwtBearer(opt =>
        {
            opt.IncludeErrorDetails = true;
            opt.MapInboundClaims = false;
            opt.TokenValidationParameters.RoleClaimType = "role";
            opt.TokenValidationParameters.NameClaimType = "name";
            opt.Audience = "paymentapi";
            opt.Authority = "https://localhost:6001";
        });
    
        services.AddControllers();
    }
    

    I did give it a test on .NET 5 and if I have this access token:

    {
      "nbf": 1640033816,
      "exp": 1640037416,
      "iss": "https://localhost:6001",
      "aud": "paymentapi",
      "client_id": "clientcredentialclient",
      "managment": "yes",
      "email": "[email protected]",
      "name": "tore nestenius",
      "idp": "Google",
      "role": [
        "admin",
        "developer",
        "support"
      ],
      "website": "https://www.tn-data.se",
      "jti": "5DC46A29372031F0AA6F7B62B5FDCCD6",
      "iat": 1640033816,
      "scope": [
        "payment"
      ]
    }
    

    Then my user in my API controller contains the idp claim:

    enter image description here

    To complement this answer, I wrote a blog post that goes into more detail about this topic: Debugging OpenID Connect claim problems in ASP.NET Core