mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
* Remove restating comments in backend * Remove restating comments in frontend stories * Remove restating comments in pipeline * Remove restating comments in pipeline scripts * Restore location-clear comment in BreweryRepository * Remove stale/restating comments found in second pass * Restore mock curated-data branch comment in main.cc * Remove more restating/filler comments in backend * Document account-management endpoints, compose files, and env generator * Squashed commit of the following: commit a8a5b8abd9969859c695092fae6990d03b2135ed Author: Aaron Po <aaron.po97@gmail.com> Date: Sun Aug 23 23:53:56 2026 -0400 Add reverse geocoding for brewery street addresses (#245) * Add geocoding to seed application, remove postal codes from generator Update comments * Update pipeline class diagram for OpenAI generator * Update pipeline activity diagram for brewery coordinates and OpenAI backend * Add mailpit, production, db-only, and minimal environments to deployment diagram * Fix authentication flow diagram for Identity migration * Add account-management flows to authentication diagram * Fix database schema diagram note referencing removed AuthRepository
86 lines
3.4 KiB
C#
86 lines
3.4 KiB
C#
using System.Security.Claims;
|
|
using System.Text.Encodings.Web;
|
|
using Infrastructure.Jwt;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Extensions.Primitives;
|
|
using Shared.Contracts;
|
|
|
|
namespace API.Core.Authentication;
|
|
|
|
/// <summary>
|
|
/// Custom ASP.NET Core authentication handler that validates bearer JWT access tokens
|
|
/// supplied in the <c>Authorization</c> header against the <c>JWT</c> authentication scheme.
|
|
/// </summary>
|
|
public class JwtAuthenticationHandler(
|
|
IOptionsMonitor<JwtAuthenticationOptions> options,
|
|
ILoggerFactory logger,
|
|
UrlEncoder encoder,
|
|
ITokenInfrastructure tokenInfrastructure,
|
|
IConfiguration configuration
|
|
) : AuthenticationHandler<JwtAuthenticationOptions>(options, logger, encoder)
|
|
{
|
|
/// <summary>
|
|
/// Validates the incoming request's bearer JWT access token and produces an authentication result.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The signing secret is resolved first from the <c>ACCESS_TOKEN_SECRET</c> environment variable, falling
|
|
/// back to the <c>Jwt:SecretKey</c> configuration value, to stay consistent with the secret source used
|
|
/// when tokens are issued. Fails if the secret is not configured, the <c>Authorization</c> header is
|
|
/// missing or not <c>Bearer</c>-scheme, or the token fails validation.
|
|
/// </remarks>
|
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
|
{
|
|
// Use the same access-token secret source as TokenService to avoid mismatched validation.
|
|
string? secret = configuration["ACCESS_TOKEN_SECRET"];
|
|
if (string.IsNullOrWhiteSpace(secret))
|
|
secret = configuration["Jwt:SecretKey"];
|
|
|
|
if (string.IsNullOrWhiteSpace(secret))
|
|
return AuthenticateResult.Fail("JWT secret is not configured");
|
|
|
|
if (!Request.Headers.TryGetValue("Authorization", out StringValues authHeaderValue))
|
|
return AuthenticateResult.Fail("Authorization header is missing");
|
|
|
|
string authHeader = authHeaderValue.ToString();
|
|
if (!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
|
return AuthenticateResult.Fail("Invalid authorization header format");
|
|
|
|
string token = authHeader.Substring("Bearer ".Length).Trim();
|
|
|
|
try
|
|
{
|
|
ClaimsPrincipal claimsPrincipal = await tokenInfrastructure.ValidateJwtAsync(
|
|
token,
|
|
secret
|
|
);
|
|
AuthenticationTicket ticket = new(claimsPrincipal, Scheme.Name);
|
|
return AuthenticateResult.Success(ticket);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return AuthenticateResult.Fail($"Token validation failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a JSON 401 Unauthorized response when authentication fails or is required but not supplied.
|
|
/// </summary>
|
|
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
|
|
{
|
|
Response.ContentType = "application/json";
|
|
Response.StatusCode = 401;
|
|
|
|
ResponseBody response = new()
|
|
{
|
|
Message = "Unauthorized: Invalid or missing authentication token",
|
|
};
|
|
await Response.WriteAsJsonAsync(response);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Options for the <c>JWT</c> authentication scheme handled by <see cref="JwtAuthenticationHandler" />.
|
|
/// </summary>
|
|
public class JwtAuthenticationOptions : AuthenticationSchemeOptions;
|