From 6cdb12f940c85f394bbd469b8fc9f832d8388b29 Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Thu, 29 Aug 2024 09:22:56 +0200 Subject: [PATCH] Add fallback to OIDC discovery for swagger ui --- README.md | 13 ++-- src/Geopilot.Api/Program.cs | 52 +++------------ src/Geopilot.Api/SwaggerExtensions.cs | 96 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 48 deletions(-) create mode 100644 src/Geopilot.Api/SwaggerExtensions.cs diff --git a/README.md b/README.md index 751fdd0a..d8595bf7 100644 --- a/README.md +++ b/README.md @@ -96,17 +96,20 @@ In der [Entwicklungsumgebung](./config/realms/keycloak-geopilot.json) wird die A ### Appsettings -Folgende Appsettings müssen definiert sein (Beispiel aus [appsettings.Development.json](./src/Geopilot.Api/appsettings.Development.json) für die Entwicklungsumgebung): +Folgende Appsettings können definiert werden (Beispiel aus [appsettings.Development.json](./src/Geopilot.Api/appsettings.Development.json) für die Entwicklungsumgebung): + ```json5 "Auth": { // General auth options - "Authority": "http://localhost:4011/realms/geopilot", // Token issuer - "ClientId": "geopilot-client", // Token audience + "Authority": "http://localhost:4011/realms/geopilot", // Token issuer (required) + "ClientId": "geopilot-client", // Token audience (required) // Swagger UI auth options + "ApiOrigin": "https://localhost:7188", // Swagger UI origin (required) "AuthorizationUrl": "http://localhost:4011/realms/geopilot/protocol/openid-connect/auth", // OAuth2 login URL "TokenUrl": "http://localhost:4011/realms/geopilot/protocol/openid-connect/token", // OAuth2 token URL - "ApiScope": " (optional)", - "ApiOrigin": "https://localhost:7188" // Swagger UI origin + "ApiScope": "" } ``` + +Falls die `AuthorizationUrl` und/oder `TokenUrl` nicht definiert sind, wird im Swagger UI die OpenID Konfiguration der Authority (`/.well-known/openid-configuration`) geladen und alle vom Identity Provider unterstützten Flows angezeigt. diff --git a/src/Geopilot.Api/Program.cs b/src/Geopilot.Api/Program.cs index e7ceef7d..825bf0d0 100644 --- a/src/Geopilot.Api/Program.cs +++ b/src/Geopilot.Api/Program.cs @@ -103,52 +103,18 @@ // Workaround for STAC API having multiple actions mapped to the "search" route. options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); - var scopes = new Dictionary + var authUrl = builder.Configuration["Auth:AuthorizationUrl"]; + var tokenUrl = builder.Configuration["Auth:TokenUrl"]; + if (!string.IsNullOrEmpty(authUrl) && !string.IsNullOrEmpty(tokenUrl)) { - { "openid", "Open Id" }, - { "email", "User Email" }, - { "profile", "User Profile" }, - }; - var apiScope = builder.Configuration["Auth:ApiScope"]; - if (apiScope != null) - { - scopes.Add(apiScope, "geopilot API (required)"); + var apiScope = builder.Configuration["Auth:ApiScope"]; + options.AddGeopilotOAuth2(authUrl, tokenUrl, apiScope); } - - options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme - { - Name = "Authorization", - Scheme = JwtBearerDefaults.AuthenticationScheme, - In = ParameterLocation.Header, - Type = SecuritySchemeType.OAuth2, - Flows = new OpenApiOAuthFlows - { - AuthorizationCode = new OpenApiOAuthFlow - { - Scopes = scopes, - AuthorizationUrl = new Uri(builder.Configuration["Auth:AuthorizationUrl"] !), - TokenUrl = new Uri(builder.Configuration["Auth:TokenUrl"] !), - RefreshUrl = new Uri(builder.Configuration["Auth:TokenUrl"] !), - }, - }, - }); - options.AddSecurityRequirement(new OpenApiSecurityRequirement + else { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = JwtBearerDefaults.AuthenticationScheme, - }, - Scheme = "oauth2", - Name = JwtBearerDefaults.AuthenticationScheme, - In = ParameterLocation.Header, - }, - Array.Empty() - }, - }); + var authority = builder.Configuration["Auth:Authority"]; + options.AddOpenIdConnect(authority!); + } }); builder.Services diff --git a/src/Geopilot.Api/SwaggerExtensions.cs b/src/Geopilot.Api/SwaggerExtensions.cs new file mode 100644 index 00000000..77f9284a --- /dev/null +++ b/src/Geopilot.Api/SwaggerExtensions.cs @@ -0,0 +1,96 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Geopilot.Api; + +/// +/// Provides extension methods related to Swagger. +/// +public static class SwaggerExtensions +{ + private const string SchemeName = "Authorization"; + + /// + /// Adds a security definition and requirement for OpenId Connect using the well-known configuration of the . + /// + /// The swagger options. + /// The authority and token issuer. + public static void AddOpenIdConnect(this SwaggerGenOptions options, string authority) + { + options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme + { + Name = SchemeName, + Scheme = JwtBearerDefaults.AuthenticationScheme, + In = ParameterLocation.Header, + Type = SecuritySchemeType.OpenIdConnect, + OpenIdConnectUrl = new Uri($"{authority}/.well-known/openid-configuration"), + }); + options.AddOAuth2SecurityRequirement(); + } + + /// + /// Adds a security definition and requirement for OAuth2 authorization code flow. + /// + /// The swagger options. + /// The authorization URL. + /// The token URL. + /// An optional scope defined for the client. + public static void AddGeopilotOAuth2(this SwaggerGenOptions options, string authUrl, string tokenUrl, string? apiScope) + { + var scopes = new Dictionary + { + { "openid", "Open Id" }, + { "email", "User Email" }, + { "profile", "User Profile" }, + }; + if (apiScope != null) + { + scopes.Add(apiScope, "geopilot API (required)"); + } + + options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme + { + Name = SchemeName, + Scheme = JwtBearerDefaults.AuthenticationScheme, + In = ParameterLocation.Header, + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows + { + AuthorizationCode = new OpenApiOAuthFlow + { + Scopes = scopes, + AuthorizationUrl = new Uri(authUrl), + TokenUrl = new Uri(tokenUrl), + RefreshUrl = new Uri(tokenUrl), + }, + }, + }); + options.AddOAuth2SecurityRequirement(); + } + + /// + /// Adds a security requirement for OAuth2. + /// + /// The swagger options. + private static void AddOAuth2SecurityRequirement(this SwaggerGenOptions options) + { + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme, + }, + Scheme = "oauth2", + Name = JwtBearerDefaults.AuthenticationScheme, + In = ParameterLocation.Header, + }, + Array.Empty() + }, + }); + } +}