3

I'm currently failing at wrapping my head around claims. I have a ASP.Net Core 3 project with the angular template and users stored in app.

I want to add claims to my users, reading up on I thought it would be easy, just add something along the lines of

await _UserManager.AddClaimAsync(user, new Claim(AccountStatic.ClaimTypes._Claim_Id, user.Id));

When you create the user, and then get it back using the below line once they are logged in again:

User.FindFirst(AccountStatic.ClaimTypes._Claim_Id)?.Value;

This does however not work. I can see the claims being written to AspNetUserClaims table in my database but it's not there in the users claims when they log in. There are a few other claims there, but not the ones I have added.

Do I need to define somewhere which of the users claims get included when they log in?

Edit. I found a post stating that I need to add claims using a DI AddClaimsPrincipalFactory. So I added this class.

public class UserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
    public UserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager,IOptions<IdentityOptions> optionsAccessor): base(userManager, optionsAccessor)
    {}

    //https://levelup.gitconnected.com/add-extra-user-claims-in-asp-net-core-web-applications-1f28c98c9ec6
    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        identity.AddClaim(new Claim(AccountStatic.ClaimTypes.Claim_Id, user.Id ?? "[no id]"));
        return identity;
    }
}

And if I step through the code I can see the claims being added here. But in the Controller my custom claims are not present.

internal string GetUserId()
{
    if (User.Identity.IsAuthenticated == false)
        return null;

    return User.FindFirst(AccountStatic.ClaimTypes.Claim_Id)?.Value;
}

Update. Ok I find this very strange. I have been trying to do what others claim work but for me nothing gets me the users name or id. inspecting the User I get the following. Nothing here contains any reference to the logged in user.

enter image description here

Update 2: Just noticed that there is actually an Id in there: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: ed107a11-6c62-496b-901e-ed9e6497662a} Seems to be the users id from the database. Not sure how to access it yet though.

These return null.

User.FindFirst(JwtRegisteredClaimNames.NameId)?.Value;
User.FindFirst("nameidentifier")?.Value;
User.FindFirst("NameIdentifier")?.Value;

Another update I'm using a UserClaimsPrincipalFactory and breakingpointing it and looking at the Claims I can see that all of the ones I want are there. But again, these are not available in my API controllers as seen in the first picture. enter image description here

JensB
  • 6,663
  • 2
  • 55
  • 94
  • 1
    There are two tokens: _identity token_ and _access token_. Read my answer [here](https://stackoverflow.com/questions/53976553/#54004765) and [here](https://stackoverflow.com/questions/54006026/#54016213) for more info. The client receives an identity token, which contains by default only a sub claim. Additional info can be requested by the client using the UserInfo endpoint. The client requests info from the api, on behalf of the user, using the access token. It's the only source the api has, apart from info in the request or stored info linked to the sub (resource-based authorization) –  Oct 25 '19 at 22:53
  • 1
    In your case your client app can request the user information from the UserInfo endpoint and store it locally. No need to make claims part of the access token or store information in the initial identity token. Use the access token for communication with the resource(s). If configured correctly, you don't have to add code to IdentityServer to add claims. –  Oct 25 '19 at 23:06
  • @RuardvanElburg I wrote an answer summarizing my findings after reading your comment. If I understood it correctly I am now forcing my id claim into the Authorization token. – JensB Oct 26 '19 at 08:37

1 Answers1

4

I finally understood the problem, in large parts thanks to Ruard van Elburgs comments, and the answer he made in the linked question IdentityServer4 Role Based Authorization.

The problem is that the claims are not added to the access token. There are two tokens, the access token and the identity token. - Ruard van Elburg

They key to understanding what was going on was finding out that there are two tokens, and that they contain different claims and have different purposes.

You can force claims from one token to also be included in the other if you deem it necessary.

The solution to my problem was to add this in Startup.ConfigureServices

services
    .AddIdentityServer(options => {})
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
    {
        foreach (var c in options.ApiResources)
        {
            // the string name of the token I want to include
            c.UserClaims.Add(AccountStatic.ClaimTypes.Claim_Id); 
        }
    });

I still have not figured out how to get the Identity token, but as I'm now including the user Id in the access token my problems are solved for the moment.

Yennefer
  • 5,704
  • 7
  • 31
  • 44
JensB
  • 6,663
  • 2
  • 55
  • 94
  • 1
    The Identity token is part of the initial request, but contains the sub claim only. The response of the UserInfo endpoint should contain the additional claims. The endoint is accessable with the access token, as documented [here](http://docs.identityserver.io/en/latest/endpoints/userinfo.html). You can use the extension method [GetUserInfoAsync](https://github.com/IdentityModel/IdentityModel/blob/a7e13ff81f723a3af9218abbaf02d8cf01e57899/src/Client/Extensions/HttpClientUserInfoExtensions.cs#L24) –  Oct 26 '19 at 12:02
  • 1
    [Mvc clients](http://docs.identityserver.io/en/latest/quickstarts/5_hybrid_and_api_access.html#modifying-the-mvc-client) can also force the claims by using this statement in the configuration: `options.GetClaimsFromUserInfoEndpoint`. –  Oct 26 '19 at 12:06