Secrets Management with Azure Key Vault
Azure Key Vault is the single source of truth for every secret. No credentials live in code or appsettings — identities authenticate themselves and the values map straight into .NET configuration.
flowchart LR
subgraph Consumers[Compute Identities]
A([Web Apps]):::app
B([Azure Functions]):::app
C([Report VMs]):::app
end
ID{Managed Identity<br/>DefaultAzureCredential}:::sec
KV[(Azure Key Vault<br/>soft-delete 90d<br/>purge protection)]:::sec
CFG[GmiKeyVaultSecretManager<br/>kebab-case to nested<br/>IConfiguration]:::svc
subgraph Downstream[Mapped Configuration Targets]
S1[Stripe]:::ext
S2[SendGrid]:::ext
S3[Twilio]:::ext
S4[FMP Market Data]:::ext
S5[Anthropic Claude]:::ext
DB[(Database)]:::data
end
A --> ID
B --> ID
C --> ID
ID -->|read-only access| KV
KV --> CFG
CFG --> S1
CFG --> S2
CFG --> S3
CFG --> S4
CFG --> S5
CFG --> DB
classDef app fill:#1f2630,stroke:#C0894E,color:#F3EFE8;
classDef svc fill:#241c14,stroke:#E2B583,color:#F3EFE8;
classDef data fill:#22201a,stroke:#C0894E,color:#EBD3AE;
classDef ext fill:#1b1f27,stroke:#5A616B,color:#C4C0B8;
classDef sec fill:#1d2733,stroke:#5cc8e0,color:#eafaff;
A Single Source of Truth
Every secret the platform needs lives in Azure Key Vault and nowhere else. A custom
GmiKeyVaultSecretManager bridges the gap between how secrets are named in the
vault and how .NET configuration expects to see them, and the whole pipeline is wired up
through a single AddGmiKeyVault() extension method.
Name Normalization
Azure's default secret manager flattens vault names in a way that does not match nested .NET configuration. The custom manager overrides that normalization to map kebab-case vault secret names onto nested configuration paths.
| Vault secret name | .NET configuration path |
|---|---|
stripe-api-key |
Stripe:ApiKey |
Opt-In by Environment
Key Vault integration only activates when the KEY_VAULT_NAME environment
variable is set. This keeps local builds simple while making the vault authoritative
wherever it is configured.
- Trigger variable
KEY_VAULT_NAME - Wired via
AddGmiKeyVault() - Secrets in code None
Authentication
Authentication uses DefaultAzureCredential from Azure.Identity,
which automatically picks the right credential for the running environment — no secret
is ever needed to fetch the secrets.
- In Azure — managed identity No credentials provisioned or rotated by hand
- Locally — developer credentials The signed-in developer's Azure identity
- No secrets in code or appsettings The bootstrap is itself secret-free
Packages
The integration is built on the official Azure SDK configuration packages:
| Package | Version |
|---|---|
Azure.Identity |
1.13.2 |
Azure.Extensions.AspNetCore.Configuration.Secrets |
1.3.2 |
What Is Stored
Roughly ten secret types are stored in the vault, covering data, messaging, payments, market data, AI, and authentication:
- Database connection string
- SendGrid (email)
- Stripe (payments)
- Twilio (SMS)
- FMP market data API key
- Anthropic Claude API key
- Google OAuth
- and more
Vault Hardening
The vault itself is configured defensively against accidental loss and over-broad access:
- Soft-delete retention 90 days
- Purge protection Enabled
Soft-delete keeps deleted secrets recoverable for 90 days, and purge protection prevents anyone — even an administrator — from permanently deleting the vault or its secrets before that window elapses.
Access Policies — Scoped per Identity
| Identity | Access | Rationale |
|---|---|---|
| Deployer | Full | Provisions and rotates secrets during deployment |
| Web apps | Read-only | Consume secrets at runtime, never write |
| VMs | Read-only | Background workers consume secrets at runtime |