Configuration
Every configuration key the AppointMe .NET 10 API reads, with its type, default, and where it's set locally (appsettings, Aspire) and on Azure (appsettings.Devtest.json, Key Vault references).
- Files
appsettings.json+appsettings.<Env>.json- Env var form
ConnectionStrings__AppointMeSql- Azure secrets
- Key Vault references, set by Bicep
- Validation
- Options are validated at startup
This page lists every key the API reads at commit 2ccbe87, found by searching src/ for options classes, BindConfiguration, GetConnectionString and IConfiguration reads. Each table gives the default from appsettings.json (or from code when the file doesn’t set it), the local value and the deployed devtest value. A dash means the key isn’t set there. Keys are alphabetical within each table.
How configuration is loaded
Section titled “How configuration is loaded”The standard ASP.NET Core order applies. Later sources win, and on Azure every secret arrives as an environment variable that App Service resolves from Key Vault.
| Order | Source | Notes |
|---|---|---|
| 1 | src/AppointMe.Api/appsettings.json |
Defaults for every environment |
| 2 | appsettings.<Environment>.json |
Development, Devtest or Codegen |
| 3 | Environment variables | __ replaces :. Aspire, the Dockerfile and App Service app settings all arrive this way. |
| 4 | Command-line arguments |
The API project has no UserSecretsId, so user secrets aren’t loaded. On Azure, a Key Vault reference such as @Microsoft.KeyVault(SecretUri=…) is resolved by App Service before the API sees it, using the Web App’s managed identity.
ASPNETCORE_ENVIRONMENT |
Selected by | What changes in code |
|---|---|---|
Codegen |
Code, when the entry assembly is GetDocument.Insider (build-time OpenAPI) or the arguments contain codegen |
Placeholder values from appsettings.Codegen.json. Hangfire isn’t registered, migrations don’t run, and Wolverine runs as an in-memory mediator with the durability agent off. |
Development |
Properties/launchSettings.json, so dotnet run and Aspire |
MapOpenApi() is mapped, HSTS is off, and Wolverine generates handler code at runtime (TypeLoadMode.Dynamic) |
Devtest |
Bicep: ASPNETCORE_ENVIRONMENT app setting |
HSTS is on. Wolverine loads pre-generated handlers (TypeLoadMode.Static), which the Docker build writes with codegen write. |
Connection strings
Section titled “Connection strings”One SQL database holds everything. The other two connection strings are optional.
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
ConnectionStrings:AppointMeMessaging |
string | "" |
— | — | The Azure Service Bus connection. Read only when Wolverine:Transport is AzureServiceBus. |
ConnectionStrings:AppointMeSql |
string | None (required) | appsettings.Development.json: Server=localhost,60740;Database=AppointMe;…. Aspire injects its own for the AppointMeSql database resource. |
Key Vault reference AppointMeSql |
Every module’s EF Core DbContext and Dapper reads, Wolverine’s message store (schema wolverine) and Hangfire storage (schema hangfire). When it’s missing, startup fails with Connection string 'AppointMeSql' not found. |
ConnectionStrings:DataProtectionStorage |
string | "" |
— | Key Vault reference DataProtectionStorage |
An Azure Storage connection string. When it’s set, the Data Protection key ring is stored in blob data-protection-keys/keys.xml. When it’s empty, the default local key ring is used. |
Authentication and identity providers
Section titled “Authentication and identity providers”Authentication:Provider chooses Keycloak or Entra External ID. Only the chosen provider’s sections are bound, and every key in them is required and validated at startup.
Authentication
Section titled “Authentication”| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
Authentication:Provider |
string: Keycloak or EntraExternalId |
Keycloak (file and code) |
— (default) | EntraExternalId |
Chooses the OIDC and JWT settings, the demo-login authenticator and the IIdentityProvider. Any other value throws at startup. |
Authentication:RequireHttpsMetadata |
bool | true (code) |
false |
true |
Whether the OIDC and JWT handlers require HTTPS to fetch discovery metadata and keys. Codegen also sets false. |
Authentication:SuperAdmins |
string[] | [] |
["demo@appointme.dev"] |
["demo@appointme.dev"] |
Emails, matched case-insensitively, that pass the HangfireDashboard policy for /admin/jobs. They also get IsSuperAdmin in GET /api/v1/me. |
Authentication:Keycloak
Section titled “Authentication:Keycloak”Bound when Authentication:Provider is Keycloak. KeycloakOptions marks every property [Required].
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
Authentication:Keycloak:ApiAudience |
string | — | appointme-api |
— | The audience the JWT bearer handler expects |
Authentication:Keycloak:Authority |
string | — | https://localhost:8082/realms/appointme. Aspire overrides it with Keycloak’s endpoint plus /realms/appointme. |
— | The OIDC authority. The demo login posts to <Authority>/protocol/openid-connect/token. |
Authentication:Keycloak:FrontendClientId |
string | — | appointme-frontend |
— | The OIDC client for browser sign-in and the demo password grant |
Authentication:Keycloak:FrontendClientSecret |
string | — | A local-only value in appsettings.Development.json |
— | That client’s secret |
Authentication:EntraExternalId
Section titled “Authentication:EntraExternalId”Bound when Authentication:Provider is EntraExternalId. EntraExternalIdOptions marks every property [Required], so the empty defaults fail validation until you fill them in.
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
Authentication:EntraExternalId:ApiAudience |
string | "" |
— | api://<client-id> |
The audience the JWT bearer handler expects |
Authentication:EntraExternalId:Authority |
string | "" |
— | https://<tenant>.ciamlogin.com/<tenant-id>/v2.0 |
The OIDC authority. The demo login derives the native-auth base …/oauth2/v2.0/ from it. |
Authentication:EntraExternalId:ClientId |
string | "" |
— | The app registration’s client ID | The OIDC client, and the client for native-auth demo login |
Authentication:EntraExternalId:ClientSecret |
string | "" |
— | Key Vault reference EntraExternalIdClientSecret |
The OIDC client secret |
KeycloakAdmin
Section titled “KeycloakAdmin”Bound by the Identity module when Authentication:Provider is Keycloak. KeycloakAdminOptions marks every property [Required]. It’s used to create users with the VERIFY_EMAIL and UPDATE_PASSWORD actions, and to resend the verification email.
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
KeycloakAdmin:BaseUrl |
string | — | https://localhost:8082. Aspire overrides it with Keycloak’s endpoint. |
— | The Keycloak base URL for the admin API |
KeycloakAdmin:ClientId |
string | — | appointme-api |
— | The client for the client-credentials flow, also used for the verify-email redirect |
KeycloakAdmin:ClientSecret |
string | — | A local-only value in appsettings.Development.json |
— | That client’s secret |
KeycloakAdmin:Realm |
string | — | appointme |
— | The realm users are created in |
EntraIdentity
Section titled “EntraIdentity”Bound by the Identity module when Authentication:Provider is EntraExternalId. EntraIdentityOptions marks every property [Required]. It builds a Microsoft Graph client with ClientSecretCredential that sends invitations and looks users up by email.
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
EntraIdentity:ClientId |
string | "" |
— | The same client ID as above | The app that calls Graph |
EntraIdentity:ClientSecret |
string | "" |
— | Key Vault reference EntraExternalIdClientSecret, the same secret |
Its secret |
EntraIdentity:TenantId |
string | "" |
— | The External ID tenant ID | The tenant the credential signs in to |
Entra External ID shows where each value comes from in the portal.
Demo and frontend
Section titled “Demo and frontend”Demo:Enabled turns on the public demo login, the demo-data seeding and two daily jobs. Frontend:BaseUrl is where every sign-in and invitation flow sends the user back.
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
Demo:Enabled |
bool | false |
true |
true |
Lets GET /api/v1/login/demo sign in (it returns 404 when off). DemoSeedingSaga seeds each new company with 150 customers, 3 appointments each. Registers the crm:top-up-demo-customers and booking:top-up-demo-appointments jobs, daily at 03:00 UTC. |
Demo:User:Email |
string | — | demo@appointme.dev |
demo@appointme.dev |
The demo user, which must already exist in the identity provider. If Demo:User is missing, the demo login returns 404. |
Demo:User:Name |
string, optional | — | John Doe |
John Doe |
The display name used when the token has no name claim. Falls back to Demo User. |
Demo:User:Password |
string | — | AppointMe1 |
AppointMe1 |
The demo user’s password, sent to Keycloak’s password grant or Entra’s native-auth API |
Frontend:BaseUrl |
absolute URI, required | https://localhost:5173 |
— (default) | https://app.appointme.dev |
Where users land after sign-in, sign-out and demo login. The invitation link is <BaseUrl>/auth/login. Validated with [Required, Url] at startup. |
Turning Demo:Enabled off doesn’t remove recurring jobs that are already registered. Production checklist covers that.
Messaging, jobs, Data Protection and telemetry
Section titled “Messaging, jobs, Data Protection and telemetry”Wolverine has one key. Hangfire and Data Protection reuse connection strings, and OpenTelemetry reads only the standard OTEL_* variables.
| Key | Type | Default | Dev | Devtest | What it does |
|---|---|---|---|---|---|
APPLICATIONINSIGHTS_CONNECTION_STRING |
env var | — | — | Bicep: the App Insights connection string | Set on the Web App, but nothing in the API reads it |
OTEL_EXPORTER_OTLP_ENDPOINT |
env var | The OTLP exporter’s own default | Aspire points it at its dashboard | Bicep: https://<location>.in.applicationinsights.azure.com/v2/track |
Where the OTLP exporter sends traces and metrics |
Wolverine:Transport |
string: SqlDurable or AzureServiceBus |
SqlDurable (file and code) |
— (default) | SqlDurable |
SqlDurable uses durable local queues on the SQL outbox. AzureServiceBus adds a Service Bus broker that auto-provisions its queues, and needs ConnectionStrings:AppointMeMessaging. Any other value throws at startup. |
These have no keys of their own:
| Feature | Reads | Fixed in code |
|---|---|---|
| Data Protection | ConnectionStrings:DataProtectionStorage |
Application name appointme, container data-protection-keys, blob keys.xml |
| Hangfire | ConnectionStrings:AppointMeSql |
Schema hangfire, created if missing. Dashboard at /admin/jobs, gated by Authentication:SuperAdmins. Not registered in Codegen. |
| OpenTelemetry | OTEL_* environment variables |
Service AppointMe.Api, version 1.0.0. Traces: ASP.NET Core, HttpClient, SqlClient and Wolverine. Metrics: ASP.NET Core, HttpClient and Wolverine*. No logs. |
| Wolverine | ConnectionStrings:AppointMeSql, Wolverine:Transport |
MessageStorageSchemaName = "wolverine" (the wolverine_* tables are still created in dbo at this commit), durable local queues, auto-applied transactions, ValidationException moved to the error queue |
Host, container and App Service settings
Section titled “Host, container and App Service settings”These are read by ASP.NET Core, the container or App Service itself, not by AppointMe code. You set them per environment, and several come from the Dockerfile or the Bicep.
| Key | Dev | Devtest | What it does |
|---|---|---|---|
AllowedHosts |
* |
* |
ASP.NET Core host filtering. * accepts any Host header. |
ASPNETCORE_ENVIRONMENT |
Development (launch profile) |
Devtest (Bicep) |
Picks appsettings.<Environment>.json and the code paths in the first section |
ASPNETCORE_FORWARDEDHEADERS_ENABLED |
— | true (Bicep) |
Turns on ASP.NET Core’s forwarded-headers handling for the App Service front end. Program.cs also calls UseForwardedHeaders(). |
ASPNETCORE_URLS |
The https launch profile, which Aspire uses: https://localhost:7233;http://localhost:5179. A plain dotnet run takes the first profile, http: http://localhost:5179. |
http://+:8080 (Dockerfile) |
Where Kestrel listens |
DOCKER_REGISTRY_SERVER_URL |
— | https://<acr>.azurecr.io (Bicep) |
Read by App Service: the registry it pulls the image from |
Logging:LogLevel:Default |
Information |
Information |
Minimum log level |
Logging:LogLevel:Microsoft.AspNetCore |
Warning |
Warning |
Quietens framework request logs |
Logging:LogLevel:Microsoft.EntityFrameworkCore |
— | Warning |
Set in appsettings.Devtest.json only |
WEBSITES_ENABLE_APP_SERVICE_STORAGE |
— | false (Bicep) |
Read by App Service: no persistent /home mount in the container |
WEBSITES_PORT |
— | 8080 (Bicep) |
Read by App Service: the container port to route to |
Build-time values that end up in the image or the SPA:
| Key | Where | Default | What it does |
|---|---|---|---|
APP_VERSION |
Docker build arg | local |
Becomes VITE_APP_VERSION, the version in the SPA footer. CI passes the tag, or main-<short sha>. |
BUILD_CONFIGURATION |
Docker build arg | Release |
The configuration for codegen write and dotnet publish |
VITE_API_PROXY_TARGET |
src/AppointMe.Frontend/.env.development |
https://localhost:7233 |
Where the Vite dev server proxies /api, /signin-oidc and /admin outside Aspire. Under Aspire, services__appointme-api__https__0 takes precedence. |
Set in code, not configuration
Section titled “Set in code, not configuration”These look like settings but are constants. Change them in code if you need to.
| Setting | Value | Where |
|---|---|---|
| Antiforgery | Cookie appointme.csrf. The SPA reads the XSRF-TOKEN cookie and sends it back as X-XSRF-TOKEN. |
Antiforgery/ |
| Auth cookie | appointme.auth: HttpOnly, SameSite Lax, always Secure, 7 days, sliding |
Authentication/AuthenticationExtensions.cs |
| CSP | Sent as Content-Security-Policy-Report-Only |
SecurityHeaders/SecurityHeadersMiddleware.cs |
| Demo top-up schedule | 0 3 * * * (daily, 03:00 UTC) |
The two *JobRegistrar.cs files |
| Forwarded host header | X-Original-Host, with every proxy trusted |
Program.cs |
| HSTS | 365 days, IncludeSubDomains, outside Development |
Program.cs |
| OIDC | Code flow, scopes openid profile email, callback /signin-oidc, pushed authorization disabled, only the id_token kept |
Authentication/AuthenticationExtensions.cs |
| Startup migrations | Every module’s DbContext migrates on startup, except in Codegen |
AppointMe.Shared/Database/Migrations |
| Tenant header | X-Company-Id |
Program.cs, see Multi-tenancy |
Paths in this table are relative to src/AppointMe.Api/ unless they start with another project.