|
- 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<KeycloakOptions>(builder.Configuration.GetSection(KeycloakOptions.SectionName));
- builder.Services.AddSingleton<IAuthenticationAuditStore, InMemoryAuthenticationAuditStore>();
- builder.Services.AddHttpClient<IKeycloakTokenClient, KeycloakTokenClient>();
-
- var allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get<string[]>() ?? [];
- 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<KeycloakOptions>() ?? 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<IAuthenticationAuditStore>();
- 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<IAuthenticationAuditStore>();
-
- 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<IAuthenticationAuditStore>();
-
- 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;
|