using System.Security.Claims; using System.Text.Encodings.Web; using Features.Users.Services; using Configuration; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Shared.Contracts; namespace API.Core.Authentication; /// /// Custom ASP.NET Core authentication handler that validates bearer JWT access tokens /// supplied in the Authorization header against the JWT authentication scheme. /// public class JwtAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ITokenInfrastructure tokenInfrastructure, IConfiguration configuration ) : AuthenticationHandler(options, logger, encoder) { /// /// Validates the incoming request's bearer JWT access token and produces an authentication result. /// /// /// The signing secret is resolved first from the ACCESS_TOKEN_SECRET environment variable. /// Fails if the secret is not configured, the Authorization header is /// missing or not Bearer-scheme, or the token fails validation. /// protected override async Task HandleAuthenticateAsync() { string? secret = configuration[ConfigurationKeys.AccessTokenSecret]; 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}"); } } /// /// Writes a JSON 401 Unauthorized response when authentication fails or is required but not supplied. /// 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); } } /// /// Options for the JWT authentication scheme handled by . /// public class JwtAuthenticationOptions : AuthenticationSchemeOptions;