Files
MilkyShots/Lactose/Authorization/RefreshTokenAuthorization.cs

80 lines
3.0 KiB
C#

using Lactose.Repositories;
using Lactose.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
namespace Lactose.Authorization;
/*
* Technical note:
*
* The RefreshTokenTransformation is used to add a claim to the user's claims principal that indicates if
* the user's login is still valid. while UnexpiredLoginRequirement is used to check the claim.
*
* More Claim Transformation and Requirements can be added with IClaimsTransformation IAuthorizationRequirement and
* added to Program.cs
*/
/// <summary>
/// Authorization requirement that checks whether the user's login (refresh token) is still valid.
/// </summary>
public class UnexpiredLoginRequirement : IAuthorizationRequirement {
/// <summary>
/// Handles the <see cref="UnexpiredLoginRequirement"/> by checking the login validity claim.
/// </summary>
public class RefreshTokenValidityHandler : AuthorizationHandler<UnexpiredLoginRequirement> {
/// <inheritdoc />
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
UnexpiredLoginRequirement requirement
) {
Claim? firstOrDefault
= context.User.Claims.FirstOrDefault(claim => claim.Type == RefreshTokenTransformation.ClaimType);
if (firstOrDefault is not { Value: "true" }) {
context.Fail();
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}
/// <summary>
/// Claims transformation that adds a login validity claim based on the user's refresh token status.
/// </summary>
public class RefreshTokenTransformation(
IUserRepository userRepository,
LactoseAuthService authService
) : IClaimsTransformation
{
/// <summary>
/// The claim type used to indicate login validity.
/// </summary>
public const string ClaimType = "LoginValid";
/// <inheritdoc />
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
if (principal.HasClaim(claim => claim.Type == ClaimType)) {
foreach (ClaimsIdentity identity in principal.Identities) {
if (identity.HasClaim(claim => claim.Type == ClaimType)) {
identity.RemoveClaim(identity.FindFirst(ClaimType)!);
}
}
}
var userData = authService.GetUserData(principal);
if (userData == null) return Task.FromResult(principal);
var user = userRepository.Find(userData.Id);
if (user == null) return Task.FromResult(principal);
if (string.IsNullOrEmpty(user.RefreshToken) || user.RefreshTokenExpires == null || user.RefreshTokenExpires < DateTime.UtcNow) return Task.FromResult(principal);
claimsIdentity.AddClaim(new Claim(ClaimType, "true", ClaimValueTypes.Boolean));
principal.AddIdentity(claimsIdentity);
return Task.FromResult(principal);
}
}