I'm always excited to take on new projects and collaborate with innovative minds.
Learn how to securely connect a .NET application to Azure Key Vault using Microsoft Entra ID, a client secret, Azure RBAC, and the Azure SDK, with production-ready security and configuration practices.
If you're building a .NET application, you'll eventually run into the question: where should I keep passwords, API keys, connection strings, and other sensitive configuration?
Putting them directly in appsettings.json is easy, but it's also a bad idea once your application moves beyond your local machine.
That's where Azure Key Vault comes in.
Azure Key Vault allows you to store sensitive information in Azure and retrieve it securely from your application when you need it.
In this article, I'll walk through how to connect a .NET application to Azure Key Vault using a Microsoft Entra ID application registration and client secret.
We'll start from the Azure Portal and end with actual C# code that retrieves a secret from Key Vault.
A quick production note: If your application is running on Azure, you should seriously consider using Managed Identity instead of a client secret. Managed Identity removes the need to store and rotate a client secret. I'll explain this toward the end of the article.
Before jumping into the Azure Portal, let's understand the flow.
Suppose we have a .NET application and a Key Vault containing a database connection string.
The application needs to authenticate itself before Azure Key Vault will allow it to read anything.
The basic flow looks like this:
.NET Application
|
| Client ID + Client Secret
v
Microsoft Entra ID
|
| Access Token
v
Azure Key Vault
|
| Secret
v
.NET Application
There are two different things happening here.
First, Microsoft Entra ID authenticates the application. In other words, it confirms that the application is who it says it is.
Then, Azure Key Vault authorizes the application. It checks whether that application actually has permission to read the requested secret.
This distinction is important because having a valid client ID and client secret does not automatically give your application access to Key Vault.
The first thing we need is an identity for our application.
Go to the Azure Portal and search for:
Microsoft Entra ID
Then select:
App registrations → New registration
Give the application a meaningful name.
For example:
KeyVault-DotNet-App
For a typical application that is only going to be used inside your organization's Azure tenant, you can select:
Accounts in this organizational directory only
Then click Register.
Once the application is created, you'll land on the application's Overview page.
There are two values here that we'll need later:
Application (client) ID
Directory (tenant) ID
They'll look something like:
Application (client) ID:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Directory (tenant) ID:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Keep these values handy.
The client ID identifies your application, while the tenant ID identifies your Microsoft Entra tenant.
Now we need a credential that the application can use to authenticate with Microsoft Entra ID.
From the App Registration, go to:
Certificates & secrets → Client secrets
Click:
New client secret
Give it a useful description.
For example:
KeyVault-Production-Access
You'll also be asked to select an expiration period.
Don't choose an expiration period without thinking about it.
A client secret is a credential, and once it expires, your application won't be able to authenticate anymore.
In production, you should have a proper process for rotating credentials before they expire.
For now, select an appropriate expiration based on your organization's policy and click Add.
After creating the secret, Azure will show you something similar to:
Description Secret ID Value Expires
Copy the value immediately.
The value is only shown when the secret is created.
You might see both:
Secret ID
and:
Value
You need the Value.
Don't accidentally copy the Secret ID.
If you leave the page and come back later, Azure won't show you the value again. If you've lost it, you'll need to create a new client secret.
And please don't commit this value to Git.
Treat it exactly like a password.
Next, let's create the Key Vault that will actually contain our secrets.
In the Azure Portal, search for:
Key Vaults
Click Create.
Choose your:
For example, you might call it:
mycompany-production-kv
Azure will give the vault a URI similar to:
https://mycompany-production-kv.vault.azure.net/
We'll use this URI from our .NET application.
Once the Key Vault has been created, open it.
Go to:
Objects → Secrets
Click:
Generate/Import
For the secret name, let's use:
MyDatabaseConnectionString
For the value, imagine we have something like:
Server=production-sql.database.windows.net;
Database=Orders;
User ID=application_user;
Password=********;
Obviously, don't use an actual production password in a blog post or test environment.
Click Create.
Our Key Vault now contains a secret called:
MyDatabaseConnectionString
The next step is making sure our application is actually allowed to read it.
This is a step that often gets missed.
Creating the App Registration doesn't automatically give it access to Key Vault.
We need to explicitly grant access.
Azure Key Vault can use different authorization models, but for new deployments, Azure RBAC is generally the recommended approach.
Open your Key Vault and go to:
Access control (IAM)
Click:
Add → Add role assignment
For an application that only needs to read secret values, look for:
Key Vault Secrets User
This is important.
Don't give the application something like Owner or Contributor just because it makes the problem go away.
If the application only needs to read secrets, give it permission to read secrets.
That's the principle of least privilege.
Next, select the application we created earlier.
Depending on what you're looking at in the Azure Portal, you may see the application's service principal rather than the App Registration itself.
Select your application and complete the role assignment.
The end result is essentially:
KeyVault-DotNet-App
|
| Key Vault Secrets User
v
MyCompany-Production-KeyVault
Now the application has permission to read secrets.
If you're working with an older Key Vault, you might see Access policies instead of Azure RBAC.
In that case, go to:
Key Vault → Access policies
Create a new access policy and under Secret permissions, grant the permissions your application needs.
For a simple read scenario, that might be:
Get
List
Then select the application.
Access policies are still supported, but Azure RBAC is the preferred approach for new Key Vault deployments.
Now let's move to the .NET side.
I'm going to assume we're working with a modern .NET application.
The first thing we need are two NuGet packages.
Run:
dotnet add package Azure.Identity
dotnet add package Azure.Security.KeyVault.Secrets
Azure.Identity contains the authentication classes we'll use.
Azure.Security.KeyVault.Secrets contains the client we use to communicate with Key Vault.
The two classes we'll mainly work with are:
ClientSecretCredential
and:
SecretClient
We need four pieces of information:
Vault URI
Tenant ID
Client ID
Client Secret
We could put all of them in appsettings.json, but don't put a real client secret in source control.
For example, your appsettings.json can contain:
{
"KeyVault": {
"VaultUri": "https://mycompany-production-kv.vault.azure.net/",
"TenantId": "",
"ClientId": "",
"ClientSecret": ""
}
}
The sensitive values should come from a secure configuration source.
For local development, .NET User Secrets is a good option.
For example:
dotnet user-secrets init
Then:
dotnet user-secrets set "KeyVault:TenantId" "your-tenant-id"
dotnet user-secrets set "KeyVault:ClientId" "your-client-id"
dotnet user-secrets set "KeyVault:ClientSecret" "your-client-secret"
Another option is environment variables.
For example:
KeyVault__TenantId
KeyVault__ClientId
KeyVault__ClientSecret
The double underscore is important because ASP.NET Core uses it to represent nested configuration.
So:
KeyVault__ClientId
maps to:
KeyVault:ClientId
Now we get to the interesting part.
The Azure SDK provides a class specifically for this scenario:
ClientSecretCredential
We can create one like this:
var credential = new ClientSecretCredential(
tenantId,
clientId,
clientSecret);
At this point, we have an Azure credential that can authenticate our application with Microsoft Entra ID.
We can then use that credential to create a SecretClient.
var secretClient = new SecretClient(
new Uri(vaultUri),
credential);
That's all the setup we need.
Now let's actually retrieve our database connection string.
The Key Vault secret is called:
MyDatabaseConnectionString
So we can do:
var secret = await secretClient.GetSecretAsync(
"MyDatabaseConnectionString");
The value is available through:
secret.Value.Value
Notice that we're using the returned KeyVaultSecret object.
Here's the complete example:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.AddUserSecrets<Program>(optional: true)
.Build();
var vaultUri = configuration["KeyVault:VaultUri"]
?? throw new InvalidOperationException(
"Key Vault URI is not configured.");
var tenantId = configuration["KeyVault:TenantId"]
?? throw new InvalidOperationException(
"Tenant ID is not configured.");
var clientId = configuration["KeyVault:ClientId"]
?? throw new InvalidOperationException(
"Client ID is not configured.");
var clientSecret = configuration["KeyVault:ClientSecret"]
?? throw new InvalidOperationException(
"Client secret is not configured.");
var credential = new ClientSecretCredential(
tenantId,
clientId,
clientSecret);
var secretClient = new SecretClient(
new Uri(vaultUri),
credential);
try
{
var secret = await secretClient.GetSecretAsync(
"MyDatabaseConnectionString");
var connectionString = secret.Value.Value;
Console.WriteLine("Secret retrieved successfully.");
// Use connectionString here.
}
catch (Azure.RequestFailedException ex)
{
Console.Error.WriteLine(
$"Key Vault request failed. Status code: {ex.Status}");
throw;
}
One thing you'll notice is that we're not printing the secret value.
That's deliberate.
This might seem obvious, but it's surprisingly easy to accidentally leak secrets through logging.
Don't do this:
Console.WriteLine(secret.Value.Value);
And don't do this either:
logger.LogInformation(
"Database connection string: {ConnectionString}",
connectionString);
Your logs might be stored in:
Once a secret gets into a centralized logging system, it can be very difficult to remove completely.
Instead, log something harmless:
logger.LogInformation(
"Database connection string retrieved successfully.");
The rule is simple:
Log that you retrieved the secret, not the secret itself.
If you're working with ASP.NET Core, you probably don't want to create a SecretClient every time you need a secret.
Register it with dependency injection instead.
For example, in Program.cs:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
var builder = WebApplication.CreateBuilder(args);
var vaultUri = builder.Configuration["KeyVault:VaultUri"]
?? throw new InvalidOperationException(
"Key Vault URI is not configured.");
var tenantId = builder.Configuration["KeyVault:TenantId"]
?? throw new InvalidOperationException(
"Tenant ID is not configured.");
var clientId = builder.Configuration["KeyVault:ClientId"]
?? throw new InvalidOperationException(
"Client ID is not configured.");
var clientSecret = builder.Configuration["KeyVault:ClientSecret"]
?? throw new InvalidOperationException(
"Client secret is not configured.");
var credential = new ClientSecretCredential(
tenantId,
clientId,
clientSecret);
builder.Services.AddSingleton(
new SecretClient(
new Uri(vaultUri),
credential));
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
Now any service can request SecretClient through dependency injection.
For example:
public class PaymentService
{
private readonly SecretClient _secretClient;
public PaymentService(SecretClient secretClient)
{
_secretClient = secretClient;
}
public async Task<string> GetPaymentApiKeyAsync()
{
var secret = await _secretClient.GetSecretAsync(
"PaymentApiKey");
return secret.Value.Value;
}
}
This is a much cleaner approach for an ASP.NET Core application.
There's another issue worth thinking about.
Imagine an API endpoint that receives hundreds or thousands of requests.
You don't want your code doing this on every request:
HTTP Request
↓
GetSecretAsync()
↓
Azure Key Vault
↓
Return secret
If the secret doesn't change frequently, cache it appropriately.
A more sensible approach is:
Application
|
v
Key Vault
|
v
Secret
|
v
Application Cache
The exact caching strategy depends on what you're storing and how frequently it needs to rotate.
For example, a database password that changes once every few months doesn't need to be fetched from Key Vault for every HTTP request.
When you're setting this up for the first time, errors from Azure can be confusing.
Here are the common ones.
Usually means the application couldn't authenticate.
Check:
This usually means authentication worked, but the application isn't authorized to access the Key Vault.
In other words:
Microsoft Entra ID
|
| Authentication successful
v
Azure Key Vault
|
| Access denied
v
403
Check the Key Vault role assignment.
Make sure the role was assigned to the correct application/service principal.
For a read-only secret scenario, verify that the application has an appropriate Key Vault secrets role such as:
Key Vault Secrets User
This can mean the vault or secret doesn't exist at the location you're requesting.
Check:
For example, this:
await secretClient.GetSecretAsync("PaymentApiKey");
requires a secret named exactly as expected.
This is probably the biggest operational downside of using a client secret.
Client secrets expire.
Imagine everything is working perfectly today.
Then six months later:
Client Secret
↓
Expired
↓
Authentication fails
↓
Application can't access Key Vault
That's not something you want to discover because your production application just started throwing authentication errors.
Have a rotation process.
A typical approach is:
Existing secret
|
v
Create new secret
|
v
Store new credential securely
|
v
Deploy application
|
v
Verify application
|
v
Remove old secret
Don't wait until the old secret expires.
Ideally, expiration should be monitored and treated as an operational event.
I would also avoid using the same Key Vault for everything.
For example:
Development
↓
company-dev-kv
Test
↓
company-test-kv
Production
↓
company-prod-kv
This gives you a much better security boundary.
You don't want a developer's local application accidentally reading production database credentials simply because both environments happen to use the same vault.
The same principle applies to application identities.
Keep production access tightly controlled.
Authentication and authorization aren't the whole security story.
You should also consider how your application reaches Key Vault.
Depending on your architecture and security requirements, you may want to look at:
For highly sensitive production workloads, a private endpoint can allow traffic to Key Vault through a private network path rather than exposing the service through a broadly accessible network route.
The exact setup depends heavily on where your application is hosted.
Now let's talk about something important.
If you're deploying this .NET application to Azure, I wouldn't automatically choose client-secret authentication.
Why?
Because the client secret becomes another credential that your team has to:
Azure has a better option for many scenarios:
Managed Identity.
With Managed Identity, your Azure resource gets an identity in Microsoft Entra ID.
Your application can then authenticate without you storing a client secret.
The code can become as simple as:
var credential = new DefaultAzureCredential();
var secretClient = new SecretClient(
new Uri(vaultUri),
credential);
The application still needs permission to access Key Vault.
You would assign the appropriate RBAC role to the managed identity, such as:
Key Vault Secrets User
But there is no client secret sitting in:
appsettings.json
or:
Environment Variables
or:
CI/CD Secrets
That's a significant improvement.
Client-secret authentication isn't useless.
There are situations where it makes sense.
For example:
In those cases, Client-Secret Credential is a perfectly valid option.
Just make sure you treat the client secret as a sensitive credential and have a rotation strategy.
If you're hosting an ASP.NET Core application in Azure App Service, you can also look at Key Vault references.
This lets App Service retrieve values from Key Vault and expose them to your application through configuration.
For example, an App Service setting can reference a Key Vault secret rather than containing the secret value directly.
Combined with Managed Identity, the architecture becomes:
ASP.NET Core Application
|
v
Azure App Service
|
| Managed Identity
v
Azure Key Vault
|
v
Secret
This can be a very clean solution when your application simply needs secrets as configuration values.
Before calling this integration production-ready, I'd check all of these:
Azure.Identity installedAzure.Security.KeyVault.Secrets installedSecretClient is reusedConnecting a .NET application to Azure Key Vault isn't particularly difficult.
The actual code is only a few lines:
var credential = new ClientSecretCredential(
tenantId,
clientId,
clientSecret);
var secretClient = new SecretClient(
new Uri(vaultUri),
credential);
var secret = await secretClient.GetSecretAsync(
"MyDatabaseConnectionString");
But the code is the easy part.
The important part is everything around it.
You need to make sure the application is properly registered in Microsoft Entra ID, the Key Vault permissions are correctly configured, credentials aren't accidentally exposed, secrets aren't logged, and client-secret expiration and rotation are handled before they become production incidents.
And if you're running the application on Azure, take a serious look at Managed Identity + Azure RBAC before settling on client-secret authentication.
The goal isn't just to make the application successfully read a secret from Key Vault.
The goal is to build an authentication and secret-management setup that continues to be secure and maintainable six months or two years from now.
Your email address will not be published. Required fields are marked *