mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-04-06 02:19:05 +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(
|
public class AuthController(
|
||||||
IRegisterService registerService,
|
IRegisterService registerService,
|
||||||
ILoginService loginService,
|
ILoginService loginService,
|
||||||
IConfirmationService confirmationService)
|
IConfirmationService confirmationService
|
||||||
: ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<ActionResult<UserAccount>> Register(
|
public async Task<ActionResult<UserAccount>> Register(
|
||||||
@@ -69,13 +69,16 @@ namespace API.Core.Controllers
|
|||||||
public async Task<ActionResult> Confirm([FromQuery] string token)
|
public async Task<ActionResult> Confirm([FromQuery] string token)
|
||||||
{
|
{
|
||||||
var rtn = await confirmationService.ConfirmUserAsync(token);
|
var rtn = await confirmationService.ConfirmUserAsync(token);
|
||||||
return Ok(new ResponseBody<ConfirmationPayload>
|
return Ok(
|
||||||
{
|
new ResponseBody<ConfirmationPayload>
|
||||||
Message = "User with ID " + rtn.userId + " is confirmed.",
|
{
|
||||||
Payload = new ConfirmationPayload(
|
Message = "User with ID " + rtn.userId + " is confirmed.",
|
||||||
rtn.userId, rtn.confirmedAt
|
Payload = new ConfirmationPayload(
|
||||||
)
|
rtn.userId,
|
||||||
});
|
rtn.confirmedAt
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ using Infrastructure.Repository.UserAccount;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
using Service.Auth;
|
using Service.Auth;
|
||||||
using Service.UserManagement.User;
|
|
||||||
using Service.Emails;
|
using Service.Emails;
|
||||||
|
using Service.UserManagement.User;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
namespace Infrastructure.Jwt;
|
namespace Infrastructure.Jwt;
|
||||||
|
|
||||||
public interface ITokenInfrastructure
|
public interface ITokenInfrastructure
|
||||||
@@ -8,4 +10,6 @@ public interface ITokenInfrastructure
|
|||||||
DateTime expiry,
|
DateTime expiry,
|
||||||
string secret
|
string secret
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
Task<ClaimsPrincipal> ValidateJwtAsync(string token, string secret);
|
||||||
|
}
|
||||||
@@ -16,4 +16,8 @@
|
|||||||
Version="8.2.1"
|
Version="8.2.1"
|
||||||
/>
|
/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Domain.Exceptions\Domain.Exceptions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Text;
|
|||||||
using Microsoft.IdentityModel.JsonWebTokens;
|
using Microsoft.IdentityModel.JsonWebTokens;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
using JwtRegisteredClaimNames = System.IdentityModel.Tokens.Jwt.JwtRegisteredClaimNames;
|
||||||
|
using Domain.Exceptions;
|
||||||
|
|
||||||
namespace Infrastructure.Jwt;
|
namespace Infrastructure.Jwt;
|
||||||
|
|
||||||
@@ -16,16 +17,19 @@ public class JwtInfrastructure : ITokenInfrastructure
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
var handler = new JsonWebTokenHandler();
|
var handler = new JsonWebTokenHandler();
|
||||||
|
var key = Encoding.UTF8.GetBytes(secret);
|
||||||
var key = Encoding.UTF8.GetBytes(
|
|
||||||
secret ?? throw new InvalidOperationException("secret not set")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Base claims (always present)
|
|
||||||
var claims = new List<Claim>
|
var claims = new List<Claim>
|
||||||
{
|
{
|
||||||
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||||
new(JwtRegisteredClaimNames.UniqueName, username),
|
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()),
|
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,4 +45,36 @@ public class JwtInfrastructure : ITokenInfrastructure
|
|||||||
|
|
||||||
return handler.CreateToken(tokenDescriptor);
|
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);
|
Task<ConfirmationServiceReturn> ConfirmUserAsync(string confirmationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ConfirmationService(IAuthRepository authRepository)
|
||||||
public class ConfirmationService(IAuthRepository authRepository) : IConfirmationService
|
: IConfirmationService
|
||||||
{
|
{
|
||||||
|
public async Task<ConfirmationServiceReturn> ConfirmUserAsync(
|
||||||
public async Task<ConfirmationServiceReturn> ConfirmUserAsync(string confirmationToken)
|
string confirmationToken
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return new ConfirmationServiceReturn(DateTime.Now, Guid.NewGuid());
|
return new ConfirmationServiceReturn(DateTime.Now, Guid.NewGuid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,21 @@ using Infrastructure.Jwt;
|
|||||||
|
|
||||||
namespace Service.Auth;
|
namespace Service.Auth;
|
||||||
|
|
||||||
|
public enum TokenType
|
||||||
|
{
|
||||||
|
AccessToken,
|
||||||
|
RefreshToken,
|
||||||
|
ConfirmationToken,
|
||||||
|
}
|
||||||
|
|
||||||
public interface ITokenService
|
public interface ITokenService
|
||||||
{
|
{
|
||||||
public string GenerateAccessToken(UserAccount user);
|
public string GenerateAccessToken(UserAccount user);
|
||||||
public string GenerateRefreshToken(UserAccount user);
|
public string GenerateRefreshToken(UserAccount user);
|
||||||
public string GenerateConfirmationToken(UserAccount user);
|
public string GenerateConfirmationToken(UserAccount user);
|
||||||
|
|
||||||
|
public string GenerateToken<T>(UserAccount user)
|
||||||
|
where T : struct, Enum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TokenServiceExpirationHours
|
public static class TokenServiceExpirationHours
|
||||||
@@ -76,4 +86,28 @@ public class TokenService(ITokenInfrastructure tokenInfrastructure)
|
|||||||
_confirmationTokenSecret
|
_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