using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi; using System.IdentityModel.Tokens.Jwt; using System.Text; // Required NuGet Packages // Microsoft.AspNet.WebApi.Core // Microsoft.AspNetCore.Authentication.JwtBearer // Microsoft.AspNetCore.Authentication.OpenIdConnect // Microsoft.IdentityModel.Protocols.OpenIdConnect namespace OpenID_API.OpenIDConnect { public class OpenIDConnectUtils { // Configuration priority from highest to lowest // Highest: Command line arguments // : Non-prefixed environment variables // : User secrets from the .NET User Secrets Manager // : Any appsettings.{ ENVIRONMENT_NAME }.json files // : The appsettings.json file // Lowest : Fallback to the host configuration /// /// Setting up OpenIDConnect authentication (Program.cs) /// /// WebApplicationBuilder public void ConfigureBuilder(WebApplicationBuilder builder) { // Add services to the container. MyConfiguration.Set(builder.Configuration); // >>> This adds the authentication service builder.Services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddCookie() .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { options.Authority = builder.Configuration["OpenIDRealmURI"]; options.Audience = builder.Configuration["OpenIDClient"]; //options.TokenValidationParameters.RoleClaimType = "roles"; // adjust if your roles claim is named differently options.TokenValidationParameters = new TokenValidationParameters { RoleClaimType = "roles", // 👈 IMPORTANT NameClaimType = "name", ValidateIssuer = true, ValidateAudience = true }; options.MapInboundClaims = false; // prevent automatic claim type mapping options.RequireHttpsMetadata = true; // set to false only for development options.IncludeErrorDetails = true; // Map claim types if needed //options.TokenValidationParameters = new TokenValidationParameters //{ // NameClaimType = "name", // RoleClaimType = "roles" //}; options.Events = new JwtBearerEvents { OnMessageReceived = context => { // Optional: log the token or handle custom token retrieval var token = context.Request.Headers["Authorization"].ToString(); string path = context.Request.Path; if (!string.IsNullOrEmpty(token)) { Console.WriteLine("Access token"); Console.WriteLine($"URL: {path}"); Console.WriteLine($"Token: {token}\r\n"); } else { Console.WriteLine("Access token"); Console.WriteLine("URL: " + path); Console.WriteLine("Token: No access token provided\r\n"); } return Task.CompletedTask; }, OnTokenValidated = context => { var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger("Api.Authorization"); var claims = context?.Principal?.Claims; if (claims is null || !claims.Any()) { logger.LogWarning("Claims null or empty"); return Task.CompletedTask; } var claimsBuilder = new StringBuilder(); claimsBuilder.AppendLine("User Claims:"); foreach (var claim in claims) { claimsBuilder.AppendLine($"[{claim.Type}] - [{claim.Value}]"); } logger.LogTrace("{Claims}", claimsBuilder.ToString()); return Task.CompletedTask; }, OnAuthenticationFailed = context => { // Optional: log authentication failures Console.WriteLine("Authentication failed: " + context.Exception.Message); return Task.CompletedTask; } }; }); // <<< End of authentication service setup // >>> This adds the authorization policies builder.Services .AddAuthorizationBuilder() .AddPolicy("read_access", builder => { // claim, list of acceptable values builder.RequireClaim("myClaim", "MyClaimValueRO1", "MyClaimValueRO2"); }) .AddPolicy("write_access", builder => { builder.RequireClaim("myClaim", "MyClaimValueRW1", "MyClaimValueRW2"); }) .AddPolicy("Admin", policy => policy.RequireClaim("roles", "admin", "warlock")); // <<< End of authorization policies setup ConfigureBuilderOpenAPI(builder); builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); }); } private void ConfigureBuilderOpenAPI(WebApplicationBuilder builder) { // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); // Add Swagger with Authorization option // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/HEAD/docs/configure-and-customize-swaggergen.md#add-security-definitions-and-requirements builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); options.AddSecurityDefinition("bearer", new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "JWT Authorization header using the Bearer scheme." }); options.AddSecurityRequirement(document => new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference("bearer", document)] = [] }); }); // Add Swagger with Authorization option } private void ConfigureAppOpenAPI(WebApplication app) { app.MapSwagger(); app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("v1/swagger.json", "Your Name Here"); }); } /// /// /// WebApplication public void ConfigureApp(WebApplication app) { if (app.Environment.IsDevelopment()) ConfigureAppOpenAPI(app); } public JwtSecurityToken GetJwtPayload(HttpContext myContext) { var handler = new JwtSecurityTokenHandler(); return handler.ReadJwtToken(myContext.GetTokenAsync("access_token").Result); } public string GetJwtClaim(HttpContext myContext, string theClaim) { JwtSecurityToken jwtPayload = GetJwtPayload(myContext); return jwtPayload.Claims.FirstOrDefault(claim => claim.Type == theClaim).Value; } } static public class MyConfiguration { static ConfigurationManager _config; static public void Set(ConfigurationManager config) { _config = config; } static public ConfigurationManager Get() { return _config; } } }