Managed Identity¶
This recipe enables system-assigned managed identity, grants RBAC roles, and uses DefaultAzureCredential to access Azure Storage and Cosmos DB without secrets.
Architecture¶
flowchart TD
FUNC[Function App] --> MSI[System-assigned Managed Identity]
MSI --> ENTRA[Microsoft Entra ID]
ENTRA --> STORAGE[Azure Storage Data Plane]
ENTRA --> COSMOS[Azure Cosmos DB Data Plane] How RBAC Connects Identity to Resources¶
A managed identity alone does not grant access. Azure RBAC binds three elements into a role assignment:
flowchart TD
P[Principal<br/>Who: Managed Identity or Service Principal] --> RA[Role Assignment<br/>Unique GUID per binding]
RD[Role Definition<br/>What: Storage Blob Data Owner, Key Vault Secrets User, etc.] --> RA
S[Scope<br/>Where: Subscription, Resource Group, or Resource] --> RA
RA --> ACCESS[Access Granted]
style RA fill:#f5c542,stroke:#333,color:#000 | Element | Question it answers | Example |
|---|---|---|
| Principal | Who needs access? | Function app's managed identity |
| Role Definition | What permission? | Storage Blob Data Owner, Key Vault Secrets User |
| Scope | On which resource? | A specific Storage account, Key Vault, or resource group |
| Role Assignment | The binding itself | Unique GUID — one per (principal + role + scope) combination |
Azure RBAC enforces a uniqueness constraint: only one role assignment can exist for the same (principal, role definition, scope) triple. Attempting to create a duplicate with a different assignment GUID results in a RoleAssignmentExists conflict.
Prerequisites¶
Use extension bundle v4 in host.json:
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
Create baseline resources:
az storage account create \
--name $STORAGE_NAME \
--resource-group $RG \
--location $LOCATION \
--sku Standard_LRS
az cosmosdb create \
--name <cosmos-account-name> \
--resource-group $RG \
--kind GlobalDocumentDB
| CLI element | Explanation |
|---|---|
| Command(s) | az storage account create, az cosmosdb create |
| Key flags | --name, --resource-group, --location, --sku, --kind |
| Variables | $STORAGE_NAME, $RG, $LOCATION |
| Expected result | Azure CLI returns provisioning details; confirm the resource name and successful provisioning state before continuing. |
Enable identity and capture principal id:
| CLI element | Explanation |
|---|---|
| Command(s) | az functionapp identity assign |
| Key flags | --name, --resource-group |
| Variables | $APP_NAME, $RG |
| Expected result | Azure CLI applies the configuration change; confirm the returned JSON or follow-up query shows the expected value. |
Assign RBAC roles:
az role assignment create \
--assignee <principal-id> \
--role "Storage Blob Data Contributor" \
--scope $(az storage account show --name $STORAGE_NAME --resource-group $RG --query id --output tsv)
az cosmosdb sql role assignment create \
--account-name <cosmos-account-name> \
--resource-group $RG \
--scope "/" \
--principal-id <principal-id> \
--role-definition-name "Cosmos DB Built-in Data Contributor"
| CLI element | Explanation |
|---|---|
| Command(s) | az role assignment create, az cosmosdb sql role assignment |
| Key flags | --assignee, --role, --scope, --name, --resource-group, --query, --output, --account-name, --principal-id, --role-definition-name |
| Variables | $STORAGE_NAME, $RG |
| Expected result | Azure CLI returns provisioning details; confirm the resource name and successful provisioning state before continuing. |
Install SDK packages:
Working Node.js v4 Code¶
const { app } = require("@azure/functions");
const { DefaultAzureCredential } = require("@azure/identity");
const { BlobServiceClient } = require("@azure/storage-blob");
const { CosmosClient } = require("@azure/cosmos");
const credential = new DefaultAzureCredential();
const blobServiceClient = new BlobServiceClient(
`https://${process.env.STORAGE_ACCOUNT_NAME}.blob.core.windows.net`,
credential
);
const cosmosClient = new CosmosClient({
endpoint: process.env.COSMOS_ENDPOINT,
aadCredentials: credential
});
app.http("identityProbe", {
methods: ["GET"],
route: "identity/probe",
authLevel: "function",
handler: async (_request, context) => {
const containerClient = blobServiceClient.getContainerClient("incoming");
const exists = await containerClient.exists();
const { database } = await cosmosClient.databases.createIfNotExists({
id: "appdb"
});
context.log("Managed identity calls succeeded", {
storageContainerExists: exists,
databaseId: database.id
});
return {
status: 200,
jsonBody: {
storageContainerExists: exists,
cosmosDatabaseId: database.id
}
};
}
});
Implementation Notes¶
DefaultAzureCredentialuses managed identity automatically in Azure and developer credentials locally.- Keep endpoint-style settings (
COSMOS_ENDPOINT,STORAGE_ACCOUNT_NAME) and avoid storing keys. - Assign least-privilege data-plane roles instead of broad management-plane roles.
- Initialize SDK clients once per process to reduce cold-start overhead.