using System.Security.Claims; using System.Text; using Campaign_Tracker.Server.Authentication; using Campaign_Tracker.Server.Configuration; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); DotEnvConfiguration.Load(builder.Configuration, builder.Environment.ContentRootPath); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); builder.Services.Configure(builder.Configuration.GetSection(KeycloakOptions.SectionName)); builder.Services.AddSingleton(); builder.Services.AddHttpClient(); var allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get() ?? []; builder.Services.AddCors(options => { options.AddPolicy("ConfiguredOrigins", policy => { if (allowedOrigins.Length > 0) { policy.WithOrigins(allowedOrigins) .AllowAnyHeader() .AllowAnyMethod(); } }); }); var keycloakOptions = builder.Configuration .GetSection(KeycloakOptions.SectionName) .Get() ?? new KeycloakOptions(); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Audience = keycloakOptions.TokenAudience; options.RequireHttpsMetadata = !keycloakOptions.DisableHttpsMetadata; if (!string.IsNullOrWhiteSpace(keycloakOptions.MetadataAddress)) { options.MetadataAddress = keycloakOptions.MetadataAddress; } options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = keycloakOptions.TokenIssuer, ValidateAudience = true, ValidAudience = keycloakOptions.TokenAudience, ValidateLifetime = true, NameClaimType = ClaimTypes.Name, RoleClaimType = ClaimTypes.Role, }; if (!string.IsNullOrWhiteSpace(keycloakOptions.TestSigningKey)) { var issuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keycloakOptions.TestSigningKey)); options.TokenValidationParameters.ValidateIssuerSigningKey = true; options.TokenValidationParameters.IssuerSigningKey = issuerSigningKey; options.TokenValidationParameters.IssuerSigningKeys = [issuerSigningKey]; options.Configuration = new OpenIdConnectConfiguration { Issuer = keycloakOptions.TokenIssuer, }; options.Configuration.SigningKeys.Add(issuerSigningKey); } else { options.Authority = keycloakOptions.Authority; } options.Events = new JwtBearerEvents { OnTokenValidated = context => { var auditStore = context.HttpContext.RequestServices .GetRequiredService(); var subject = context.Principal?.Identity?.Name ?? context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier) ?? "unknown"; auditStore.RecordSuccess(subject, context.HttpContext.TraceIdentifier); return Task.CompletedTask; }, OnAuthenticationFailed = context => { var auditStore = context.HttpContext.RequestServices .GetRequiredService(); var reason = context.Exception is null ? "invalid bearer token" : $"invalid bearer token: {context.Exception.GetType().Name}"; auditStore.RecordFailure(reason, context.HttpContext.TraceIdentifier); return Task.CompletedTask; }, OnChallenge = context => { if (context.AuthenticateFailure is null && context.Request.Headers.ContainsKey("Authorization")) { var auditStore = context.HttpContext.RequestServices .GetRequiredService(); auditStore.RecordFailure("invalid authorization header", context.HttpContext.TraceIdentifier); } return Task.CompletedTask; }, }; }); builder.Services.AddAuthorization(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.MapOpenApi(); } app.UseHttpsRedirection(); app.UseCors("ConfiguredOrigins"); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapGet("/health", () => Results.Ok(new { status = "ok" })); app.Run(); public partial class Program;