Secure app development with Azure AD, Key Vault and Managed Identities

Developing applications using security best practices doesn't have to be hard. And yet, many organizations and developers struggle with this since the space is so vast and it's hard to know where to start. However, if you are developing that leverage Azure, then there are some powerful features in place to ensure that you and your team can develop secure code end to end. And for me, it's this end-to-end scenario that is done so beautifully, elegantly and effortlessly using the tools and services that you most likely already use on a daily basis. This scenario gets even stronger if you use Microsoft tools and develop on the Microsoft stack. For example, building .NET Core apps in Visual Studio and using Azure DevOps to deploy to Azure is an incredible tool chain that simplifies many security tasks.

There are 2 main prerequisites that you need to meet in order to benefit from today's blog post:

  1. You have an Azure Subscription
  2. You or your company is using Azure Active Directory

If you don't have an Azure subscription, you can get one totally free here. The Azure subscription is needed for Azure Active Directory and Key Vault which in turn are leveraged by Managed Identities. Your app can also use a totally separate Azure AD tenant (single-tenant) or can be build to run as a multi-tenant app.

I mentioned earlier that application security is a vast space so let's scope this down a bit. In this blog post we will look at how we can get rid of sensitive configuration settings or get secrets on demand from Key Vault without having to configure any connection strings to the Key Vault. In case this is the first time you hear about Key Vault, then you can refer to my previous blog posts for a quick refresher:

Wait a minute! The second blog post ^^^ seems suspiciously a duplicate...Well, sort of. You see, when I wrote the original blog post in 2017 there was a big component missing - Managed Identities. So this is why I decide to write today's blog post and show you the unique value proposition that the Microsoft toolchain brings to your development cycle.

So, let's get to it!

The ASP.NET Core Web App

Section prerequisites

If you have an existing ASP.NET Core web app you will only need to add the relevant bits to your application instead of starting from scratch. For the purpose of this example, I am creating a standard .NET Core Web App using Razor Pages targeting .NET Core 2.1. You can target a later version as well - currently, the latest is 3.1

Make sure you have .NET Core installed as per the prereqs above. You can check by opening up your favorite command prompt and running dotnet --version

If you already have an app, you can skip to the section where I explain the config settings for Azure AD.

Next, open Visual Studio 2019 and create a new ASP.NET Core project as per the steps below:



At this point I chose to add Authentication to my project as I wanted my users to be able to log in using Azure AD.

Once you configure your authentication settings, Visual Studio will scaffold your app accordingly. In particular, it will first create an Enterprise App Registration in the designated Azure AD tenant (determined by your login) and then populate the appsettings.json with the appropriate tenant and client id details


Visual Studio will also bootstrap the StartUp.cs middleware with the appropriate services and using directives to ensure that the app is ready to go as soon as you press F5

Notice that up until this point I didn't have to change any code or do anything other than log in to my tenant with the appropriate account. Visual Studio took care of everything. We can now test that everything is fine by running the app locally using Visual Studio. Press F5 and see all the magic happen....

Unfortunately, you'll notice that the current behavior is to require authentication for all pages, which is not the desired behavior. I want the home page to be accessible to everyone so to fix this, I opened the Index.cshtml.cs class and added the [AllowAnonymous] attribute at the top of the class. Make sure to add the appropriate reference either using the "Quick actions and refactorings" or using the "Ctrl + ." keyboard shortcut.

Rerun the web app to see how this solves our problem :)

The first step to add authentication and have a running web app is complete. Next we will add a KeyVault to store our sensitive application settings such as API/Storage keys, connection strings etc.

Create your Azure Key Vault

Azure Key Vault, like Storage, are a integral services for apps and infrastructure and I cannot believe how it has transformed the way I develop apps today. And before you start worrying about costs (what price do you put on security and ease of use anyway??) rest assured that it's extremely cost efficient, so much so that you should be using it across your full development lifecycle from local dev to production. Costs (dependent on region) shown below for reference

You can use whichever option you feel comfortable with (Azure CLI, PowerShell, ARM templates or the portal) to create your Azure Key Vault. In this instance, we're using the Azure CLI via the Azure Cloud Shell. In case you haven't used it before, Azure Cloud Shell is a browser-based shell experience to manage and develop Azure resources and it's available right there in the Azure Portal!

Before you run any commands, you also need to make sure that your Azure CLI context is using the right subscription.

az login
az group create --name "<YourResourceGroupName>" --location "West US"
az keyvault create --name "<YourKeyVaultName>" --resource-group "<YourResourceGroupName>" --location "West US"

Next, we will using the Azure CLI to add a secret that our application can use later.

az keyvault secret set --vault-name "<YourKeyVaultName>" --name "AppSecret" --value "MySecret"

PSA: I love how the Azure CLI allows us to check the value for each secrets

az keyvault secret show --name "AppSecret" --vault-name "<YourKeyVaultName>"

The Azure Key Vault set up is complete.

Retrieving secrets from Azure Key Vault in code - the easy way

The beauty of the tight integration between Azure AD, Visual Studio and Key Vault is that we don't have to do much in our code to be able to pull secrets from Key Vault because Managed Identities take care of everything securely behind the scenes. And it works seamlessly between multiple environments, from the local dev box to production without the need to change the code or add directives to code different behaviors depending on the target environment. So the benefits of Managed Identities extend beyond the developer space to encompass teams across Dev + Ops + Security.

To start working with Key Vault and Managed Identities, we need to add the appropriate NuGet packages to our project. Open the NuGet Manager Console and type the following:

install-package Microsoft.Azure.KeyVault;
install-package Microsoft.Azure.Services.AppAuthentication


You can check that the packages were installed successfully by expanding the "Dependencies/NuGet" folder:

Next, I will add a new Razor page to my web app where I will implement the code that pulls the Azure Key Vault secret using a Managed Identity, i.e. without configuring a connection string for the Key Vault.

Right-click on the Pages folder and select Add -> Razor Page

Select the simple Razor Page and give it a meaningful name:


Open the KeyVaultSecrets.cshtml.cs class and add the following code

In 3 lines of code (12-14), we're able to retrieve the value of the KVSercret (typo much??) secret and add it to our page model to display in the front end. Notice how nowhere in the code do I need to instantiate the KeyVaultClient using a connection string or key!

Press F5 and see the magic happen!

Awesome! At this point, you may wonder how we managed to achieve this, especially since I haven't touched Azure AD yet and I haven't created an App Registration. Visual Studio is using the account I logged in with (In VS) to authenticate against Azure Key Vault via Azure AD. As long as the account that is logged in to Visual Studio has permissions to access the Key Vault, then I can run the code in Visual Studio without having to provide any credentials. So what would happen if I were to use:

  • A different account to log it to Visual Studio?
  • An account that is not configured to access Azure Key Vault?
  • I was logged off from Visual Studio?

This...

So far we were able to pull secrets of Key Vault on demand in our code using Managed Identities. This is a very powerful feature and one that you should start using right now to ensure that no sensitive information is persisted in your code, environment variables or config files and provide a seamless way for your application to transition across environments.

However, there is one extra step we can take to make our application more robust. In many cases, we want the application to start with config settings that we can pull later in our solution without having to write code to do it on demand, like in this example. Luckily, ASP.NET Core has the capacity to securely populate config settings referenced in the appsettings.json using Azure Key Vault.

Retrieve application configuration settings from Key Vault using a Managed Identity

There are 3 steps needed in order to allow our ASP.NET Core app to populate appsettings.json settings from Key Vault

  1. Update the appsettings.config
  2. Add the appropriate NuGet package
  3. Add the bootstrap code into the Program.cs class

First, we need to add 2 new variables in the appsetting.json file. The first is the Azure Key Vault name so that we can use it during bootstrapping. This is also important as we will want to use different Key Vaults between dev, QA and production environments:

Next, we want to add the config variable that will be populated by Key Vault:

"SomeKeyVaultVariable":"",

We don't have to populate the value as the bootstrap code will take care of it.

For our application startup code to be able to bootstrap Key Vault, we need to add the necessary NuGet package. Open the NuGet Package Manager Console and type:

package-install Microsoft.Extensions.Configuration.AzureKeyVault

Finally, we need to update the Program.cs code to pull in the app settings from Key Vault as per the code sample below:

We can now use config settings in our code. In our Razor page, we first need to create a public property to store the config setting and then use dependency injection in the constructor to get access to the IConfiguration, the object that holds our app configuration.

private readonly IConfiguration configuration;
public string ConfigSetting { get; set; }

public KeyVaultSecretsModel(IConfiguration config)
{
    configuration = config;
}

Then, in the OnGetAsyn() method we can populate the ConfigSetting like so:

ConfigSetting = configuration["SomeKeyVaultVariable"];

Finally, we should update the KeyVaultSecrets.cshtml to display the ConfigSetting. The final page code should look like this:

@page
@model ManagedIdentityDemo.Pages.KeyVaultSecretsModel
@{
    ViewData["Title"] = "KeyVaultSecrets";
}

<h2>KeyVault Secrets</h2>

<p>
    The KeyVault secret is: <strong> @Model.SecretValue</strong>
    <br />
    The Config setting from Key Vault is: <strong>@Model.ConfigSetting</strong>
</p>

Save and run the app using F5. If all was configured correctly, you should be presented with something very similar to this:

Once again, we were able to pull the config settings directly from Key Vault without the need to supply a connection string or access keys and instead relying on the Azure AD Managed Identities to provide the end-to-end security.

Summary

If there is one thing you should take from this, admittedly longer than I anticipated, blog post is that you can build highly secure and robust apps with little effort, benefiting from the tight integration of our tools. Visual Studio, Azure and Azure AD have been designed ad optimized to work together and help you be more secure and productive end-to-end. In the next blog post I will show you how to set up your production environment to use Managed Identities and then we will deploy our application so that we can test it.


  • Share this post on