using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; namespace Api.SeaHavenIndustries.Infrastructure { public static class JwtAuthenticationRegistration { /// /// Registers bearer-token authentication as the default scheme. Program.cs and the /// behavior tests both compose authentication through this method so the token /// rules under test are the rules that run. /// public static IServiceCollection AddSeaHavenJwtAuthentication(this IServiceCollection services, IConfiguration configuration) { services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.SaveToken = true; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidateAudience = true, ValidAudience = configuration["JWT:ValidAudience"], ValidIssuer = configuration["JWT:ValidIssuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( configuration["JWT:Secret"] ?? throw new InvalidOperationException("JWT:Secret configuration is required"))) }; options.Events = new JwtBearerEvents { OnTokenValidated = RejectEndedSessionAsync }; }); return services; } /// /// Refuses a correctly signed token whose session stamp no longer matches the /// account: the password was reset or changed, or the account was deactivated or /// deleted, after the token was issued. A token without a stamp is refused too. /// private static async Task RejectEndedSessionAsync(TokenValidatedContext context) { var userId = context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier); var claimValue = context.Principal?.FindFirstValue(SeaHavenClaimTypes.SessionStamp); var sessions = context.HttpContext.RequestServices.GetRequiredService(); if (string.IsNullOrEmpty(userId) || !await sessions.IsCurrentAsync(userId, claimValue, context.HttpContext.RequestAborted)) { // The handler logs this text; it names no account, token or stamp. context.Fail("The session has ended."); } } } }