How to add additional claims to be included in the access_token using ASP.Net Identity with IdentityServer4

前端 未结 1 1264
太阳男子
太阳男子 2020-12-08 23:25

How to add additional claims to be included within the token?

As soon as the API receives the bearer token, the User.Identity object gets populated with the followin

相关标签:
1条回答
  • 2020-12-09 00:23

    I had been fighting this same issue for hours and finally pieced together the solution. This article was a big help, but to summarize and share my implementation:

    In order to get the claims assigned to the user and attach them to the access token, you need to implement two interfaces on the identity server: IResourceOwnerPasswordValidator and IProfileService. The following are my implementations of the two classes and are rough drafts, but they work.

    **Be sure to get the latest version of IdentityServer4 - 1.0.2 at this time.

    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {
        private readonly UserManager<ApplicationUser> _userManager;
    
        public ResourceOwnerPasswordValidator(UserManager<ApplicationUser> userManager)
        {
            _userManager = userManager;
        }
    
        public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            var userTask = _userManager.FindByNameAsync(context.UserName);
            var user = userTask.Result;
    
            context.Result = new GrantValidationResult(user.Id, "password", null, "local", null);
            return Task.FromResult(context.Result);
        }
    }
    

    and

    public class AspNetIdentityProfileService : IProfileService
    {
        private readonly UserManager<ApplicationUser> _userManager;
    
        public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
        {
            _userManager = userManager;
        }
    
        public async Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            var subject = context.Subject;
            if (subject == null) throw new ArgumentNullException(nameof(context.Subject));
    
            var subjectId = subject.GetSubjectId();
    
            var user = await _userManager.FindByIdAsync(subjectId);
            if (user == null)
                throw new ArgumentException("Invalid subject identifier");
    
            var claims = await GetClaimsFromUser(user);
    
            var siteIdClaim = claims.SingleOrDefault(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
            context.IssuedClaims.Add(new Claim(JwtClaimTypes.Email, user.Email));
            context.IssuedClaims.Add(new Claim("siteid", siteIdClaim.Value));
            context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, "User"));
    
            var roleClaims = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
            foreach (var roleClaim in roleClaims)
            {
                context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, roleClaim.Value));
            }
        }
    
        public async Task IsActiveAsync(IsActiveContext context)
        {
            var subject = context.Subject;
            if (subject == null) throw new ArgumentNullException(nameof(context.Subject));
    
            var subjectId = subject.GetSubjectId();
            var user = await _userManager.FindByIdAsync(subjectId);
    
            context.IsActive = false;
    
            if (user != null)
            {
                if (_userManager.SupportsUserSecurityStamp)
                {
                    var security_stamp = subject.Claims.Where(c => c.Type == "security_stamp").Select(c => c.Value).SingleOrDefault();
                    if (security_stamp != null)
                    {
                        var db_security_stamp = await _userManager.GetSecurityStampAsync(user);
                        if (db_security_stamp != security_stamp)
                            return;
                    }
                }
    
                context.IsActive =
                    !user.LockoutEnabled ||
                    !user.LockoutEnd.HasValue ||
                    user.LockoutEnd <= DateTime.Now;
            }
        }
    
        private async Task<IEnumerable<Claim>> GetClaimsFromUser(ApplicationUser user)
        {
            var claims = new List<Claim>
            {
                new Claim(JwtClaimTypes.Subject, user.Id),
                new Claim(JwtClaimTypes.PreferredUserName, user.UserName)
            };
    
            if (_userManager.SupportsUserEmail)
            {
                claims.AddRange(new[]
                {
                    new Claim(JwtClaimTypes.Email, user.Email),
                    new Claim(JwtClaimTypes.EmailVerified, user.EmailConfirmed ? "true" : "false", ClaimValueTypes.Boolean)
                });
            }
    
            if (_userManager.SupportsUserPhoneNumber && !string.IsNullOrWhiteSpace(user.PhoneNumber))
            {
                claims.AddRange(new[]
                {
                    new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber),
                    new Claim(JwtClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed ? "true" : "false", ClaimValueTypes.Boolean)
                });
            }
    
            if (_userManager.SupportsUserClaim)
            {
                claims.AddRange(await _userManager.GetClaimsAsync(user));
            }
    
            if (_userManager.SupportsUserRole)
            {
                var roles = await _userManager.GetRolesAsync(user);
                claims.AddRange(roles.Select(role => new Claim(JwtClaimTypes.Role, role)));
            }
    
            return claims;
        }
    }
    

    Once you have those, they need to be added to your services in startup.cs:

    services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
    services.AddTransient<IProfileService, AspNetIdentityProfileService>();
    

    Here is a quick look at my config:

    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId()
        };
    }
    
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource
            {
                Name = "api1",
                Description = "My Api",
                Scopes =
                {
                    new Scope()
                    {
                        Name = "api1",
                        DisplayName = "Full access to Api"
                    }
                }
            }
        };
    }
    
    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "apiClient",
                ClientName = "Api Angular2 Client",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                AlwaysSendClientClaims = true,
                AlwaysIncludeUserClaimsInIdToken = true,
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
    
                AllowedScopes =
                {
                    "api1"
                }
            }
        };
    }
    

    After that, a call to the identity server from a client:

    var discoTask = DiscoveryClient.GetAsync("http://localhost:5000");
    var disco = discoTask.Result;
    
    var tokenClient = new TokenClient(disco.TokenEndpoint, "apiClient", "secret");
    var tokenResponseTask = tokenClient.RequestResourceOwnerPasswordAsync("user@domain.com", "my-password", "api1");
    
    var tokenResponse = tokenResponseTask.Result;
    var accessToken = tokenResponse.AccessToken;
    
    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }
    

    Inspect the token at jwt.io and see your results...

    0 讨论(0)
提交回复
热议问题