Accessing Azure SQL Connection String from Azure Key Vault in ASP.NET Core API

In this article, we will see the use of Azure Key Vault access in ASP.NET Core application. Azure Key Vault is a cloud service for securely storing and accessing secrets. A secret is anything that we want to tightly control access to, such as Database Connection Strings, API Auth keys, passwords, certificates, cryptographic keys, etc. The Azure Key Vault helps to solve problems as follows:

  • Secrets Management - It can be used to Securely store and tightly control access to tokens, passwords, certificates, API keys, and other secrets like Database Connection String
  • Key Management - It can be used as a Key Management solution. Azure Key Vault makes it easy to create and control the encryption keys used to encrypt the data of the Application.
  • Certificate Management - It allows an easy provision, manage, and deploy public and private Transport Layer Security/Secure Sockets Layer (TLS/SSL) certificates for use with Azure. 

 More information of Azure Key Vault can be read from this link.

This article is divided into two sections, in the first section we will see the creation of Azure Key Vault and adding secret in it. In the second section we will see the access of the Azure Key Vault in ASP.NET Core Application. 

Creating Azure Key Vault and adding secret in it

Step 1: To create Azure Key Vault, we need the Azure Subscription. You can get the same from this link. Login using your subscription. Once you login, go to Microsoft Entra ID and from the Properties option, enable the Access management for Azure resources as shown in Figure 1



Figure 1: The Access Management for the Subscription

This is the important step for the implementation. Why? The reason is that although we can the Azure Key Vault, we need to provide its access to the User and the Application so that Secret from the Azure Key Vault can be accessed.

Step 2: Create a resource group from the resources by filling the required information as shown in Figure 2.



Figure 2: Creating a resource group

We will create the Azure Key Vault in this resource group.

Step 3: Create a Key Vault as shown in Figure 3



Figure 3: Creating a Kay Vault

While creating a Key Vault, we must think about the Soft-delete behavior. This behavior provides a mechanism for recovering the deleted object, essentially undoing the deletion. The default time period is 90-days. Once soft-delete is enabled on a key vault it can't be disabled. The retention policy interval can only be configured during key vault creation and can't be changed afterwards. We can change this from 7 to 90 days.  

Purge protection is an optional Key Vault behavior. This is not enabled by default. Purge protection can only be enabled once soft-delete is enabled. This is recommended when using keys for encryption to prevent data loss. All those Azure services that integrate with Azure Key Vault, e.g. Storage, require purge protection to prevent data loss. When purge protection is on, a vault or an object in the deleted state can't be purged until the retention period has passed.  

Click on Next button, the Access Configuration page will be displayed, on this page we need to define Permissions to access the Azure Key Vault by users as well as by applications. The default permission is Azure Role-Based Access Control (RBAC). On the Networking page select the Enable Public access and provide access from All Networks

Click on Review and Create button to create a Key Vault.

Step 4: Once the Key Vault is created, we need to store secrets in it. In this case, we will be storing Azure SQL Connection String in it. Create a database in Azure SQL and Copy its Connection String.  Click on Secrets on its Property pan as shown in Figure 4



Figure 4: The Secrets

To generate new secret, click on the Generate/Import button, this will open the Create a secret page where we can need to enter Upload Options, Name, Secret Value, etc. Figure 5 shows the secret details those can be stored in Key Vault. Make sure that the Name is unique. Set the Azure SQL Connection String as a Secret vale as shown in Figure 5. 



Figure 5: Create a secret

Click on the Create button.

Once the Secret is creates, we need to make sure that the role assignment is configured to access the Azure Key Vault. Click on the Access control (IAM) and click on Add role assignment button as shown in Figure 6  



Figure 6: Access control to add role assignment

Once the Add role assignment button is clicked, on the next page we can grant access to Azure resources based on the job function e.g. Key Vault Administrator, Key Vault Reader, Key Value Secret Officer as shown in Figure 7



Figure 7: The Job function role

Once roles for Azure Key Vault are selected, click on member and select the member who can use the Key Vault role as shown in Figure 8, on this page we need to click on Select members link to select Group or applications to those we want to assign role.



Figure 8: Members to assign role  

On this page we will be selecting applicationaccess group. This group will make sure that the application running on the development machine will be able to access Key Vault based on roles assignment, click on the Select button to save the member and then click on the Review and Create button.

Once the Access Control is set, we need to provide the Access Permission to our application so that it can access secret from the Azure Key Vault.  Click on the Access configuration of the Key Vault and select the Value access policy as shown in Figure 9



Figure 9: Access Configuration 

By selecting Value access policy, we can grant access permission to the application or user group to perform different operations on Secret. This is the most important step; this step will make sure that the Azure Key Vault is accessible to our application. Click on the Go to access policies, it will open the Access Policies page where we can assign policies to access the Secret from the application like  Get, List, etc.  Figure 10, shows the Access Policies Page as shown in Figure 10



Figure 10: The Access Policies Page

On this page click on Create button, this will open the Create an access policy page, on this page select permissions as per the need as shown in Figure 11



Figure 11: The Select Permission to assign permission.

Once permissions are set then click on Next button, the Principal tab is opened where we can select the principal, a group or application that access the Azure Key Vault, since we will be accessing the Key Vault in the developer machine local application, the applicationaccess group principal is selected as shown in Figure 12



Figure 12: The Principal
  
Click on Next and then finally click on Review and Create button to complete the Access Policy, once the policies are set the Access Policies will display details as shown in Figure 13



Figure 13: The Access Policies
 
So far, we have created Azure Key Vault and assigned Access Policies to it. 

Creating ASP.NET Core API Project to access the Azure Key Vault

Step 5: Open Visual Studio 2022 and create a new ASP.NET Core API project. Name this project as API_Azure_KeyVault. In this project, add following packages.

  • Azure.Security.KeyVault.Secrets
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Relational
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tool
  • Microsoft.EntityFrameworkCore.Design
Since we be using Entity Framework Core, we need Entity Framework Core packages. To access Azure Key Vault, we need the Azure.Security.KeyVault.Secrets package. In the Azure SQL Database named eShoppingCodi. In this database, we have the Table as shown in Listing 1

CREATE TABLE [dbo].[ProductInfo](
[ProductRowId] [int] IDENTITY(1,1) NOT NULL,
[ProductId] [varchar](100) NOT NULL,
[ProductName] [varchar](200) NOT NULL,
[CategoryName] [varchar](100) NOT NULL,
[Manufacturer] [varchar](200) NOT NULL,
[Description] [varchar](400) NOT NULL,
[BasePrice] [int] NOT NULL,
PRIMARY KEY CLUSTERED 
(
[ProductRowId] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY],
UNIQUE NONCLUSTERED 
(
[ProductId] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
Listing 1: The Table in Azure SQL Database

Step 6: In the appsettings.json, add the URI for the Azure Key Vault so that the ASP.NET Core API will be able to access it. Listing 2, shows the configuration

"KeyVault": {
  "VaultUri": "https://wsit0031133333333.vault.azure.net/"
}
Listing 2: The Azure Key Vault URI

Step 6: Open the Command Prompt and navigate to the API Project path we have created in Step 5, and use the Command shown in Listing 3 to generate Models Entities using Entity Framework Core
dotnet ef dbcontext scaffold "Server=tcp:myserver.database.windows.net,1433;
Initial Catalog=eShoppingCodi;Persist Security Info=False;User ID=MyAdmin;
Password={your_password};MultipleActiveResultSets=False;Encrypt=True;
TrustServerCertificate=False;Connection Timeout=30;" 
Microsoft.EntityFrameworkCore.SqlServer -o Models


Listing 3: The CLI command
 
This command will add a new folder named Models, in this folder the ProductIndo.cs and  EShoppingCodiContext.cs class files will be added with ProductInfo and EShoppingCodiContext class respectively in them. 

Step 7: This is the most important steps in the project. In this step we will be adding logic for accessing the Azure Key Vault. In this project, add a new folder named Repositories. In this folder add a new interface file named IKeyVaultManager.cs file. In this file we will add code for IKeyVaultStoreManager interface as shown in Listing 4

public interface IKeyVaultStoreManager
{
    public Task<string> GetVaultSecret(string secretName);
}

Listing 4: The IKeyVaultStoreManager interface

In the same folder, add a new class file named KeyVaultManager.cs. In this file we will add code for KeyVaultManager class that implements IKeyVaultStoreManager  interface. This class contains code for reading the secret from the Azure Key Vault based on the secret name passed to the GetValueSecret() method. The code for this class is shown in Listing 5

using Azure.Security.KeyVault.Secrets;

namespace API_Azure_KeyValue.Repositories
{
    public class KeyVaultStoreManager : IKeyVaultStoreManager
    {
        private readonly SecretClient _secretClient;
        public KeyVaultStoreManager(SecretClient secretClient)
        {
            _secretClient = secretClient;
        }
        public async Task<string> GetVaultSecret(string secretName)
        {
            try
            {
                KeyVaultSecret keyValueSecret = await
                _secretClient.GetSecretAsync(secretName);
                return keyValueSecret.Value;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}


Listing 5: The KeyVaultManager  class 

As shown in Listing 5, the KeyVaultStoreManager class uses is constructor injected with the SecretClient class. This class is used to manage Synchronous and Asynchronous methods to manage secrets in the Azure Key Vault. The SecretClient class contains GetSecretAsync() method. This method accepts the Secret Name as string and returns KeyVaultSecret resource object that contains the value of the secret.
 
Step 8: To read data from the Azure SQL Database we will use the Entity Framework Core object model. To read the data, in the Repositories folder add a new class file named ProductInfoRepo.cs. In this file we will add code for ProductInfoRepo class. This class is constructor injected with IKeyVaultStoreManager interface and EShpppingCodeContext classes. The IKeyVaultStoreManager interface will be used to read the secret value from the Azure Key Vault and the EShpppingCodeContext  class is used to read data from Azure SQL Database. The code for the ProductInfoRepo class is shown in Listing 6.

using API_Azure_KeyValue.Models;
using Microsoft.EntityFrameworkCore;

namespace API_Azure_KeyValue.Repositories
{
    public class ProductInfoRepo
    {
        IKeyVaultStoreManager mgr;
        EShoppingCodiContext ctx;

        public ProductInfoRepo(IKeyVaultStoreManager mgr, EShoppingCodiContext ctx)
        {
            this.mgr = mgr;
            this.ctx = ctx;
        }

        public async Task<List<ProductInfo>> GetProducts()
        {
            return await ctx.ProductInfos.ToListAsync();
        }
    }
}


Listing 6: The ProductInfoRepo class

Step 9: In the Program.cs, add the code for registering various dependencies for the application like AzureFactoryClientBuilder class. This class is used to create a builder type to Azure SDK Client so that we can use the current application as a client for Azure to access its secrets. We will also register the IKeyVaultStoreManager, the EShoppingCodeContext and the ProductInfoRepo in the dependency container. Listing 7 shows the code for registration.


//1. Register AzureFactoryClientBuilder class, to read 
// section from appsettings.json so that we will be able to
// create a registeration for Azure SDK clients.
builder.Services.AddAzureClients(azureClientFactoryBuilder =>
{
    azureClientFactoryBuilder.AddSecretClient(builder.Configuration.GetSection("KeyVault"));
});
//2. Register IKeyVaultStoreManager, KeyVaultStoreManager
builder.Services.AddSingleton<IKeyVaultStoreManager, KeyVaultStoreManager>();
builder.Services.AddDbContext<EShoppingCodiContext>(options => 
{
    // Read the ConnectionString using the GetVaultSecret() method of  IKeyVaultStoreManager
    IServiceProvider serviceProvider = builder.Services.BuildServiceProvider();
    var service = serviceProvider.GetService<IKeyVaultStoreManager>();
    var connectionString = service.GetVaultSecret("connstr").Result;
    options.UseSqlServer(connectionString);
});
builder.Services.AddTransient<ProductInfoRepo>();
Listing 7: Dependency Registration of dependencies

As shown in Listing 7, we have registered the IKeyVaultStoreManager and KeyVaultStoreManager in the dependency container, but in fact we need to use these types to access the secret, the Azure SQL database connection string from Azure Key Vault so that we can use it for instantiating the  EShoppingCodeContext. We have used the IServiceProvider to access instance of KeyVaultStoreManager from the dependency container using IKeyVaultStoreManager so that we can retrieve the Azure Key Vault secret using the secret name connstr. Using the secret name we will be retereving the connection strig so that we can create an instance of the EShoppingCodeContext class for accessing Azure SQL Database. This is hoe ASP.NET Core application access the Aure Key Vault. 

Step 10: Modify the Program.cs to create a minimal API to access products as shown in Listing 8


.....
app.MapGet("/api/products", async (ProductInfoRepo repo) =>
{
    return await repo.GetProducts();
});
......
Listing 8: Minimal API


Run the application, from the Swagger Test Page, make request to the API, the Product Data will be returned as shown in Figure 14



Figure 14: The Response
  
 The code for this article can be downloaded from this link.

When the Application is published on Azure, we need to make sure that the Object ID aka Principal ID is copied from the published Web app and add it into the Access Policy for the Azure Key Vault as shown in Figure 12. The Figure 15 shows the page from where we can copy the Object ID for the published App. This will make sure that the published Application will be able to access the Azure Key Vault. 


 
Figure 15: Copy Object ID
 
       
Conclusion: It is highly recommended that we must store all crucial information like database connection string and other secrets in Azure Key Vault.  
 

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach