using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using FictionArchive.Service.Shared.Models.Authentication; namespace FictionArchive.Service.Shared.Extensions; public static class AuthenticationExtensions { public static IServiceCollection AddOidcAuthentication(this IServiceCollection services, IConfiguration configuration) { var oidcConfig = configuration.GetSection("OIDC").Get(); if (oidcConfig == null) { throw new InvalidOperationException("OIDC configuration is required but not found in app settings"); } services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = oidcConfig.Authority; options.Audience = oidcConfig.Audience; options.RequireHttpsMetadata = !string.IsNullOrEmpty(oidcConfig.Authority) && oidcConfig.Authority.StartsWith("https://"); options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = oidcConfig.ValidateIssuer, ValidateAudience = oidcConfig.ValidateAudience, ValidateLifetime = oidcConfig.ValidateLifetime, ValidateIssuerSigningKey = oidcConfig.ValidateIssuerSigningKey, ClockSkew = TimeSpan.FromMinutes(5) }; }); return services; } public static IServiceCollection AddOidcCookieAuthentication(this IServiceCollection services, IConfiguration configuration, string cookieName = "fa_session") { var oidcConfig = configuration.GetSection("OIDC").Get(); if (oidcConfig == null) { throw new InvalidOperationException("OIDC configuration is required but not found in app settings"); } services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = oidcConfig.Authority; options.Audience = oidcConfig.Audience; options.RequireHttpsMetadata = !string.IsNullOrEmpty(oidcConfig.Authority) && oidcConfig.Authority.StartsWith("https://"); options.Events = new JwtBearerEvents { OnMessageReceived = context => { // Try to get token from cookie first, then from Authorization header if (context.Request.Cookies.ContainsKey(cookieName)) { context.Token = context.Request.Cookies[cookieName]; } return Task.CompletedTask; } }; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = oidcConfig.ValidateIssuer, ValidateAudience = oidcConfig.ValidateAudience, ValidateLifetime = oidcConfig.ValidateLifetime, ValidateIssuerSigningKey = oidcConfig.ValidateIssuerSigningKey, ClockSkew = TimeSpan.FromMinutes(5) }; }); return services; } public static IServiceCollection AddFictionArchiveAuthorization(this IServiceCollection services) { services.AddAuthorizationBuilder() .AddPolicy("Admin", policy => policy.RequireRole("admin")) .AddPolicy("User", policy => policy.RequireAuthenticatedUser()); return services; } }