.NET Secrets Management: Azure Key Vault vs User Secrets vs Environment Variables β Enterprise Decision Guide
Secrets mismanagement is one of the most predictable causes of enterprise security incidents. Leaked database connection strings, exposed API keys, and hardcoded credentials surface repeatedly in post-mortems β not because developers are careless, but because most teams lack a deliberate, environment-specific strategy. For .NET teams, the ecosystem provides three primary mechanisms: User Secrets, environment variables, and Azure Key Vault. Each serves a distinct purpose. The mistake is treating them as interchangeable or defaulting to a single approach across all environments.
Want implementation-ready .NET source code you can adapt fast? Join Coding Droplets on Patreon. π https://www.patreon.com/CodingDroplets
Why Secrets Management Is a Governance Problem, Not a Dev Problem
The instinct is to frame secrets management as a developer task β something you configure once during onboarding and forget. That framing is wrong, and it's why teams get burned. Secrets management is a governance problem because it spans every stage of the software lifecycle: local development, CI/CD pipelines, staging environments, and production deployments.
Governance means defining policies: who can read a secret, who can rotate it, which systems are authorised to retrieve it, and what happens when a secret is compromised. Without these policies, you end up with secrets scattered across developer machines, CI configuration files, Kubernetes manifests, and shared spreadsheets β all with different access controls, different rotation schedules, and different risk profiles.
Regulatory frameworks like SOC 2, ISO 27001, HIPAA, and PCI DSS all include explicit requirements for credential management. "We use environment variables" is not a compliance answer. "We use Azure Key Vault with RBAC, automated rotation, and audit logging integrated into our SIEM" is. The distinction matters when auditors ask.
Enterprise .NET teams need to make an explicit architectural decision: which mechanism applies where, who owns that decision, and how it's enforced. That decision starts with understanding what each mechanism is actually built for.
The Three Layers Every .NET Team Actually Uses
Before comparing approaches, it helps to map them to the environments they're designed for:
Development (local machine): Developer-specific, high-velocity, personal credentials. Secrets rotate frequently. Isolation matters β one developer's Azure subscription credentials should never leak into another developer's environment or, worse, into version control.
Staging and CI/CD: Shared environments with controlled access. Secrets need to be consistent across pipeline runs but should differ from production values. Access must be auditable.
Production: Live traffic, customer data, compliance scope. Secrets must be centralised, access must be role-based, rotation must be automated where possible, and every access event should be logged.
The right strategy layers these three environments, using different mechanisms for each rather than forcing one approach across all of them.
User Secrets β The Developer Sandbox
User Secrets exist specifically for local development. They store sensitive configuration values in a JSON file located outside the project directory β on the developer's own machine β which means they're never committed to source control, even accidentally.
The mechanism integrates cleanly with ASP.NET Core's configuration pipeline. Values stored in User Secrets override values in appsettings.json without any changes to the application code. This makes it easy for developers to supply their own credentials for services like Azure, local databases, or third-party APIs without altering shared configuration files.
The critical constraint is scope: User Secrets are a developer sandbox. They provide no access controls, no audit trails, and no centralised management. They cannot be deployed. If a developer leaves the team, their User Secrets leave with them β there's no revocation mechanism.
Use User Secrets when:
Credentials are specific to an individual developer's environment
Values should never appear in version control
The environment is a local development machine
Do not use User Secrets when:
Multiple people need the same credentials
The application runs in a CI/CD pipeline or hosted environment
You need to track who accessed what and when
Environment Variables β The Deployment Workhorse
Environment variables are the most portable secrets mechanism in .NET. They work across every deployment target β Docker containers, Kubernetes pods, Azure App Service, on-premises servers, and GitHub Actions β without requiring any SDK or infrastructure dependency. ASP.NET Core reads them automatically through the built-in configuration provider.
Their portability is also their weakness. Environment variables are process-level values with no built-in access controls. Anyone with shell access to a machine can read them. They don't rotate automatically. They're often set in deployment scripts, CI configuration files, or orchestration manifests β all of which become implicit copies of the secret, each with its own lifecycle and access permissions.
In practice, environment variables work well for non-sensitive configuration that varies between environments (feature flags, log levels, service endpoints) and for short-lived CI/CD secrets injected at runtime from a secure source. They're also appropriate for containerised workloads where the orchestrator (Kubernetes, Azure Container Apps) injects secrets from a trusted vault at deployment time.
Use environment variables when:
The deployment target doesn't support managed identity or vault access
Secrets are injected by an orchestrator from a trusted source
Configuration values are environment-specific but not highly sensitive
You need maximum portability with no external dependencies
Do not use environment variables as your primary production secrets strategy. They lack audit trails, rotation automation, and centralised access controls. They're a transport layer, not a vault.
Azure Key Vault β The Enterprise Standard
Azure Key Vault is the right answer for production secrets in Azure-hosted .NET applications, and increasingly the right answer for any enterprise workload regardless of cloud. It provides a centralised, auditable, access-controlled store for secrets, certificates, and cryptographic keys.
The integration with ASP.NET Core's configuration pipeline is direct: the Key Vault configuration provider loads secrets into the standard IConfiguration interface at application startup. From the application's perspective, the source of a configuration value is transparent β the same code works whether the value comes from appsettings.json, environment variables, or Key Vault.
The security model is what separates Key Vault from the alternatives. Access is controlled via Azure RBAC and managed identities, which means applications don't need credentials to retrieve secrets β the Azure platform authenticates the workload directly. Every access event is logged in Azure Monitor and can be streamed to a SIEM. Secrets can be versioned, and rotation can be automated with event-driven notifications via Azure Event Grid.
For compliance-sensitive workloads, Key Vault also supports hardware security modules (HSMs) for key storage, satisfying requirements that software-based key management cannot.
Use Azure Key Vault when:
The application runs in Azure (App Service, AKS, Azure Container Apps, Azure Functions)
You need audit logs of secret access for compliance
Secrets require automated rotation
Access must be role-based with no shared credentials
The workload handles sensitive customer data or falls within a regulatory scope
Decision Matrix: Which Approach for Which Environment
| Environment | Recommended Approach | Why |
|---|---|---|
| Local development | User Secrets | Isolated to developer machine, never in version control |
| CI/CD pipelines | Environment variables (injected from vault) | Portability; secrets sourced from a trusted system |
| Staging | Azure Key Vault (separate vault) | Consistent with production model; isolated secrets |
| Production | Azure Key Vault | Audit logs, RBAC, rotation, compliance |
| Non-Azure production | HashiCorp Vault / AWS Secrets Manager | Same governance principles, different provider |
The pattern that causes the most damage is using User Secrets or hardcoded appsettings values in CI/CD pipelines. Pipelines are shared infrastructure. A secret stored in a pipeline variable without a proper injection mechanism becomes a shared credential with no revocation path.
Common Governance Mistakes .NET Teams Make
Using the same secret values across environments. Development, staging, and production should have distinct credentials for every service. If a developer accidentally exposes their local database password, it should not also be the staging database password.
Storing secrets in appsettings.json committed to source control. This is still common, especially in older codebases. Any secret that has ever been committed to a git repository must be treated as compromised and rotated, regardless of whether it was later removed.
Injecting long-lived credentials into containers. Container images built with embedded credentials are a significant risk. Container runtimes should retrieve secrets at startup from a vault using managed identity, not from environment variables baked into image manifests.
No rotation policy. Secrets that never rotate are permanent risks. Teams that rely on Azure Key Vault but never configure rotation are using a vault as a glorified configuration file. Rotation should be automated for database credentials and API keys wherever the target service supports it.
Treating all secrets with the same sensitivity level. Not all secrets are equal. A public API key for a telemetry service has a different risk profile than a database master password or a signing certificate. Governance frameworks should tier secrets by sensitivity and apply proportionally stricter controls to higher-risk values.
Frequently Asked Questions
Q: Can I use User Secrets in a Docker container for local development?A: Yes, but with caveats. The User Secrets JSON file is stored at a path tied to the developer's local machine. When running in Docker, you need to mount that path explicitly into the container, or use environment variables passed at runtime. For team-wide Docker development environments, a local secrets manager or environment variable injection from a .env file (excluded from version control) is often more practical.
Q: Does Azure Key Vault work outside Azure?A: Yes. Key Vault is accessible over HTTPS from anywhere. However, the managed identity authentication model β which eliminates credential dependencies β only works for workloads running on Azure. Non-Azure workloads authenticate using a service principal with a client ID and secret, which reintroduces the credential management problem. For non-Azure production environments, HashiCorp Vault or AWS Secrets Manager offer comparable capabilities with native managed identity support.
Q: How should secrets be handled in GitHub Actions pipelines?A: Store secrets in GitHub Actions Secrets (repository or organisation level), which are injected as environment variables at runtime and masked in logs. For Azure-hosted deployments, use OIDC-based federated identity to authenticate the pipeline to Azure without any static credentials. Retrieve production secrets from Key Vault during deployment rather than storing them in the pipeline configuration.
Q: What is the right way to rotate database connection strings in production without downtime?A: Azure Key Vault supports secret versioning. Rotate the credential at the database level first, then update the secret in Key Vault. Configure your application to reload the Key Vault configuration provider on a scheduled interval or use Key Vault references in Azure App Service, which auto-reload when a new secret version is published. For zero-downtime rotation, maintain two active credential versions briefly during the transition.
Q: Should staging use the same Key Vault as production?A: No. Staging and production should use separate Key Vault instances with separate access policies. This prevents a misconfigured staging workload from accessing production secrets, and it allows you to rotate staging credentials independently. It also makes it easier to grant developers read access to staging secrets without exposing production credentials.
Q: Is it safe to log configuration values at startup for debugging purposes?A: No. Logging configuration at startup is a common debugging pattern, but it will expose secrets to log sinks, dashboards, and anyone with log access. If you need to verify that a secret is loaded correctly, log its presence (whether it's null or non-null) rather than its value. Configure structured logging to redact known sensitive field names.
Choosing the right secrets management approach for each environment is not a one-time architectural decision β it's an ongoing operational practice. The teams that handle this well share a common trait: they treat secrets with the same governance rigour they apply to access credentials and financial controls. For .NET teams on Azure, the path is clear: User Secrets for local development, environment variable injection for pipelines, and Azure Key Vault for everything that runs in a hosted environment. The investment in that infrastructure pays for itself the first time it prevents a breach.




