Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Milestone 1 - Customizable JWT Claims #167

Open
wants to merge 1 commit into
base: dev/milestone-1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions src/Identity/uBeac.Core.Identity.Jwt/Extensions.cs
Original file line number Diff line number Diff line change
@@ -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<TUserKey, TUser>(this IServiceCollection services,
IConfiguration config)
public static IServiceCollection AddJwtService<TUserKey, TUser>(this IServiceCollection services, IConfiguration config)
where TUserKey : IEquatable<TUserKey>
where TUser : User<TUserKey>
{
services.Configure<JwtOptions>(config.GetSection("Jwt"));

services.AddScoped<ITokenService<TUserKey, TUser>, JwtTokenService<TUserKey, TUser>>();
services.AddScoped<IJwtTokenService<TUserKey, TUser>, JwtTokenService<TUserKey, TUser>>();

services.AddScoped<UserJwtClaimsManager<TUserKey, TUser>>();
services.AddScoped<IUserJwtClaimsDecorator<TUserKey, TUser>, DefaultUserJwtClaimsDecorator<TUserKey, TUser>>();

return services;
}

public static IServiceCollection AddJwtService<TUser>(this IServiceCollection services, IConfiguration config)
public static IServiceCollection AddJwtService<TUser>(this IServiceCollection services, IConfiguration config, IUserJwtClaimsDecorator<TUser> claimsDecorator = null)
where TUser : User
{
services.Configure<JwtOptions>(config.GetSection("Jwt"));

services.AddScoped<ITokenService<TUser>, JwtTokenService<TUser>>();
services.AddScoped<IJwtTokenService<TUser>, JwtTokenService<TUser>>();

services.AddScoped<UserJwtClaimsManager<TUser>>();
services.AddScoped<IUserJwtClaimsDecorator<TUser>, DefaultUserJwtClaimsDecorator<TUser>>();

return services;
}

public static IServiceCollection AddJwtClaimsDecorator<TUserKey, TUser, TDecorator>(this IServiceCollection services)
where TUserKey : IEquatable<TUserKey>
where TUser : User<TUserKey>
where TDecorator : class, IUserJwtClaimsDecorator<TUserKey, TUser>
{
services.AddScoped<IUserJwtClaimsDecorator<TUserKey, TUser>, TDecorator>();

return services;
}

public static IServiceCollection AddJwtClaimsDecorator<TUser, TDecorator>(this IServiceCollection services)
where TUser : User
where TDecorator : class, IUserJwtClaimsDecorator<TUser>
{
services.AddScoped<IUserJwtClaimsDecorator<TUser>, TDecorator>();

return services;
}
}
}
53 changes: 53 additions & 0 deletions src/Identity/uBeac.Core.Identity.Jwt/JwtClaimsDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace uBeac.Identity.Jwt;

public interface IUserJwtClaimsDecorator<TKey, in TUser>
where TKey : IEquatable<TKey>
where TUser : User<TKey>
{
IEnumerable<Claim> GetClaims(TUser user);
}

public interface IUserJwtClaimsDecorator<in TUser> : IUserJwtClaimsDecorator<Guid, TUser>
where TUser : User
{
}

public class DefaultUserJwtClaimsDecorator<TKey, TUser> : IUserJwtClaimsDecorator<TKey, TUser>
where TKey : IEquatable<TKey>
where TUser : User<TKey>
{
public IEnumerable<Claim> GetClaims(TUser user)
{
var userId = user.Id.ToString();

var result = new List<Claim>
{
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<TUser> : DefaultUserJwtClaimsDecorator<Guid, TUser>, IUserJwtClaimsDecorator<TUser>
where TUser : User
{
}
24 changes: 8 additions & 16 deletions src/Identity/uBeac.Core.Identity.Jwt/JwtTokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using uBeac.Identity.Jwt;

namespace uBeac.Identity;

Expand All @@ -23,9 +24,12 @@ public class JwtTokenService<TUserKey, TUser> : IJwtTokenService<TUserKey, TUser
{
protected readonly JwtOptions Options;

public JwtTokenService(Microsoft.Extensions.Options.IOptions<JwtOptions> options)
protected readonly UserJwtClaimsManager<TUserKey, TUser> ClaimsManager;

public JwtTokenService(Microsoft.Extensions.Options.IOptions<JwtOptions> options, UserJwtClaimsManager<TUserKey, TUser> claimsManager)
{
Options = options.Value;
ClaimsManager = claimsManager;
}

public virtual async Task<TokenResult> Generate(TUser user)
Expand Down Expand Up @@ -76,21 +80,9 @@ protected virtual JwtResult GenerateToken(TUser user)
};
}

protected virtual List<Claim> GetJwtClaims(TUser user)
protected virtual IReadOnlyList<Claim> GetJwtClaims(TUser user)
{
var userId = user.Id.ToString();
var result = new List<Claim>
{
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<TUserKey> Validate(string accessToken)
Expand Down Expand Up @@ -154,7 +146,7 @@ protected virtual TUserKey GetUserId(ClaimsPrincipal principal)
public class JwtTokenService<TUser> : JwtTokenService<Guid, TUser>, IJwtTokenService<TUser>
where TUser : User
{
public JwtTokenService(Microsoft.Extensions.Options.IOptions<JwtOptions> options) : base(options)
public JwtTokenService(Microsoft.Extensions.Options.IOptions<JwtOptions> options, UserJwtClaimsManager<TUser> claimsManager) : base(options, claimsManager)
{
}
}
37 changes: 37 additions & 0 deletions src/Identity/uBeac.Core.Identity.Jwt/UserJwtClaimsManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Security.Claims;

namespace uBeac.Identity.Jwt;

public class UserJwtClaimsManager<TKey, TUser>
where TKey : IEquatable<TKey>
where TUser : User<TKey>
{
protected readonly IReadOnlyList<IUserJwtClaimsDecorator<TKey, TUser>> Decorators;

public UserJwtClaimsManager(IEnumerable<IUserJwtClaimsDecorator<TKey, TUser>> decorators)
{
Decorators = decorators.ToList().AsReadOnly();
}

public virtual IReadOnlyList<Claim> GetClaims(TUser user)
{
var result = new List<Claim>();

foreach (var decorator in Decorators)
{
var claims = decorator.GetClaims(user);

result.AddRange(claims);
}

return result.AsReadOnly();
}
}

public class UserJwtClaimsManager<TUser> : UserJwtClaimsManager<Guid, TUser>
where TUser : User
{
public UserJwtClaimsManager(IEnumerable<IUserJwtClaimsDecorator<TUser>> decorators) : base(decorators)
{
}
}