Managed Identities are the only way to authenticate Azure services to other Azure services without ever touching a secret.
Let’s see it in action. Imagine an Azure Function needs to read data from Azure Blob Storage.
Here’s the setup:
-
Create a Function App:
- Go to the Azure portal.
- Search for "Function App" and click "Create."
- Fill in the basics: Subscription, Resource Group, Function App name (e.g.,
my-awesome-function-app), Runtime stack (e.g., Node.js), Version, and Region. - Click "Review + create" then "Create."
-
Enable System-Assigned Managed Identity:
- Navigate to your newly created Function App.
- In the left-hand menu, under "Settings," click "Identity."
- Under the "System assigned" tab, toggle the "Status" to "On."
- Click "Save."
- You’ll see a confirmation: "System assigned managed identity is enabled for MyAwesomeFunctionApp." The Object (principal) ID is also displayed; note this down.
-
Create a Storage Account:
- Go to the Azure portal.
- Search for "Storage account" and click "Create."
- Fill in the basics: Subscription, Resource Group, Storage account name (e.g.,
myawesomestorageaccount), Region. - For "Performance," choose "Standard."
- For "Redundancy," choose "Locally-redundant storage (LRS)."
- Click "Review + create" then "Create."
-
Grant the Function App Access to the Storage Account:
- Navigate to your Storage Account.
- In the left-hand menu, under "Access control (IAM)," click "Add role assignment."
- In the "Role" dropdown, search for and select "Storage Blob Data Reader." (This role grants read access to blob data).
- For "Assign access to," select "Managed identity."
- Click "+ Select members."
- In the "Managed identity" dropdown, select "Function App."
- In the "Subscription" dropdown, select your subscription.
- In the "Managed identity" dropdown, select your Function App (e.g.,
my-awesome-function-app). - Click "Select," then "Review + assign."
-
Write the Azure Function Code:
- Go back to your Function App.
- Under "Functions," click "Create." Choose a template, e.g., "Blob trigger."
- Here’s a Node.js example for a blob trigger function that reads blob content:
const { BlobServiceClient } = require("@azure/storage-blob"); module.exports = async function (context, myBlob) { context.log("JavaScript blob trigger function processed blob"); context.log(`Name: ${context.bindingData.name}`); context.log(`Size: ${myBlob.length} Bytes`); // Construct the connection string using the managed identity // The environment variable AZURE_TENANT_ID is automatically populated // The environment variable AZURE_CLIENT_ID is the Object ID of the managed identity const blobServiceClient = new BlobServiceClient( `https://myawesomestorageaccount.blob.core.windows.net`, new DefaultAzureCredential() // This uses the managed identity ); const containerClient = blobServiceClient.getContainerClient("my-container"); // Replace with your container name const blobClient = containerClient.getBlobClient(context.bindingData.name); try { const downloadBlockBlobResponse = await blobClient.download(); const downloadedContent = await downloadBlockBlobResponse.text(); context.log(`Blob content: ${downloadedContent}`); } catch (error) { context.log.error(`Error downloading blob: ${error.message}`); } };-
Important Note: For the
DefaultAzureCredential()to work in an Azure Function with a system-assigned identity, you need to ensure your Function App’s runtime is configured to use it. This is usually automatic when the identity is enabled. For local development, you might useAzureCliCredentialorEnvironmentCredentialby logging into Azure CLI (az login) or setting environment variables. -
Environment Variables: The
DefaultAzureCredentialwill automatically pick up the managed identity context when running inside Azure. It looks for theAZURE_TENANT_ID(your Azure AD tenant ID) andAZURE_CLIENT_ID(the Object ID of your Function App’s managed identity) environment variables. These are typically injected by the Azure Functions runtime. -
The
DefaultAzureCredential: This is the magic. It tries multiple credential types in a specific order. When running in Azure with a managed identity enabled, it will automatically use the managed identity’s token. This means you don’t hardcode any secrets or connection strings with embedded secrets.
-
Deploy and Test:
- Deploy your function code.
- Upload a file to a blob container in your
myawesomestorageaccount(ensure the container exists, e.g., namedmy-container). - The function will trigger, and you should see the blob content logged in the Function App’s logs.
The core problem this solves is credential management. Traditionally, you’d need to generate a storage account key or a Shared Access Signature (SAS) token and store it securely, often in Azure Key Vault. Managed Identities eliminate this by providing an identity for the service itself, which Azure AD can then authenticate.
When you enable a managed identity for a service like a Function App, Azure creates an Object (principal) ID for it in your Azure AD tenant. This Object ID is like a user account, but it represents the service. You then grant this Object ID permissions to other Azure resources (like your storage account) using Azure RBAC (Role-Based Access Control), just as you would grant permissions to a human user.
When your Function App code needs to access Blob Storage, it uses an Azure SDK (like @azure/storage-blob). The SDK, when configured with DefaultAzureCredential, asks the Azure Instance Metadata Service (IMDS) for a token on behalf of its managed identity. IMDS returns a token, which the SDK then uses to authenticate requests to Blob Storage. The storage service validates this token with Azure AD, confirming the identity of the Function App and its granted permissions.
The most surprising thing is how seamlessly DefaultAzureCredential handles the runtime context. Whether you’re running locally (where it might fall back to your Azure CLI login) or deployed in Azure (where it automatically uses the managed identity), the code remains the same. You don’t write conditional logic for different environments; the credential provider abstracts it. The underlying mechanism involves the Azure SDK requesting a token from the Azure Instance Metadata Service (IMDS) endpoint (http://169.254.169.254/metadata/identity/oauth2/token) which is only accessible from within the Azure environment, and only by authenticated workloads.
This approach means you never have to worry about rotating keys or exposing secrets in application settings or code, significantly enhancing security posture.
The next concept to explore is how to manage access to Azure Key Vault itself using managed identities, for scenarios where you do need to retrieve secrets programmatically.