Skip to main content

Configuration

Standard ASP.NET Core configuration — appsettings.json, environment-specific overrides, environment variables and user secrets, in the usual precedence order.

Never commit a secret

Jwt:PrivateKey, connection strings and any provider credentials belong in user secrets locally and in your secret store in a deployment. The appsettings.json files in the repository carry empty placeholders for exactly this reason. Every example below shows a placeholder, not a value.

Common to every service

Audit

{
"Audit": {
"AuditApiUrl": "http://audit-api.internal/"
}
}

Where the audit API is. Required by every service that writes audit entries — which is all of them.

Account

{
"Account": {
"ApiUrl": "http://account-api.internal/"
}
}

Where the account API is. Optional, and read by services that set IncludeRecordAuthors or otherwise need to ask about accounts. A service with nowhere to ask is treated as one that was never meant to, and author names degrade to absent rather than the request failing.

One address per deployment, written down once: the identity API's own account config reads the same section and key.

Logging

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

Serilog is wired by AddCSMCommonWeb.

Identity API

{
"Jwt": {
"Issuer": "auth@tagshape.com",
"Audience": "tagshape.com",
"Subject": "auth@tagshape.com",
"DurationInMinutes": 60,
"RefreshDurationInMinutes": 10080,
"PrivateKey": "<private key PEM>"
}
}
SettingDefault
Issuerauth@tagshape.comiss claim
Audiencetagshape.comaud claim
DurationInMinutes60Access token lifetime
RefreshDurationInMinutes10080Refresh window (7 days)
PrivateKey(required)RS256 signing key, PEM

Generate a key pair with GET /generatejwk/{key} on the identity API.

Shorten DurationInMinutes where a revoked account or a withdrawn permission needs to take effect quickly — a token stays valid until it expires. Note that the repository's appsettings.json files ship with short development values (5–10 minutes); production values belong in your environment configuration.

Audit API

{
"Audit": {
"AuthNotRequired": ["FailedLogin", "Authentication", "JWT", "JWK"]
}
}

The entry types that may be written to POST / without a JWT, because they happen before one exists. Anything else is refused with 401.

caution

This list is the security boundary for the only endpoint in the product that accepts unauthenticated writes. Keep it to the entry types that genuinely precede a token.

Auth UI

{
"Account": { "ApiUrl": "http://account-api.internal/" },
"Identity": { "ApiUrl": "http://identity-api.internal/" },
"Audit": { "AuditApiUrl": "http://audit-api.internal/" },
"Authentication": {
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tagshape.com",
"TenantId": "",
"MetadataAddress": "https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc",
"SignedOutRedirectUri": "/",
"RequestProfilePhoto": true
}
},
"TokenRefresh": { "AllowedOrigins": [] }
}
Provider credentials do not live here

A client ID and secret belong to one organization's app registration, so they are held on the organization record and read from the account API. What remains under Authentication:AzureAd is the shape of the deployment, which is the same whoever signs in to it. TenantId is kept only as the gate on the diagnostics page; leave it empty and that page asks only that somebody is signed in.

Google is configured per organization like every other provider, which is why there is no Google section here.

TokenRefresh:AllowedOrigins lists the origins permitted to call the token refresh endpoint.

Connection strings

{
"ConnectionStrings": {
"AccountsContext": "Server=…;Database=TagShape_Accounts;…",
"LibrarySetsContext": "Server=…;Database=TagShape_LibrarySets;…",
"AuditContext": "Server=…;Database=TagShape_Audit;…"
}
}

Each service reads only its own. See Data stores.

The account and library set contexts enable sensitive data logging, which puts parameter values in the logs. Worth knowing before pointing a verbose log sink at an environment holding real secrets.

Environment variables

Any of the above, in the usual double-underscore form:

Jwt__PrivateKey='-----BEGIN PRIVATE KEY-----…'
Audit__AuditApiUrl='http://audit-api.internal/'
ConnectionStrings__AccountsContext='Server=…'

Local development

User secrets, per project:

dotnet user-secrets --project .\src\TagShape.Api.Identity.Web\ set "Jwt:PrivateKey" "<pem>"

Under the Aspire host, service URLs come from service discovery rather than configuration, so Audit:AuditApiUrl and Account:ApiUrl do not need setting locally. The AppHost also sets x-tagshape-org on the auth UI to name the organization being signed in to.

Options wired in code

AddCSMCommonWeb takes a CommonOptions rather than reading configuration, because these are facts about the service rather than about the deployment:

Option
OpenTelemetrySourceTelemetry source name
OpenApiEnabled, OpenApiTitle, OpenApiJwtAuthOptionOpenAPI document
DiscoverableServicesAspire service names to register clients for
IncludeRecordAuthorsWire up author-name lookups; wants Account:ApiUrl

See Shared libraries.