using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using FlatRender.IdentitySvc.Application.Services.Interfaces; using FlatRender.IdentitySvc.Domain.Entities; using Microsoft.IdentityModel.Tokens; namespace FlatRender.IdentitySvc.Application.Services; public class TokenService(IConfiguration config) : ITokenService { private readonly string _secret = config["Jwt:Secret"] ?? throw new InvalidOperationException("Jwt:Secret not configured"); private readonly string _issuer = config["Jwt:Issuer"] ?? "flatrender-identity"; private readonly string _audience = config["Jwt:Audience"] ?? "flatrender"; private readonly int _accessTokenMinutes = int.Parse(config["Jwt:AccessTokenMinutes"] ?? "15"); public string GenerateAccessToken(User user, Tenant tenant) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); // Role claim drives [Authorize(Roles = "...")] in the other services. var role = user.IsAdmin ? "Admin" : user.IsTenantAdmin ? "TenantAdmin" : "User"; var claims = new List { new(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new("tenant_id", tenant.Id.ToString()), new("tenant_slug", tenant.Slug), new("is_admin", user.IsAdmin.ToString().ToLower()), new("is_tenant_admin", user.IsTenantAdmin.ToString().ToLower()), new("role", role), }; if (!string.IsNullOrEmpty(user.Email)) claims.Add(new(JwtRegisteredClaimNames.Email, user.Email)); var token = new JwtSecurityToken( issuer: _issuer, audience: _audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(_accessTokenMinutes), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } public string GenerateRefreshToken() => Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); public string HashToken(string token) { var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(token)); return Convert.ToHexString(bytes).ToLower(); } public (Guid userId, Guid tenantId, bool isAdmin) ValidateAccessToken(string token) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)); var handler = new JwtSecurityTokenHandler(); var parameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateIssuer = true, ValidIssuer = _issuer, ValidateAudience = true, ValidAudience = _audience, ValidateLifetime = true, }; var principal = handler.ValidateToken(token, parameters, out _); var userId = Guid.Parse(principal.FindFirstValue(JwtRegisteredClaimNames.Sub)!); var tenantId = Guid.Parse(principal.FindFirstValue("tenant_id")!); var isAdmin = bool.Parse(principal.FindFirstValue("is_admin") ?? "false"); return (userId, tenantId, isAdmin); } public string GenerateServiceToken() { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _issuer, audience: _audience, claims: [new("type", "service"), new("service", "identity")], expires: DateTime.UtcNow.AddHours(24), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } }