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.

Library set API​

CreditCosts​

{
"CreditCosts": {
"VariableGetCost": 1,
"VariablePreviewCost": 10
}
}

What each billable call costs an organization, in credits. Costs are per call, not per variable or value returned.

SettingDefaultCharged by
VariableGetCost1GET /variables/{id}, GET /variablesets/{id}, and each library set exported by GET /export
VariablePreviewCost10GET /variablevalues and GET /variablevalues/preview

A cost of 0 makes that call free. The values are read on each request, so changing them takes effect without a restart. Only the library set API charges; the account API has no CreditCosts section.

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;…",
"keyvault": "https://<vault-name>.vault.azure.net/"
}
}

Each service reads only its own. keyvault is read only by the library set API, for organisation client certificates — see Data stores. Under the Aspire host it is injected automatically by the Key Vault Emulator resource, the same way the other three are injected by their respective hosting resources; nothing above needs setting locally.

servicebus is the Azure Service Bus connection, used for the credits-spent queue (published by the library set API, consumed by the account API). Without it the library set API discards spend with a warning and the account API does not run the consumer — see Credits.

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
IncludeCreditMeteringEnable billable endpoints; wants Account:ApiUrl and, to record spend, the servicebus connection

See Shared libraries.