Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 4 additions & 1 deletion src/Ignis.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"DisplayName": "Ignis Client",
"AllowedGrantTypes": ["client_credentials"]
}
]
],
"Endpoints": {
"LoginPath": "/connect/login"
}
},
"SparkSettings": {
"Endpoint": "https://localhost:5201/fhir",
Expand Down
3 changes: 3 additions & 0 deletions src/Ignis.Auth/AuthSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class AuthCertificateSettings
public class AuthEndpointSettings
{
public string TokenEndpointPath { get; set; } = "connect/token";
public string LoginPath { get; set; } = "connect/login";
}

public class ClientDefinition
Expand All @@ -27,4 +28,6 @@ public class ClientDefinition
public string ClientSecret { get; set; } = "";
public string DisplayName { get; set; } = "";
public List<string> AllowedGrantTypes { get; set; } = [];
public List<string> RedirectUris { get; set; } = [];
public List<string> PostLogoutRedirectUris { get; set; } = [];
}
74 changes: 26 additions & 48 deletions src/Ignis.Auth/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
# Ignis.Auth

OAuth 2.0 token service for Ignis, built on [OpenIddict](https://documentation.openiddict.com/) with MongoDB storage.

The library provides an `AuthorizationHandler` containing the token endpoint logic, while the host application supplies a thin controller that delegates to it. Currently supports the `client_credentials` grant type.
OAuth 2.0 authorization server built on [OpenIddict](https://documentation.openiddict.com/) with MongoDB storage. Currently supports the `client_credentials` grant type; `authorization_code` with mandatory PKCE and Pushed Authorization Requests (PAR) is prepared in settings and client sync but not yet enabled in the server pipeline.

## Configuration

```json
{
"AuthSettings": {
"Enabled": true,
"ConnectionString": "mongodb://localhost:27017/ignis",
"Clients": [
{
"ClientId": "my-client",
"ClientId": "my-backend",
"ClientSecret": "my-secret",
"DisplayName": "My Client",
"DisplayName": "Backend Service",
"AllowedGrantTypes": ["client_credentials"]
},
{
"ClientId": "my-web-app",
"ClientSecret": "web-secret",
"DisplayName": "My Web App",
"AllowedGrantTypes": ["authorization_code"],
"RedirectUris": ["https://app.example.com/callback"],
"PostLogoutRedirectUris": ["https://app.example.com"]
}
],
"Endpoints": {
"LoginPath": "/connect/login"
},
"Certificates": {
"SigningCertificatePath": "certs/signing.pfx",
"SigningCertificatePassword": "",
Expand All @@ -29,56 +37,26 @@ The library provides an `AuthorizationHandler` containing the token endpoint log
}
```

## Certificates

OpenIddict requires a signing certificate and an encryption certificate for token generation and validation.
All clients are confidential and require a `ClientSecret`. `AllowedGrantTypes` is required.

### Development

In development mode, OpenIddict automatically generates ephemeral development certificates. No configuration needed.

### Production
## Certificates

Generate self-signed PFX certificates for signing and encryption:
Development mode uses ephemeral auto-generated certificates. For production, generate PFX certificates:

```bash
mkdir -p certs

# Signing certificate
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout certs/signing-key.pem -out certs/signing-cert.pem \
-days 365 -subj "/CN=Ignis Token Signing"
openssl pkcs12 -export -out certs/signing.pfx \
-inkey certs/signing-key.pem -in certs/signing-cert.pem \
-passout pass:

# Encryption certificate
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout certs/encryption-key.pem -out certs/encryption-cert.pem \
-days 365 -subj "/CN=Ignis Token Encryption"
openssl pkcs12 -export -out certs/encryption.pfx \
-inkey certs/encryption-key.pem -in certs/encryption-cert.pem \
-passout pass:

# Clean up PEM files
for NAME in signing encryption; do
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout certs/$NAME-key.pem -out certs/$NAME-cert.pem \
-days 365 -subj "/CN=Ignis ${NAME^}"
openssl pkcs12 -export -out certs/$NAME.pfx \
-inkey certs/$NAME-key.pem -in certs/$NAME-cert.pem -passout pass:
done
rm certs/*.pem
```

The `certs/` directory is gitignored. Mount certificates via volume or secrets in production.
The `certs/` directory is gitignored. Mount via volume or secrets in production.

## Client sync

Clients defined in `AuthSettings.Clients` are synced to MongoDB on startup:

- New clients are created
- Existing clients are updated (secret, display name)
- Clients not in config are removed

## Usage

```bash
curl -X POST https://localhost:5201/connect/token \
-d grant_type=client_credentials \
-d client_id=my-client \
-d client_secret=my-secret
```
Clients in `AuthSettings.Clients` are synced to MongoDB on startup — created, updated, or removed to match configuration.
30 changes: 28 additions & 2 deletions src/Ignis.Auth/Services/ClientSyncInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ private OpenIddictApplicationDescriptor BuildDescriptor(ClientDefinition client)
}
};

var hasRedirectUris = client.RedirectUris.Count > 0;

foreach (var grantType in client.AllowedGrantTypes)
{
switch (grantType)
Expand All @@ -97,8 +99,32 @@ private OpenIddictApplicationDescriptor BuildDescriptor(ClientDefinition client)
break;

case GrantTypes.AuthorizationCode:
throw new NotImplementedException(
$"Grant type '{GrantTypes.AuthorizationCode}' is not yet supported.");
if (!hasRedirectUris)
{
_logger.LogWarning(
"Client {ClientId} allows authorization_code but has no RedirectUris configured – skipping auth code permissions.",
client.ClientId);
break;
}

descriptor.Permissions.Add(Permissions.Endpoints.Authorization);
descriptor.Permissions.Add(Permissions.Endpoints.PushedAuthorization);
descriptor.Permissions.Add(Permissions.GrantTypes.AuthorizationCode);
descriptor.Permissions.Add(Permissions.ResponseTypes.Code);

descriptor.Requirements.Add(Requirements.Features.ProofKeyForCodeExchange);
descriptor.Requirements.Add(Requirements.Features.PushedAuthorizationRequests);

foreach (var uri in client.RedirectUris)
descriptor.RedirectUris.Add(new Uri(uri));

if (client.PostLogoutRedirectUris.Count > 0)
{
descriptor.Permissions.Add(Permissions.Endpoints.EndSession);
foreach (var uri in client.PostLogoutRedirectUris)
descriptor.PostLogoutRedirectUris.Add(new Uri(uri));
}
break;

default:
_logger.LogWarning(
Expand Down
8 changes: 7 additions & 1 deletion tests/Ignis.Api.Tests/AuthConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ private WebApplicationFactory<Program> CreateFactory(
{
return new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((_, c) => c.AddInMemoryCollection(config));
builder.ConfigureAppConfiguration((_, c) =>
{
// Re-add environment variables then override with in-memory config,
// ensuring test values take precedence over leaked env vars.
c.AddEnvironmentVariables();
c.AddInMemoryCollection(config);
});
builder.UseEnvironment(environment);
});
}
Expand Down
Loading