Configuration
Standard ASP.NET Core configuration — appsettings.json, environment-specific overrides, environment variables
and user secrets, in the usual precedence order.
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>"
}
}
| Setting | Default | |
|---|---|---|
Issuer | auth@tagshape.com | iss claim |
Audience | tagshape.com | aud claim |
DurationInMinutes | 60 | Access token lifetime |
RefreshDurationInMinutes | 10080 | Refresh 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.
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": [] }
}
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 | |
|---|---|
OpenTelemetrySource | Telemetry source name |
OpenApiEnabled, OpenApiTitle, OpenApiJwtAuthOption | OpenAPI document |
DiscoverableServices | Aspire service names to register clients for |
IncludeRecordAuthors | Wire up author-name lookups; wants Account:ApiUrl |
See Shared libraries.