using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using JobTrackerApi.Models; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; namespace JobTrackerApi.Services; public interface ITokenService { Task CreateAccessTokenAsync(ApplicationUser user, CancellationToken cancellationToken = default); } public sealed class TokenService : ITokenService { private readonly IConfiguration _cfg; private readonly UserManager _users; public TokenService(IConfiguration cfg, UserManager users) { _cfg = cfg; _users = users; } public async Task CreateAccessTokenAsync(ApplicationUser user, CancellationToken cancellationToken = default) { var jwtKey = (_cfg["Auth:JwtKey"] ?? "").Trim(); if (string.IsNullOrWhiteSpace(jwtKey)) { throw new InvalidOperationException("Auth:JwtKey is not configured."); } var issuer = (_cfg["Auth:JwtIssuer"] ?? "JobTrackerApi").Trim(); var audience = (_cfg["Auth:JwtAudience"] ?? "job-tracker-ui").Trim(); var minutes = _cfg.GetValue("Auth:JwtExpiresMinutes", 60 * 12); if (minutes < 5) minutes = 5; if (minutes > 60 * 24 * 30) minutes = 60 * 24 * 30; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var roles = await _users.GetRolesAsync(user); var claims = new List { new(ClaimTypes.NameIdentifier, user.Id), }; if (!string.IsNullOrWhiteSpace(user.Email)) claims.Add(new Claim(ClaimTypes.Email, user.Email)); if (!string.IsNullOrWhiteSpace(user.UserName)) claims.Add(new Claim(ClaimTypes.Name, user.UserName)); foreach (var r in roles) claims.Add(new Claim(ClaimTypes.Role, r)); var now = DateTime.UtcNow; var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, notBefore: now.AddSeconds(-5), expires: now.AddMinutes(minutes), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } }