
In my previous post, we explored how .NET Aspire simplifies the orchestration of a distributed application, streamlining our local development experience. We saw how it manages multiple projects and services, allowing us to focus on coding rather than configuration. But what happens when our application needs to access sensitive information like database connection strings or API keys? Storing secrets in source control or configuration files constitutes a major security risk.
The best practice for managing secrets is to use a dedicated secret store like Azure Key Vault — but this raises a challenge: how can your local development environment securely fetch secrets from an existing vault without disrupting your workflow? To bridge this gap, we’ll leverage .NET Aspire’s orchestration to wire Key Vault values directly into environment variables. This approach avoids the “it works on my machine” problem by using a realistic and secure configuration. This guide assumes your Azure infrastructure—including the Key Vault and its secrets—is already provisioned.
In the sections that follow, we’ll cover prerequisites, wiring your vault into Aspire, configuring AppHost, and viewing the final Program.cs
Prerequisites: Setting Up .NET Aspire with Azure Key Vault
The following prerequisites must be in place before continuing.
- Secrets already exist in Azure Key Vault. (This guide focuses on integrating with existing resources, not creating new ones.)
- Azure CLI is installed (download from Microsoft Docs) (.NET Aspire uses the Azure CLI for authentication and subscription access.)
- Logged into your Azure subscription via
az login
and set to correct subscription withaz account set
. (This ensures Aspire has the necessary permissions to fetch secrets and manage roles.)
If you are new to Aspire or don’t have a .NET solution with Aspire orchestration to work with, you can refer to my previous post — Diving into .NET Aspire: Simplifying Distributed Application Development. The base .NET solution that I use for the demonstration in this post can be downloaded from https://github.com/codeygrove-learning-repo/AspireLearning from ForDemo
branch.
Integrating Azure Key Vault with .NET Aspire Environment Variables
While .NET Aspire supports client-side key vault integration so your application can fetch secrets at runtime, this post focuses on wiring existing Key Vault secrets into environment variables via Aspire’s orchestration.
You can read more about client integration in the official .NET Aspire Key Vault Integration documentation.
If you have an existing .NET Aspire project, update the Aspire.Hosting.AppHost
package to version 9.3 or later, which introduced a new overload of WithEnvironment(...)
that accepts an IAzureKeyVaultSecretReference
, enabling direct environment-variable wiring from key vault secrets. You can read more about this change in https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/dotnet-aspire-9.3#-use-key-vault-secrets-in-environment-variables.
If you are interested in the overview for .NET Aspire Azure integrations, you can read about it in https://learn.microsoft.com/en-us/dotnet/aspire/azure/integrations-overview.
Add Existing Azure Key Vault to .NET Aspire Project
For this demo, I will be wiring the following secrets into my existing .NET Aspire project:

First and foremost, add Aspire.Hosting.Azure.KeyVault
package by running dotnet add package Aspire.Hosting.Azure.KeyVault
to .NET Aspire project.
Once the Aspire.Hosting.Azure.KeyVault
package has been added successfully, you can add the existing Azure Key Vault resource to the application builder..
var builder = DistributedApplication.CreateBuilder(args);
var keyvault = builder
.AddAzureKeyVault("keyvault") // Aspire grants required permissions to the Azure Key Vault at runtime
.RunAsExisting("<existing-keyvault-name>", "<keyvault-resource-group>"); // Use .RunAsExisting(...) to reference an existing Key Vault; no provisioning occurs at runtime.
Since this application will be executing in the “run” mode, we use RunAsExisting
. In this mode, it assumes that the referenced Azure resource already exists and integrates with it during execution without provisioning the resource. Besides RunAsExisting
, .NET Aspire has two additional APIs to provide support for referencing existing Azure resources. They are PublishAsExisting
and AsExisting
APIs which you can read more in here and here.
Once the key vault resource has been added, next step is to reference the secrets from application environment through IAzureKeyVaultSecretReference
object. An IAzureKeyVaultSecretReference
is a powerful way to reference a secret from Azure Key Vault without directly exposing its value in your code. It acts as a pointer to a specific secret, allowing .NET Aspire to fetch the actual value at runtime and inject it into your application’s environment. .NET Aspire provide GetSecret
method to retrieve the IAzureKeyVaultSecretReference
object.
This approach is more secure and helps maintain a clean separation between your application code and sensitive configuration.
// Get IAzureKeyVaultSecretReference to each of the key vault secrets
var mongoConnectionString = keyvault.GetSecret("codeygrove-mongodb-connstr");
var orderServiceBusConnectionString = keyvault.GetSecret("codeygrove-servicebus-connstr");
var eventHubConnectionString = keyvault.GetSecret("codeygrove-eventhub-connstr");
Now that you have the secret references, you can pass them to the WithEnvironment
extension method, which will inject them into your application. Aspire’s orchestration will automatically resolve the reference, fetch the secret from Azure Key Vault, and set it as an environment variable for the specific project when it runs. On top of that, we also want to ensure that the rest of the projects will wait for the key vault resource to enter the running state before starting. Below is the excerpt of the code:
var orderApi = builder.AddProject<Projects.AspireLearning_OrderApi>("aspirelearning-orderapi")
.WithEnvironment("MongoDb__ConnectionString", mongoConnectionString)
.WithEnvironment("ServiceBus__ConnectionString", orderServiceBusConnectionString)
.WithEnvironment("Logging__LogLevel__Default", logLevel)
.WaitFor(keyvault); // wait for keyvault resource to enter the running state
If we don’t use .WaitFor(keyvault)
in the AddProject
, when Aspire project is executing, projects will enter “Failed to start” state because they attempted to run before the environment variable was populated by the IAzureKeyVaultSecretReference
object.

The key benefit here is that the actual secret value never needs to be hard-coded or stored in your local configuration files. The IAzureKeyVaultSecretReference
handles the secure retrieval and injection process for you, streamlining your development workflow while maintaining best security practices.
Configure Azure Credentials in .NET Aspire AppHost
With secrets wired in, the next step is ensuring Aspire can authenticate with Azure properly. To use Azure resources in your local development environment, you must supply the required configuration values. You can enter these values manually or launch Aspire’s interactive configuration mode (see https://learn.microsoft.com/en-us/dotnet/aspire/azure/local-provisioning#configuration).
Unfortunately, on my local machine, I encountered an authentication issue where an old credential was cached locally. This is due to multiple Azure subscriptions are used on my local machine which resulted in the Visual Studio credential provider reusing the old token from different subscription.
To resolve this issue I need to explicitly tell Aspire to use the credentials from the Azure CLI, which effectively bypasses the problematic Visual Studio credential provider (see https://github.com/dotnet/aspire/discussions/2963).
Provisioning keyvault-roles...
Error provisioning keyvault-roles.
Azure.RequestFailedException: The 'EvolvedSecurityTokenService' access token is from the wrong issuer 'https://sts.windows.net/<ID_1>/'. It must match the tenant 'https://sts.windows.net/<ID_2>/' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/<ID_2>' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later.
Status: 401 (Unauthorized)
ErrorCode: InvalidAuthenticationTokenTenant
Below is the appsettings.json configuration I used before running the Aspire AppHost to force Azure CLI
credentials. If you encounter a similar issue or prefer to use Azure CLI
as credential source, ensure you are logged into the correct Azure subscription (where the key vault resides) by running az login
.
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
},
"Azure": {
"SubscriptionId": "<subscription_id>",
"AllowResourceGroupCreation": true,
"ResourceGroup": "<keyvault_resource_group>",
"Location": "<keyvault_location>",
"CredentialSource": "AzureCli" // Use Azure CLI credentials to avoid cached token issues
}
}
Assign RBAC Permissions for Azure Key Vault in .NET Aspire
Aspire will automatically assign the Key Vault Secrets User role at runtime via the AddAzureKeyVault
call so that your AppHost can fetch existing secrets. This role assignment appears under the keyvault-roles
resource in the Aspire dashboard.
It is also important to note that the identity running Aspire must have RBAC rights (for example, Owner or User Access Administrator) on the target resource group to assign the Key Vault Secrets User role. On top of that, the role assignment is only for local orchestration, not production deployment and this need to be manually deleted since Aspire will not tear down the actual role in the Azure Portal.

If you go to the resource group in Azure portal, this activity will be logged on the “Activity log” page.

To view the details of the created role assignment, click on the activity log and copy the ID in the summary page.

We can search for this role assignment ID in the Azure key vault access control (IAM) page.

If you are interested to know more about managing role assignment through Aspire for Azure resources, refer to https://learn.microsoft.com/en-us/dotnet/aspire/azure/role-assignments.
Conclusion: Secure Secrets in .NET Aspire with Azure Key Vault
The following are the relevant changes to Aspire Program.cs
. For complete Program.cs
, go to the GitHub ForKeyVaultDemo
branch in https://github.com/codeygrove-learning-repo/AspireLearning/tree/ForKeyVaultDemo
using Microsoft.Extensions.Configuration;
var builder = DistributedApplication.CreateBuilder(args);
var keyvault = builder
.AddAzureKeyVault("keyvault")
.RunAsExisting("codeygrove-keyvault", "codeygrove-rg");
var mongoConnectionString = keyvault.GetSecret("codeygrove-mongodb-connstr");
var orderServiceBusConnectionString = keyvault.GetSecret("codeygrove-servicebus-connstr");
var eventHubConnectionString = keyvault.GetSecret("codeygrove-eventhub-connstr");
// Load configuration
var config = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
var logLevel = config["Logging:LogLevel:Default"] ?? "Information";
var orderApi = builder.AddProject<Projects.AspireLearning_OrderApi>("aspirelearning-orderapi")
.WithEnvironment("MongoDb__ConnectionString", mongoConnectionString)
.WithEnvironment("ServiceBus__ConnectionString", orderServiceBusConnectionString)
.WithEnvironment("Logging__LogLevel__Default", logLevel)
.WaitFor(keyvault); // wait for keyvault resource to be ready
// For the complete Program.cs, see GitHub ForKeyVaultDemo branch in https://github.com/codeygrove-learning-repo/AspireLearning/tree/ForKeyVaultDemo
builder.Build().Run();
By integrating your existing Azure Key Vault into .NET Aspire, you eliminate hard-coded secrets and closely mirror production-like configurations in your local development setup. While production environments often rely on managed identities for secret access, Aspire’s environment variable injection provides a secure and practical approach for local workflows. This pattern strengthens security, improves developer productivity, and reduces configuration drift. You can extend the same approach to other Azure resources—such as Storage Accounts or Service Bus namespaces—to further streamline and secure your local development environment.
Leave a Reply