JwtTokenService.cs 1.81 KB
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using RobotProductionSystem.Api.Domain;

namespace RobotProductionSystem.Api.Services;

public sealed class JwtOptions
{
    public string Issuer { get; set; } = "robot-production-system";
    public string Audience { get; set; } = "robot-production-system-frontend";
    public string Secret { get; set; } = "dev-secret-change-me-dev-secret-change-me";
    public int ExpiresHours { get; set; } = 8;
}

public sealed record IssuedToken(string Token, DateTimeOffset ExpiresAt);

public sealed class JwtTokenService(IOptions<JwtOptions> options)
{
    private readonly JwtOptions _options = options.Value;

    public IssuedToken Issue(User user)
    {
        var expiresAt = DateTimeOffset.UtcNow.AddHours(_options.ExpiresHours <= 0 ? 8 : _options.ExpiresHours);
        var claims = new List<Claim>
        {
            new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
            new("username", user.Username),
            new("name", user.Name),
            new("station", user.Station)
        };
        claims.AddRange(user.Roles.Select(role => new Claim(ClaimTypes.Role, role.Role)));
        claims.AddRange(user.Roles.Select(role => new Claim("roles", role.Role)));

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Secret));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _options.Issuer,
            audience: _options.Audience,
            claims: claims,
            expires: expiresAt.UtcDateTime,
            signingCredentials: credentials);

        return new IssuedToken(new JwtSecurityTokenHandler().WriteToken(token), expiresAt);
    }
}