From 14fb18a35744170b8fcf769477e5d58e9f08809f Mon Sep 17 00:00:00 2001 From: Hesam Asnaashari <46031836+ilhesam@users.noreply.github.com> Date: Fri, 20 Jan 2023 12:55:53 +0330 Subject: [PATCH] feat - implement jwt claims decoration --- .../uBeac.Core.Identity.Jwt/Extensions.cs | 35 ++++++++++-- .../JwtClaimsDecorator.cs | 53 +++++++++++++++++++ .../JwtTokenService.cs | 24 +++------ .../UserJwtClaimsManager.cs | 37 +++++++++++++ 4 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 src/Identity/uBeac.Core.Identity.Jwt/JwtClaimsDecorator.cs create mode 100644 src/Identity/uBeac.Core.Identity.Jwt/UserJwtClaimsManager.cs diff --git a/src/Identity/uBeac.Core.Identity.Jwt/Extensions.cs b/src/Identity/uBeac.Core.Identity.Jwt/Extensions.cs index 34aa33e0..74fdc4f8 100644 --- a/src/Identity/uBeac.Core.Identity.Jwt/Extensions.cs +++ b/src/Identity/uBeac.Core.Identity.Jwt/Extensions.cs @@ -1,29 +1,56 @@ using Microsoft.Extensions.Configuration; using uBeac.Identity; +using uBeac.Identity.Jwt; namespace Microsoft.Extensions.DependencyInjection; public static class Extensions { - public static IServiceCollection AddJwtService(this IServiceCollection services, - IConfiguration config) + public static IServiceCollection AddJwtService(this IServiceCollection services, IConfiguration config) where TUserKey : IEquatable where TUser : User { services.Configure(config.GetSection("Jwt")); + services.AddScoped, JwtTokenService>(); services.AddScoped, JwtTokenService>(); + services.AddScoped>(); + services.AddScoped, DefaultUserJwtClaimsDecorator>(); + return services; } - public static IServiceCollection AddJwtService(this IServiceCollection services, IConfiguration config) + public static IServiceCollection AddJwtService(this IServiceCollection services, IConfiguration config, IUserJwtClaimsDecorator claimsDecorator = null) where TUser : User { services.Configure(config.GetSection("Jwt")); + services.AddScoped, JwtTokenService>(); services.AddScoped, JwtTokenService>(); + services.AddScoped>(); + services.AddScoped, DefaultUserJwtClaimsDecorator>(); + + return services; + } + + public static IServiceCollection AddJwtClaimsDecorator(this IServiceCollection services) + where TUserKey : IEquatable + where TUser : User + where TDecorator : class, IUserJwtClaimsDecorator + { + services.AddScoped, TDecorator>(); + + return services; + } + + public static IServiceCollection AddJwtClaimsDecorator(this IServiceCollection services) + where TUser : User + where TDecorator : class, IUserJwtClaimsDecorator + { + services.AddScoped, TDecorator>(); + return services; } -} \ No newline at end of file +} diff --git a/src/Identity/uBeac.Core.Identity.Jwt/JwtClaimsDecorator.cs b/src/Identity/uBeac.Core.Identity.Jwt/JwtClaimsDecorator.cs new file mode 100644 index 00000000..a75b12e1 --- /dev/null +++ b/src/Identity/uBeac.Core.Identity.Jwt/JwtClaimsDecorator.cs @@ -0,0 +1,53 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +namespace uBeac.Identity.Jwt; + +public interface IUserJwtClaimsDecorator + where TKey : IEquatable + where TUser : User +{ + IEnumerable GetClaims(TUser user); +} + +public interface IUserJwtClaimsDecorator : IUserJwtClaimsDecorator + where TUser : User +{ +} + +public class DefaultUserJwtClaimsDecorator : IUserJwtClaimsDecorator + where TKey : IEquatable + where TUser : User +{ + public IEnumerable GetClaims(TUser user) + { + var userId = user.Id.ToString(); + + var result = new List + { + new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new(JwtRegisteredClaimNames.Iat, DateTime.Now.ToLongDateString()), + new(JwtRegisteredClaimNames.Sub, userId), + new(ClaimTypes.NameIdentifier, userId), + new(ClaimTypes.Name, user.UserName) + }; + + if (!string.IsNullOrWhiteSpace(user.NormalizedEmail)) + { + result.Add(new Claim(ClaimTypes.Email, user.NormalizedEmail)); + } + + if (!string.IsNullOrWhiteSpace(user.PhoneNumber)) + { + result.Add(new Claim(ClaimTypes.MobilePhone, user.PhoneNumber)); + } + + result.AddRange(user.Roles.Select(userRole => new Claim(ClaimTypes.Role, userRole))); + return result; + } +} + +public class DefaultUserJwtClaimsDecorator : DefaultUserJwtClaimsDecorator, IUserJwtClaimsDecorator + where TUser : User +{ +} diff --git a/src/Identity/uBeac.Core.Identity.Jwt/JwtTokenService.cs b/src/Identity/uBeac.Core.Identity.Jwt/JwtTokenService.cs index 22503bac..261e74be 100644 --- a/src/Identity/uBeac.Core.Identity.Jwt/JwtTokenService.cs +++ b/src/Identity/uBeac.Core.Identity.Jwt/JwtTokenService.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using System.Text; using Microsoft.IdentityModel.Tokens; +using uBeac.Identity.Jwt; namespace uBeac.Identity; @@ -23,9 +24,12 @@ public class JwtTokenService : IJwtTokenService options) + protected readonly UserJwtClaimsManager ClaimsManager; + + public JwtTokenService(Microsoft.Extensions.Options.IOptions options, UserJwtClaimsManager claimsManager) { Options = options.Value; + ClaimsManager = claimsManager; } public virtual async Task Generate(TUser user) @@ -76,21 +80,9 @@ protected virtual JwtResult GenerateToken(TUser user) }; } - protected virtual List GetJwtClaims(TUser user) + protected virtual IReadOnlyList GetJwtClaims(TUser user) { - var userId = user.Id.ToString(); - var result = new List - { - new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new(JwtRegisteredClaimNames.Iat, DateTime.Now.ToLongDateString()), - new(JwtRegisteredClaimNames.Sub, userId), - new(ClaimTypes.NameIdentifier, userId), - new(ClaimTypes.Name, user.UserName) - }; - if (!string.IsNullOrWhiteSpace(user.NormalizedEmail)) result.Add(new Claim(ClaimTypes.Email, user.NormalizedEmail)); - if (!string.IsNullOrWhiteSpace(user.PhoneNumber)) result.Add(new Claim(ClaimTypes.Email, user.PhoneNumber)); - result.AddRange(user.Roles.Select(userRole => new Claim(ClaimTypes.Role, userRole))); - return result; + return ClaimsManager.GetClaims(user); } public virtual async Task Validate(string accessToken) @@ -154,7 +146,7 @@ protected virtual TUserKey GetUserId(ClaimsPrincipal principal) public class JwtTokenService : JwtTokenService, IJwtTokenService where TUser : User { - public JwtTokenService(Microsoft.Extensions.Options.IOptions options) : base(options) + public JwtTokenService(Microsoft.Extensions.Options.IOptions options, UserJwtClaimsManager claimsManager) : base(options, claimsManager) { } } \ No newline at end of file diff --git a/src/Identity/uBeac.Core.Identity.Jwt/UserJwtClaimsManager.cs b/src/Identity/uBeac.Core.Identity.Jwt/UserJwtClaimsManager.cs new file mode 100644 index 00000000..e96b6e10 --- /dev/null +++ b/src/Identity/uBeac.Core.Identity.Jwt/UserJwtClaimsManager.cs @@ -0,0 +1,37 @@ +using System.Security.Claims; + +namespace uBeac.Identity.Jwt; + +public class UserJwtClaimsManager + where TKey : IEquatable + where TUser : User +{ + protected readonly IReadOnlyList> Decorators; + + public UserJwtClaimsManager(IEnumerable> decorators) + { + Decorators = decorators.ToList().AsReadOnly(); + } + + public virtual IReadOnlyList GetClaims(TUser user) + { + var result = new List(); + + foreach (var decorator in Decorators) + { + var claims = decorator.GetClaims(user); + + result.AddRange(claims); + } + + return result.AsReadOnly(); + } +} + +public class UserJwtClaimsManager : UserJwtClaimsManager + where TUser : User +{ + public UserJwtClaimsManager(IEnumerable> decorators) : base(decorators) + { + } +}