using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Principal; using CodeLiturgy.Data.Application.Users; using Microsoft.Extensions.Options; using static CodeLiturgy.Data.Auth.Context.Users.Constants; namespace CodeLiturgy.Data.Auth.Context.Users; public class JwtFactory : IJwtFactory { private readonly IJwtTokenHandler _jwtTokenHandler; private readonly JwtIssuerOptions _jwtOptions; public JwtFactory(IJwtTokenHandler jwtTokenHandler, IOptions jwtOptions) { _jwtTokenHandler = jwtTokenHandler; _jwtOptions = jwtOptions.Value; ThrowIfInvalidOptions(_jwtOptions); } public async Task GenerateEncodedToken(string id, string userName) { var identity = GenerateClaimsIdentity(id, userName); var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, userName), new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()), new Claim(JwtRegisteredClaimNames.Aud, _jwtOptions.Audience), new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64), identity.FindFirst(JwtClaimIdentifiers.Rol), identity.FindFirst(JwtClaimIdentifiers.Id) }; // Create the JWT security token and encode it. var jwt = new JwtSecurityToken( _jwtOptions.Issuer, _jwtOptions.Audience, claims, _jwtOptions.NotBefore, _jwtOptions.Expiration, _jwtOptions.SigningCredentials); return new AccessToken(_jwtTokenHandler.WriteToken(jwt), (int)_jwtOptions.ValidFor.TotalSeconds); } private static ClaimsIdentity GenerateClaimsIdentity(string id, string userName) { return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[] { new Claim(JwtClaimIdentifiers.Id, id), new Claim(JwtClaimIdentifiers.Rol, JwtClaims.ApiAccess) }); } /// Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC). private static long ToUnixEpochDate(DateTime date) => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)) .TotalSeconds); private static void ThrowIfInvalidOptions(JwtIssuerOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); if (options.ValidFor <= TimeSpan.Zero) { throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor)); } if (options.SigningCredentials == null) { throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials)); } if (options.JtiGenerator == null) { throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator)); } } }