mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-04-05 18:09:04 +00:00
Refactor authentication services and implement JWT validation logic
This commit is contained in:
@@ -11,8 +11,8 @@ namespace API.Core.Controllers
|
||||
public class AuthController(
|
||||
IRegisterService registerService,
|
||||
ILoginService loginService,
|
||||
IConfirmationService confirmationService)
|
||||
: ControllerBase
|
||||
IConfirmationService confirmationService
|
||||
) : ControllerBase
|
||||
{
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<UserAccount>> Register(
|
||||
@@ -69,13 +69,16 @@ namespace API.Core.Controllers
|
||||
public async Task<ActionResult> Confirm([FromQuery] string token)
|
||||
{
|
||||
var rtn = await confirmationService.ConfirmUserAsync(token);
|
||||
return Ok(new ResponseBody<ConfirmationPayload>
|
||||
{
|
||||
Message = "User with ID " + rtn.userId + " is confirmed.",
|
||||
Payload = new ConfirmationPayload(
|
||||
rtn.userId, rtn.confirmedAt
|
||||
)
|
||||
});
|
||||
return Ok(
|
||||
new ResponseBody<ConfirmationPayload>
|
||||
{
|
||||
Message = "User with ID " + rtn.userId + " is confirmed.",
|
||||
Payload = new ConfirmationPayload(
|
||||
rtn.userId,
|
||||
rtn.confirmedAt
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ using Infrastructure.Repository.UserAccount;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Service.Auth;
|
||||
using Service.UserManagement.User;
|
||||
using Service.Emails;
|
||||
using Service.UserManagement.User;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Infrastructure.Jwt;
|
||||
|
||||
public interface ITokenInfrastructure
|
||||
@@ -8,4 +10,6 @@ public interface ITokenInfrastructure
|
||||
DateTime expiry,
|
||||
string secret
|
||||
);
|
||||
}
|
||||
|
||||
Task<ClaimsPrincipal> ValidateJwtAsync(string token, string secret);
|
||||
}
|
||||
@@ -16,4 +16,8 @@
|
||||
Version="8.2.1"
|
||||
/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain.Exceptions\Domain.Exceptions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
||||
using Domain.Exceptions;
|
||||
|
||||
namespace Infrastructure.Jwt;
|
||||
|
||||
@@ -16,16 +17,19 @@ public class JwtInfrastructure : ITokenInfrastructure
|
||||
)
|
||||
{
|
||||
var handler = new JsonWebTokenHandler();
|
||||
|
||||
var key = Encoding.UTF8.GetBytes(
|
||||
secret ?? throw new InvalidOperationException("secret not set")
|
||||
);
|
||||
|
||||
// Base claims (always present)
|
||||
var key = Encoding.UTF8.GetBytes(secret);
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, username),
|
||||
new(
|
||||
JwtRegisteredClaimNames.Iat,
|
||||
DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()
|
||||
),
|
||||
new(
|
||||
JwtRegisteredClaimNames.Exp,
|
||||
new DateTimeOffset(expiry).ToUnixTimeSeconds().ToString()
|
||||
),
|
||||
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
};
|
||||
|
||||
@@ -41,4 +45,36 @@ public class JwtInfrastructure : ITokenInfrastructure
|
||||
|
||||
return handler.CreateToken(tokenDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<ClaimsPrincipal> ValidateJwtAsync(
|
||||
string token,
|
||||
string secret
|
||||
)
|
||||
{
|
||||
var handler = new JsonWebTokenHandler();
|
||||
var keyBytes = Encoding.UTF8.GetBytes(
|
||||
secret
|
||||
);
|
||||
var parameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false,
|
||||
ValidateLifetime = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(keyBytes),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var result = await handler.ValidateTokenAsync(token, parameters);
|
||||
if (!result.IsValid || result.ClaimsIdentity == null)
|
||||
throw new UnauthorizedAccessException();
|
||||
|
||||
return new ClaimsPrincipal(result.ClaimsIdentity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new UnauthorizedException("Invalid token");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,13 @@ public interface IConfirmationService
|
||||
Task<ConfirmationServiceReturn> ConfirmUserAsync(string confirmationToken);
|
||||
}
|
||||
|
||||
|
||||
public class ConfirmationService(IAuthRepository authRepository) : IConfirmationService
|
||||
public class ConfirmationService(IAuthRepository authRepository)
|
||||
: IConfirmationService
|
||||
{
|
||||
|
||||
public async Task<ConfirmationServiceReturn> ConfirmUserAsync(string confirmationToken)
|
||||
public async Task<ConfirmationServiceReturn> ConfirmUserAsync(
|
||||
string confirmationToken
|
||||
)
|
||||
{
|
||||
return new ConfirmationServiceReturn(DateTime.Now, Guid.NewGuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,21 @@ using Infrastructure.Jwt;
|
||||
|
||||
namespace Service.Auth;
|
||||
|
||||
public enum TokenType
|
||||
{
|
||||
AccessToken,
|
||||
RefreshToken,
|
||||
ConfirmationToken,
|
||||
}
|
||||
|
||||
public interface ITokenService
|
||||
{
|
||||
public string GenerateAccessToken(UserAccount user);
|
||||
public string GenerateRefreshToken(UserAccount user);
|
||||
public string GenerateConfirmationToken(UserAccount user);
|
||||
|
||||
public string GenerateToken<T>(UserAccount user)
|
||||
where T : struct, Enum;
|
||||
}
|
||||
|
||||
public static class TokenServiceExpirationHours
|
||||
@@ -76,4 +86,28 @@ public class TokenService(ITokenInfrastructure tokenInfrastructure)
|
||||
_confirmationTokenSecret
|
||||
);
|
||||
}
|
||||
|
||||
public string GenerateToken<T>(UserAccount userAccount)
|
||||
where T : struct, Enum
|
||||
{
|
||||
var tokenType = typeof(T);
|
||||
if (tokenType == typeof(TokenType))
|
||||
{
|
||||
var tokenTypeValue = (TokenType)
|
||||
Enum.Parse(tokenType, typeof(T).Name);
|
||||
return tokenTypeValue switch
|
||||
{
|
||||
TokenType.AccessToken => GenerateAccessToken(userAccount),
|
||||
TokenType.RefreshToken => GenerateRefreshToken(userAccount),
|
||||
TokenType.ConfirmationToken => GenerateConfirmationToken(
|
||||
userAccount
|
||||
),
|
||||
_ => throw new InvalidOperationException("Invalid token type"),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid token type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user