user14256351
user14256351

Reputation: 23

How can I make a custom claim accessible inside a webapi secured by Identity Server 4?

I have an asp.net core mvc web application and an asp.net core api project secured with IdentityServer4. When the user logs in (to the mvc app), I add a claim ("mycompany") which I would like to be accessible in the api project.

I can access the additional claim in the mvc project no problem, but I cannot access the new claim in the api project.

How can I make the claim ("mycompany") show up in the api project?

MVC App Startup.cs

services.AddAuthentication(options =>
                {
                    options.DefaultScheme = "Cookies";
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie("Cookies").AddOpenIdConnect("oidc", options =>
                {
                    options.Authority = Configuration.GetSection("IdpConfig").GetValue<string>("IdpUri");
                    options.RequireHttpsMetadata = false;

                    options.ClientId = "MyProject.Web";
                    options.ClientSecret = "SomeSecret";
                    options.ResponseType = "code id_token";
                    options.SaveTokens = true;

                    options.Events = new OpenIdConnectEvents
                    {
                        OnTicketReceived = async (context) =>
                        {
                            try
                            {
                                var claimsIdentity = context.Principal.Identity as ClaimsIdentity;

                                claimsIdentity.AddClaim(new Claim("mycompany", "some company"));

                                await Task.FromResult(0);
                            }
                            catch (Exception ex)
                            {
                                // Log This
                            }
                        },
                        OnUserInformationReceived = (context) => Task.FromResult(0),
                        OnTokenValidated = (context) =>
                        {
                            return Task.FromResult(0);
                        },
                        OnRedirectToIdentityProvider = (context) => Task.CompletedTask
                    };
                });

In the MVC app this works (returns "some company") but from within the api the claim does not exist

public static string GetCompany(this ClaimsPrincipal principal)
{
    var company = principal.Claims.FirstOrDefault(c => c.Type == "mycompany");
    return company?.Value;
}

Upvotes: 1

Views: 684

Answers (3)

rubo
rubo

Reputation: 465

To access custom claims in your API project, you need to include those claims in your JWT access token. In the case of IdentityServer4, you need to implement the IProfileService interface.

For example

public class UserProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        // Ensure adding claims to access token only
        if (context.Caller.Equals("ClaimsProviderAccessToken", StringComparison.OrdinalIgnoreCase))
        {
            var claims = new List<Claim>();

            claims.Add(new Claim("<CUSTOM_CLAIM_TYPE>", "value", ClaimValueTypes.String));
            
            context.RequestedClaimTypes = new[] { "<CUSTOM_CLAIM_TYPE>" };

            context.AddRequestedClaims(claims);
        }

        return Task.CompletedTask;
    }

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

        return Task.CompletedTask;
    }
}

You also can use IUserClaimsPrincipalFactory to add custom claims to identity and then just filter them in the GetProfileDataAsync() method by RequestedClaimTypes.

Then in the ConfigureServices() method of Startup class, register the UserProfileService with IIdentityServerBuilder:

services.AddIdentityServer(...)
    .AddProfileService<UserProfileService>();

Now, your access token has custom claims which you can access in your API project, for example, via the User property of the controller.

As it's already mentioned in the comments, it's recommended to keep the access token as light as possible and fetch custom claims only when you need them based on the user id in the access token.

Upvotes: 0

SlobodanT
SlobodanT

Reputation: 431

The way I see it working

On Identity server you can add new Claim for user(AspNetUserClaims table) and give it a name company and value TEST COMPANY. Add claim company inside IdentityClaims table and reference that record to IdentityResourceId of profile scope(IdentityResources table) so when your client app asks for profile scope IS will populate AccessToken with company claim and its value.

Configure MVC client to map custom claim and to be able to ask required scopes

options.ClaimActions.MapUniqueJsonKey("MyCompany", "company");

//two trips to load claims into the cookie
//but the id token is smaller
options.GetClaimsFromUserInfoEndpoint = true;

//configure scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");

If you get access token and examine it on JWT.IO should contain custom claim company with its value.

var accessToken = await HttpContext.GetTokenAsync("access_token");

In your MVC client you can get company name like this:

var companyName = User.Claims.Where(x => x.Type == "MyCompany").FirstOrDefault()?.Value;

Upvotes: 0

Tore Nestenius
Tore Nestenius

Reputation: 19921

they claim you add in the MVC application is added to the local identity, you need to get the new claim added to the access token that you send to the API. That claim you can add to the APIScope/APIResources in IdentitySever. You can't change the access in the MVC application because the signature of the token would no longer be correct.

Upvotes: 3

Related Questions