feat(infra): move authentication implementations and add ITokenGenerator

This commit is contained in:
Stevan Freeborn
2026-02-02 13:17:59 -06:00
parent 098aa23c62
commit b3bfa64b96
6 changed files with 50 additions and 37 deletions
@@ -1,7 +0,0 @@
namespace FiscalOS.Core.Identity;
public interface IPasswordHasher
{
string Hash(string password);
bool Verify(string providedPassword, string hashedPassword);
}
@@ -1,8 +1,6 @@
using Microsoft.Extensions.Options; namespace FiscalOS.Infra.Authentication;
namespace FiscalOS.API.Login; public sealed record JwtOptions
internal sealed record JwtOptions
{ {
public string Issuer { get; init; } = string.Empty; public string Issuer { get; init; } = string.Empty;
public string Audience { get; init; } = string.Empty; public string Audience { get; init; } = string.Empty;
@@ -10,7 +8,7 @@ internal sealed record JwtOptions
public int ExpiryInMinutes { get; init; } = 5; public int ExpiryInMinutes { get; init; } = 5;
} }
internal sealed record JwtOptionsSetup : IConfigureOptions<JwtOptions> public sealed record JwtOptionsSetup : IConfigureOptions<JwtOptions>
{ {
private const string SectionName = nameof(JwtOptions); private const string SectionName = nameof(JwtOptions);
private readonly IConfiguration _configuration; private readonly IConfiguration _configuration;
@@ -1,4 +1,4 @@
namespace FiscalOS.Infra.Identity; namespace FiscalOS.Infra.Authentication;
public sealed class PasswordHasher : IPasswordHasher public sealed class PasswordHasher : IPasswordHasher
{ {
@@ -17,6 +17,11 @@ public sealed class PasswordHasher : IPasswordHasher
return new(); return new();
} }
public static PasswordHasher From(IServiceProvider serviceProvider)
{
return new();
}
public string Hash(string password) public string Hash(string password)
{ {
var salt = RandomNumberGenerator.GetBytes(SaltSize); var salt = RandomNumberGenerator.GetBytes(SaltSize);
@@ -1,30 +1,41 @@
using System.IdentityModel.Tokens.Jwt; namespace FiscalOS.Infra.Authentication;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using FiscalOS.Core.Identity; public sealed class TokenGenerator : ITokenGenerator
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace FiscalOS.API.Login;
internal sealed class TokenService(
TimeProvider timeProvider,
IOptions<JwtOptions> jwtOptions
)
{ {
private const int RefreshTokenExpiryInHours = 12; private const int RefreshTokenExpiryInHours = 12;
private readonly JwtOptions _jwtOptions = jwtOptions.Value; private readonly IOptions<JwtOptions> _jwtOptions;
private readonly TimeProvider _timeProvider = timeProvider; private readonly TimeProvider _timeProvider;
private TokenGenerator(
TimeProvider timeProvider,
IOptions<JwtOptions> jwtOptions
)
{
_timeProvider = timeProvider;
_jwtOptions = jwtOptions;
}
public static TokenGenerator From(IServiceProvider serviceProvider)
{
var timeProvider = serviceProvider.GetRequiredService<TimeProvider>();
var jwtOptions = serviceProvider.GetRequiredService<IOptions<JwtOptions>>();
return new(timeProvider, jwtOptions);
}
public static TokenGenerator From(
TimeProvider timeProvider,
IOptions<JwtOptions> jwtOptions
)
{
return new(timeProvider, jwtOptions);
}
public string GenerateAccessToken(User user) public string GenerateAccessToken(User user)
{ {
var tokenHandler = new JwtSecurityTokenHandler(); var tokenHandler = new JwtSecurityTokenHandler();
var secretKeyBytes = Encoding.UTF8.GetBytes(_jwtOptions.Secret); var secretKeyBytes = Encoding.UTF8.GetBytes(_jwtOptions.Value.Secret);
var issuedAt = _timeProvider.GetUtcNow(); var issuedAt = _timeProvider.GetUtcNow();
var expiresAt = issuedAt.AddMinutes(_jwtOptions.ExpiryInMinutes); var expiresAt = issuedAt.AddMinutes(_jwtOptions.Value.ExpiryInMinutes);
List<Claim> claims = [ List<Claim> claims = [
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
@@ -35,8 +46,8 @@ internal sealed class TokenService(
Subject = new(claims), Subject = new(claims),
IssuedAt = issuedAt.UtcDateTime, IssuedAt = issuedAt.UtcDateTime,
Expires = expiresAt.UtcDateTime, Expires = expiresAt.UtcDateTime,
Issuer = _jwtOptions.Issuer, Issuer = _jwtOptions.Value.Issuer,
Audience = _jwtOptions.Audience, Audience = _jwtOptions.Value.Audience,
SigningCredentials = new( SigningCredentials = new(
new SymmetricSecurityKey(secretKeyBytes), new SymmetricSecurityKey(secretKeyBytes),
SecurityAlgorithms.HmacSha256Signature SecurityAlgorithms.HmacSha256Signature
@@ -64,4 +75,4 @@ internal sealed class TokenService(
var token = Convert.ToBase64String(randomBytes); var token = Convert.ToBase64String(randomBytes);
return token; return token;
} }
} }
+1
View File
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.Extensions.Hosting" /> <PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup> </ItemGroup>
+7 -2
View File
@@ -1,8 +1,12 @@
global using System.IdentityModel.Tokens.Jwt;
global using System.Security.Claims;
global using System.Security.Cryptography; global using System.Security.Cryptography;
global using System.Text;
global using FiscalOS.Core.Authentication;
global using FiscalOS.Core.Identity; global using FiscalOS.Core.Identity;
global using FiscalOS.Infra.Authentication;
global using FiscalOS.Infra.Data; global using FiscalOS.Infra.Data;
global using FiscalOS.Infra.Identity;
global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.Diagnostics; global using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -10,4 +14,5 @@ global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options; global using Microsoft.Extensions.Options;
global using Microsoft.IdentityModel.Tokens;