Use Azure Key Vault secrets in Express.js app
Store secrets in Azure Key Vault, then use those secrets programmatically from Key Vault in your Express.js app.
Prepare your development environment
Complete the Express.js with Cosmos DB tutorial.
When you complete the previous tutorial, you should have an Express.js app using a Cosmos DB database deployed to an Azure web app.
Make sure the following are installed on your local developer workstation:
- An Azure account with an active subscription. Create an account for free.
- Azure resource group already created in previous tutorial.
- Azure resources already created in previous tutorial
- Azure Cosmos DB resource
- Azure App service
- Node.js 10.1+ and npm - installed to your local machine.
- Visual Studio Code - installed to your local machine.
- Visual Studio Code extensions:
- Azure App Service extension for Visual Studio Code.
- Use Azure Cloud Shell using the bash. If you prefer, install the Azure CLI to run CLI reference commands.
Log in to Azure CLI
In the Visual Studio Code integrated terminal, log in to the Azure CLI. This requires you to authenticate in a browser with your account, which has permission on a valid Azure Subscription.
Use the az Login command to login.
az login
Create a Key Vault resource with Azure CLI
Use the az keyvault create command to create a Key Vault resource in the resource group.
az keyvault create \
--subscription REPLACE_WITH_YOUR_SUBSCRIPTION_NAME_OR_ID \
--resource-group REPLACE_WITH_YOUR_RESOURCE_GROUP_NAME \
--name REPLACE_WITH_YOUR_KEY_VAULT_NAME
Your Azure account is the only one authorized to perform any operations on this new vault. Make note of the output values:
- Vault Name: The name you provided to the
--nameparameter above. - Vault URI: The URL format is
https://<YOUR_KEY_VAULT_NAME>.vault.azure.net/.
Create a service principal with Azure CLI
The service principal allows you to create and use resources without having to use or expose your personal user account. The service principal is stored as an App Registration in Azure Active Directory.
This sample uses the DefaultAzureCredential, which requires authentication setup. One example of setting up the credential is to create and use a service principal.
Use the az ad sp create-for-rbac command to create a service principal.
az ad sp create-for-rbac \ --name REPLACE-WITH-YOUR-NEW-SERVICE-PRINCIPAL-NAME \ --role ContributorAn example service principal name is
demo-keyvault-service-principal-YOUR-NAME, whereYOUR-NAMEis postpended to the string.Capture and save the service principal output results of the command to use later.
{ "appId": "YOUR-SERVICE-PRINCIPAL-ID", "displayName": "YOUR-SERVICE-PRINCIPAL-NAME", "name": "http://YOUR-SERVICE-PRINCIPAL-NAME", "password": "!@#$%", "tenant": "YOUR-TENANT-ID" }
Give your service principal access to your key vault
Use the az keyvault set-policy command to give your service principal access to your Key Vault with Azure CLI command. The value for YOUR-SERVICE-PRINCIPAL-ID is your service principal output's appId value.
az keyvault set-policy \
--subscription REPLACE-WITH-YOUR-SUBSCRIPTION-NAME-OR-ID \
--name "REPLACE-WITH-YOUR-KEY-VAULT-NAME" \
--spn YOUR-SERVICE-PRINCIPAL-ID \
--secret-permissions get list
This service principal will only be able to list all secrets or get a specific secret.
Store your secret environment variable in Key Vault resource
Use the az keyvault secret set command to add your MongoDB connection string, created in the prior tutorial, as a secret named DATABASEURL to your key vault.
az keyvault secret set \
--subscription REPLACE-WITH-YOUR-SUBSCRIPTION-NAME-OR-ID \
--vault-name "REPLACE-WITH-YOUR-KEY-VAULT-NAME" \
--name "DATABASEURL" \
--value "YOUR-COSMOS-DB-MONGODB-CONNECTION-STRING"
Note
DATABASEURL, as a secret name, is not a keyword. You could choose any name to identify the secret. Just use that name consistently in the remaining instructions.
Switch branches in GitHub
The code to use key vault, instead of an environment variable, is provided in the keyvault branch of the sample repository.
Using git, stash the changes to your local project, then checkout out the
keyvaultbranch.git stash && git checkout keyvaultInstall dependencies and open the project in Visual Studio Code.
npm install && \ code .The Azure Key Vault integration requires two additional npm packages, @azure/identity (to use the service principal) and @azure/keyvault-secrets.
Configure Express.js required environment variables to use Azure Identity
Set these environment variables in the .env file of the sample project to create the REQUIRED context to use DefaultAzureCredential.
AZURE_TENANT_ID: Thetenantfrom the service principal output above.AZURE_CLIENT_ID: TheappIdfrom the service principal output above.AZURE_CLIENT_SECRET: Thepasswordfrom the service principal output above.
When you deploy the application to Azure app service, you will also need to add these settings to your web app.
Note
These variables names are keywords and must be used as-is, without changes, in order for Azure Identity to work successfully.
Configure Express.js required environment variables to use Azure Key Vault
Set these environment variables in the .env file of the sample project to programmatically determine which Key Vault resource and secret to use.
KEY_VAULT_NAME: Same value asREPLACE-WITH-YOUR-KEY-VAULT-NAMEused in previous commands.KEY_VAULT_SECRET_NAME_DATABASEURL: The secret name,DATABASEURL.
When you deploy the application to Azure app service, you will also need to add these settings to your web app.
Note
These variable names are specific to this sample. You can change them but make sure to change them in the environment file, the source code file, and your deployed web app settings.
Run the local program
Run the Express.js app with the following command:
npm startOpen the Express.js app in the browser:
http://localhost:8080.You may have names and jobs from the previous tutorial. Interact with the app, adding names and jobs, deleting individual names and jobs, or deleting all names and jobs.
Deploy the key vault version to Azure app service
Complete this section using VS Code and the App Service extension.
In the VS Code activity bar, select the Azure icon.
In the side bar, select your web app from your subscription under the App Service section.
Right-click your app name and select Deploy to Web app.
Add the required environment variables from your local app to the Azure app service, by right-clicking on the Application Settings. Use the value from the local
.envfile.Setting to add KEY_VAULT_NAME: KEY_VAULT_SECRET_NAME_DATABASEURL: DATABASEURLAZURE_TENANT_ID= AZURE_CLIENT_ID= AZURE_CLIENT_SECRET Remove the previous tutorial's settings by right-clicking on the setting then selecting Delete.
Setting to remove DATABASE_URL: DATABASEURLRight-click your web app name then select Restart to have the new app settings take affect.
Right-click your web app name then select Browse Website. In the subsequent pop-up window, select Open.
On the sidebar, right-click your web app and select Start streaming logs to see the service logs.
Interact with the app, adding items and deleting.
What Changed in the keyvault branch?
The original tutorial stored the database connection string in the .env file locally and in the App Settings in your Azure web app. Anyone who had access to your local workstation or your remote Azure app service would be able to see and use your Cosmos DB connection string.
This branch of the sample changes from using the environment variables to getting those values from key vault in the ./src/azure/azure-kevault.js file.
The authentication to connect to Key Vault uses the DefaultAzureCredential. The benefit of this is that the code doesn't need to use or store credentials to your key vault.
Understand the sample application Key Vault code
The sample code uses the following Azure SDKs:
- @azure/identity - uses DefaultAzureCredential and your service principal to access resources on Azure.
- @azure/keyvault-secrets - used to manage Key Vault secrets.
Get secret from Key Vault with JavaScript
After you ensure your DefaultAzureCredential is correctly configured with the required environment variables to use Azure Identity, use the DefaultAzureCredential to access your Key Vault secrets with JavaScript.
The following azure-keyvault.js file gets the secret from your key vault.
const { DefaultAzureCredential } = require("@azure/identity"); const { SecretClient } = require("@azure/keyvault-secrets"); const getSecret = async (secretName, keyVaultName) => { if (!secretName || !keyVaultName) { throw Error("getSecret: Required params missing") } if (!process.env.AZURE_TENANT_ID || !process.env.AZURE_CLIENT_ID || !process.env.AZURE_CLIENT_SECRET) { throw Error("KeyVault can't use DefaultAzureCredential"); } const credential = new DefaultAzureCredential(); // Build the URL to reach your key vault const url = `https://${keyVaultName}.vault.azure.net`; try { // Create client to connect to service const client = new SecretClient(url, credential); // Get secret Obj const latestSecret = await client.getSecret(secretName); // Return value return latestSecret.value; } catch (ex) { console.log(ex) throw ex; } } module.exports = { getSecret };The following data.js code pulls in the dependency for the key vault secret function,
getSecret, and initializes the configuration object.const { getSecret } = require("./azure/azure-keyvault"); let dbConfig = null;The following
data.jscode shows thegetConnectionfunction to get environment variables and callgetSecretfromazure-keyvault.js.const getConnectionConfiguration = async () => { const KEY_VAULT_CONNECTION_STRING_SECRET_NAME = process.env.KEY_VAULT_SECRET_NAME_DATABASEURL; const KEY_VAULT_NAME = process.env.KEY_VAULT_NAME; if (!KEY_VAULT_CONNECTION_STRING_SECRET_NAME || !KEY_VAULT_NAME) { throw Error("missing environment variables for Key Vault"); } // Get Secret (Database Connection String) from Key Vault let DATABASE_URL = await getSecret(KEY_VAULT_CONNECTION_STRING_SECRET_NAME, KEY_VAULT_NAME); // database name const DATABASE_NAME = process.env.DATABASE_NAME || 'my-tutorial-db'; // collection name const DATABASE_COLLECTION_NAME = process.env.DATABASE_COLLECTION_NAME || 'my-collection'; if (!DATABASE_URL || !DATABASE_NAME || !DATABASE_COLLECTION_NAME) { throw Error("missing DB settings;") } dbConfig = { DATABASE_URL, DATABASE_NAME, DATABASE_COLLECTION_NAME } }The following
data.jsfile code calls thegetConnectionfunction, then returns the function to the Express.jsserver.jsfile.const connectToDatabase = async () => { try { await getConnectionConfiguration(); mongoConnection = await connect(dbConfig.DATABASE_URL); db = mongoConnection.db(dbConfig.DATABASE_NAME); console.log(`DB connected = ${!!db}`); return !!db; } catch (err) { console.log('DB not connected - err'); console.log(err); } };
Clean up resources - remove resource group
Once you have completed this tutorial, you need to remove the resource group with the az group delete command.
az group delete \
--name REPLACE_WITH_YOUR_RESOURCE_GROUP_NAME -y
This command may take a few minutes.
Clean up resources - remove service principal
Delete your service principal with the az ad sp delete command.
az ad sp delete \
--id YOUR-SERVICE-PRINCIPAL-ID