feat: allow users to logout

This commit is contained in:
Stevan Freeborn
2026-03-09 05:58:04 -05:00
parent c8505081a5
commit 091fa6f01c
6 changed files with 225 additions and 80 deletions
+1
View File
@@ -9,6 +9,7 @@ internal static class AuthExtensions
var authGroup = app.MapGroup(RouteGroupPrefix);
authGroup.MapLoginEndpoint();
authGroup.MapLogoutEndpoint();
authGroup.MapRefreshEndpoint()
.RequireAuthorization(Schemes.AllowExpiredTokens);
+38
View File
@@ -0,0 +1,38 @@
internal static class Endpoint
{
private const string Route = "/logout";
public static RouteHandlerBuilder MapLogoutEndpoint(this RouteGroupBuilder groupBuilder)
{
return groupBuilder.MapPost(Route, HandleAsync);
}
private static async Task<IResult> HandleAsync(
HttpContext httpContext,
[FromServices] AppDbContext appDbContext,
CancellationToken ct
)
{
var userId = httpContext.GetUserId();
var refreshToken = httpContext.GetRefreshTokenFromCookie();
var user = await appDbContext.Users
.Include(u => u.RefreshTokens
.Where(r => r.Token == refreshToken)
)
.Where(u => u.Id == userId)
.SingleOrDefaultAsync(ct);
if (user is null || user.RefreshTokens.Any() is false)
{
return Results.Unauthorized();
}
user.RefreshTokens.First().Revoke();
await appDbContext.SaveChangesAsync(ct);
httpContext.ExpireRefreshTokenCookie();
return Results.NoContent();
}
}
@@ -25,6 +25,21 @@ internal static class HttpContextExtensions
);
}
public static void ExpireRefreshTokenCookie(this HttpContext context)
{
context.Response.Cookies.Append(
RefreshTokenCookieName,
string.Empty,
new CookieOptions
{
HttpOnly = true,
Expires = DateTimeOffset.MinValue,
SameSite = SameSiteMode.Strict,
Secure = true
}
);
}
public static string GetRefreshTokenFromCookie(this HttpContext context)
{
return context.Request.Cookies[RefreshTokenCookieName] ?? string.Empty;