using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using OpenIddict.Validation.AspNetCore; using OpenIddict.Validation.SystemNetHttp; using StackExchange.Redis; using Ablera.Serdica.Authentication.Models; using Ablera.Serdica.Authentication.Models.Oidc; using Ablera.Serdica.Authentication.Utilities; using Microsoft.AspNetCore.DataProtection; using Ablera.Serdica.Authentication.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Ablera.Serdica.Authentication.Constants; using OpenIddict.Client; using OpenIddict.Validation; using System.Linq; using System.Collections.Generic; using System.Security.Claims; using Microsoft.AspNetCore.Identity; using System.Security.Principal; using OpenIddict.Client.AspNetCore; using Microsoft.AspNetCore.Authorization; using Ablera.Serdica.DependencyInjection; using static Ablera.Serdica.Authentication.Constants.ConstantsClass; using static OpenIddict.Abstractions.OpenIddictConstants; using System.IdentityModel.Tokens.Jwt; using static OpenIddict.Client.OpenIddictClientEvents; namespace Ablera.Serdica.DependencyInjection; public sealed class AcceptAnyIssuer : IOpenIddictClientHandler { public ValueTask HandleAsync(HandleConfigurationResponseContext ctx) { // Short-circuit the built-in ValidateIssuer handler. ctx.SkipRequest(); return default; } } public static class JwtBearerWithSessionAuthenticationExtensions { public static IServiceCollection AddDataProtection(this IServiceCollection services, IConfiguration configuration) { //------------------------------------------------------------------ // 1) read configuration //------------------------------------------------------------------ var redisConfiguration = RedisConfigurationGetter.GetRedisConfiguration(configuration); var multiplexer = ConnectionMultiplexer.Connect(redisConfiguration); services.AddSingleton(multiplexer); //------------------------------------------------------------------ // 2) Data-Protection (encrypt/sign cookies) – keys stored in Redis //------------------------------------------------------------------ var xmlRepo = new RedisAndFileSystemXmlRepository( multiplexer.GetDatabase(), RedisKeyPrefixKey); services.AddDataProtection() .SetApplicationName(DataProtectionApplicationName) .PersistKeysToStackExchangeRedis(multiplexer, RedisKeyPrefixKey) .AddKeyManagementOptions(o => o.XmlRepository = xmlRepo) .SetDefaultKeyLifetime(TimeSpan.FromDays(30)); return services; } public static IServiceCollection AddMicroserviceAuthentication( this IServiceCollection services, IConfiguration cfg, IHostEnvironment env) { // --------------------------------------------------------------------- // 1) Read and validate the OIDC client settings // --------------------------------------------------------------------- var oidc = cfg.GetSection(nameof(OidcValidation)).Get() ?? throw new InvalidOperationException($"{nameof(OidcValidation)} section is missing."); if (string.IsNullOrWhiteSpace(oidc.EncryptionKey)) throw new InvalidOperationException($"{nameof(oidc.EncryptionKey)} is not defined."); // Issuer value found in the `iss` claim of the tokens (HTTPS as issued by the IdP) var issuerUrl = new Uri(oidc.IssuerUrl ?? throw new InvalidOperationException($"{nameof(oidc.IssuerUrl)} is not defined.")); services.Configure(cfg.GetSection(nameof(OidcValidation))); services .AddDataProtection(cfg) .AddOpenIddict() .AddValidation(opt => { opt.UseSystemNetHttp(); opt.UseAspNetCore(); opt.SetIssuer(issuerUrl); if (!string.IsNullOrWhiteSpace(oidc.ConfigurationUrl)) { opt.Configure(x => { x.ConfigurationEndpoint = new Uri(oidc.ConfigurationUrl); }); } opt.AddEncryptionKey( new SymmetricSecurityKey(Convert.FromBase64String(oidc.EncryptionKey))); }); services.AddAuthorization(options => options.FallbackPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build()) .AddAuthentication(options => { options.DefaultScheme = ConstantsClass.AuthenticationScheme; options.DefaultChallengeScheme = ConstantsClass.AuthenticationScheme; }) .AddScheme( ConstantsClass.AuthenticationScheme, _ => { }); return services; } }