Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication in Swagger UI & documentation #270

Merged
merged 5 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Added grid to manage organisations in administration area.
- Added grid to manage users in administration area.
- Added local Keycloak server for development.
- Added authentication in Swagger UI.

### Changed

Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,44 @@ docker inspect --format='{{json .State.Health.Status}}' container_name
## Neue Version erstellen

Ein neuer GitHub _Pre-release_ wird bei jeder Änderung auf [main](https://github.com/GeoWerkstatt/geopilot) [automatisch](./.github/workflows/pre-release.yml) erstellt. In diesem Kontext wird auch ein neues Docker Image mit dem Tag _:edge_ erstellt und in die [GitHub Container Registry (ghcr.io)](https://github.com/geowerkstatt/geopilot/pkgs/container/geopilot) gepusht. Der definitve Release erfolgt, indem die Checkbox _Set as the latest release_ eines beliebigen Pre-releases gesetzt wird. In der Folge wird das entsprechende Docker Image in der ghcr.io Registry mit den Tags (bspw.: _:v1.2.3_ und _:latest_) [ergänzt](./.github/workflows/release.yml).

## Authentifizierung

Fürs Login auf geopilot wird ein Identity Provider mit OpenID Connect (OIDC) vorausgesetzt.
Der verwendete OAuth2 Flow ist _Authorization Code Flow with Proof Key for Code Exchange (PKCE)_.

### Token

Zur Authentifizierung aus dem Frontend wird das ID-Token und aus dem Swagger UI das Access-Token verwendet.
Dabei wird geprüft, dass das Token von der angegebenen Authority ausgestellt wurde (`iss` Claim) und für die Client-Id gültig ist (`aud` Claim).
Zusätzlich werden folgende Claims im Token vorausgesetzt: `sub`, `email` und `name`.
Diese werden beispielsweise bei den OIDC Scopes `openid`, `profile` und `email` mitgeliefert.
domi-b marked this conversation as resolved.
Show resolved Hide resolved

### Redirect URIs

Als erlaubte Redirect URIs müssen für das Login aus dem Frontend `https://<app-domain>` und aus Swagger UI `https://<app-domain>/swagger/oauth2-redirect.html` angegeben werden.
_([Entwicklungsumgebung](./config/realms/keycloak-geopilot.json): `https://localhost:5173` und `https://localhost:7188/swagger/oauth2-redirect.html`)_

### Swagger UI

Je nach Identity Provider wird die Audience automatisch gesetzt, sobald ein passender Scope verwendet wird.
Der Wert `ApiScope` in den Appsettings kann dazu verwendet werden, um diesen im Swagger UI zur Auswahl anzuzeigen.
domi-b marked this conversation as resolved.
Show resolved Hide resolved

In der [Entwicklungsumgebung](./config/realms/keycloak-geopilot.json) wird die Audience stattdessen mit einem Keycloak Protocol Mapper festgelegt.

### Appsettings

Folgende Appsettings müssen definiert sein (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

// Swagger UI auth options
"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": "<custom app scope> (optional)",
"ApiOrigin": "https://localhost:7188" // Swagger UI origin
domi-b marked this conversation as resolved.
Show resolved Hide resolved
}
```
22 changes: 20 additions & 2 deletions config/realms/keycloak-geopilot.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,29 @@
"alwaysDisplayInConsole": true,
"redirectUris": [
"https://localhost:5173",
"http://localhost:5173"
"http://localhost:5173",
"https://localhost:7188/swagger/oauth2-redirect.html",
"http://localhost:5173/swagger/oauth2-redirect.html"
],
"webOrigins": [
"https://localhost:5173",
"http://localhost:5173"
"http://localhost:5173",
"https://localhost:7188"
],
"protocolMappers": [
{
"name": "geopilot-audience-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "geopilot-client",
"id.token.claim": "false",
"lightweight.claim": "false",
"access.token.claim": "true",
"introspection.token.claim": "true"
}
}
]
}
],
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ services:
ReverseProxy__Clusters__stacBrowserCluster__Destinations__stacBrowserDestination__Address: http://stac-browser:8080/
Auth__Authority: http://localhost:4011/realms/geopilot
Auth__ClientId: geopilot-client
Auth__AuthorizationUrl: http://localhost:4011/realms/geopilot/protocol/openid-connect/auth
Auth__TokenUrl: http://localhost:4011/realms/geopilot/protocol/openid-connect/token
Auth__ApiOrigin: http://localhost:5173
Validation__InterlisCheckServiceUrl: http://interlis-check-service/
volumes:
- ./src/Geopilot.Api/Uploads:/uploads
Expand Down
51 changes: 51 additions & 0 deletions src/Geopilot.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,53 @@

// Workaround for STAC API having multiple actions mapped to the "search" route.
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());

var scopes = new Dictionary<string, string>
{
{ "openid", "Open Id" },
{ "email", "User Email" },
{ "profile", "User Profile" },
};
var apiScope = builder.Configuration["Auth:ApiScope"];
if (apiScope != null)
{
scopes.Add(apiScope, "geopilot API (required)");
}

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"] !),
domi-b marked this conversation as resolved.
Show resolved Hide resolved
},
},
});
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<string>()
},
});
domi-b marked this conversation as resolved.
Show resolved Hide resolved
});

builder.Services
Expand Down Expand Up @@ -189,6 +236,10 @@
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "geopilot API v1.0");

options.OAuthClientId(builder.Configuration["Auth:ClientId"]);
options.OAuth2RedirectUrl($"{builder.Configuration["Auth:ApiOrigin"]}/swagger/oauth2-redirect.html");
options.OAuthUsePkce();
});

app.UseHttpsRedirection();
Expand Down
5 changes: 4 additions & 1 deletion src/Geopilot.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
{
"Auth": {
"Authority": "http://localhost:4011/realms/geopilot",
"ClientId": "geopilot-client"
"ClientId": "geopilot-client",
"AuthorizationUrl": "http://localhost:4011/realms/geopilot/protocol/openid-connect/auth",
"TokenUrl": "http://localhost:4011/realms/geopilot/protocol/openid-connect/token",
"ApiOrigin": "https://localhost:7188"
},
"Logging": {
"LogLevel": {
Expand Down