2

I have created multiple claims that sit in the AspNetUserClaims table for identity and have assigned them to my user id.

I am currently trying to get these to pull through in the list of claims I receive in my client application.

I have managed to pull through all the roles from the AspNetUserRoles table by adding the 'roles' scope to my client identity settings and then also in identity configuration (using the EF database format a.k.a ConfigurationDbContext) created a record in the IdentityResources table which links to an identity claim called 'role'.

This is working as expected. However, I am not getting any of my UserClaims I have created through, do I need to create another specific scope?

Here is my client configuration:

services.AddAuthentication(options =>
    {
        options.DefaultScheme = "cookie";
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("cookie")
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = "https://localhost:44335/";
        options.ClientId = "openIdConnectClient";
        options.SignInScheme = "cookie";
        options.ResponseType = "id_token";
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("openid profile roles all_claims");
    });

services.AddAuthorization();

this is how I'm checking what claims the user has:

var claims = ((ClaimsIdentity)User.Identity).Claims;

and it returns all roles and profile claims (e.g. preferred_username) just not those specified within the AspNetUserClaims table.

For my client I have also set the property [AlwaysIncludeUserClaimsInIdToken] to true with no luck.

Does anyone know what I'm missing to pass through the user claims?

DrollDread
  • 321
  • 4
  • 22

2 Answers2

2

you can get the user claims like this:

var claims = User.Claims.Select(c => new { c.Type, c.Value });

you can implement this as an endpoint in your api which you stated as scope in your identity server:

using IdentityServer4;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Linq;

namespace IdentityServer4Demo.Api
{
    [Route("/api/test")]
    [Authorize]
    public class TestController : ControllerBase
    {
        public IActionResult Get()
        {
            var claims = User.Claims.Select(c => new { c.Type, c.Value });
            return new JsonResult(claims);
        }
    }
}

If you want to add more claim you need to add property to the class which implements IdentityUser and use it in your custom profile service

using Microsoft.AspNetCore.Identity;

namespace AuthServer.Infrastructure.Data.Identity
{
    public class AppUser : IdentityUser
    {
        // Add additional profile data for application users by adding properties to this class
        public string Name { get; set; }        
    }
}

your custom profile service:

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AuthServer.Infrastructure.Constants;
using AuthServer.Infrastructure.Data.Identity;
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;

namespace AuthServer.Infrastructure.Services
{
    public class IdentityClaimsProfileService : IProfileService
    {
        private readonly IUserClaimsPrincipalFactory<AppUser> _claimsFactory;
        private readonly UserManager<AppUser> _userManager;

        public IdentityClaimsProfileService(UserManager<AppUser> userManager, IUserClaimsPrincipalFactory<AppUser> claimsFactory)
        {
            _userManager = userManager;
            _claimsFactory = claimsFactory;
        }

        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var sub = context.Subject.GetSubjectId();
            var user = await _userManager.FindByIdAsync(sub);
            var principal = await _claimsFactory.CreateAsync(user);

            var claims = principal.Claims.ToList();
            claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
            claims.Add(new Claim(JwtClaimTypes.GivenName, user.Name));
            claims.Add(new Claim(IdentityServerConstants.StandardScopes.Email, user.Email));
            // note: to dynamically add roles (ie. for users other than consumers - simply look them up by sub id
            claims.Add(new Claim(ClaimTypes.Role, Roles.Consumer)); // need this for role-based authorization - https://stackoverflow.com/questions/40844310/role-based-authorization-with-identityserver4

            context.IssuedClaims = claims;
        }
}
Kayzer
  • 103
  • 6
  • 1
    I understand how to pull through the claims from the authenticated user, the problem is the the claims stored in the database table `AspNetUserClaims` are not pulled into the list of claims. I only get back the list of default claims and the roles. `var claims = User.Claims.Select(c => new { c.Type, c.Value });` does effectively the same thing as the line I posted in the question: `var claims = ((ClaimsIdentity)User.Identity).Claims;` – DrollDread Jan 02 '20 at 15:32
  • I understand, I updated my answer. It can be useful but require more configuration. – Kayzer Jan 02 '20 at 15:52
  • I attempted to set up a custom profile service, however this did not impact the claims that get passed through to the client application. Further it does not look like this answer would help with dynamic claims, only ones with implicit naming and configuration within the user entity. – DrollDread Jan 03 '20 at 08:05
2

Do you have a IProfileService implementation to populate your custom claims?

You should implemet IProfileService as indicated in this answer.

Try other response_type than id_token since your application does not have an access token to call User Info endpoint. Maybe with id_token token to maintain the implicit flow grant of your client.

Alpha75
  • 2,140
  • 1
  • 26
  • 49
  • I attempted to implement IProfileService and registered in configure services, however this did not seem to impact the claims that come through to the client application. Am I missing something? – DrollDread Jan 03 '20 at 08:03
  • You are using the `response_type=id_token`. You aren't getting an access token and your handler needs the access token for calling the user info endpoint. Have you tried other `response_type`? – Alpha75 Jan 03 '20 at 19:53
  • I have attemped to use alternate response types including the one suggested (id_token token) however, this did not bring in the additional claims. – DrollDread Jan 06 '20 at 07:47
  • turns out I'd missed a step, the profile service had to be registered in my identity configuration as well as as a dependency. i.e. '`services.AddIdentityServer().AddProfileService()...;` – DrollDread Jan 07 '20 at 10:23