Skip to content

Managed Identity

This recipe configures a system-assigned managed identity and uses DefaultAzureCredential from Java to call downstream Azure services without secrets.

Architecture

flowchart TD
    FUNC[Function App] --> MSI[System-assigned managed identity]
    MSI --> ENTRA[Microsoft Entra token endpoint]
    FUNC --> SDK[Azure SDK with DefaultAzureCredential]
    SDK --> SERVICE["("Storage / Key Vault / Cosmos DB")"]

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

Enable identity and capture principal ID:

az functionapp identity assign --name $APP_NAME --resource-group $RG

PRINCIPAL_ID=$(az functionapp identity show --name $APP_NAME --resource-group $RG --query principalId --output tsv)
CLI element Explanation
Command(s) az functionapp identity assign, az functionapp identity show
Key flags --name, --resource-group, --query, --output
Variables $APP_NAME, $RG
Expected result Azure CLI applies the configuration change; confirm the returned JSON or follow-up query shows the expected value.

Grant RBAC access to a storage account:

STORAGE_SCOPE=$(az storage account show --name $STORAGE_NAME --resource-group $RG --query id --output tsv)

az role assignment create \
  --assignee-object-id $PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Reader" \
  --scope $STORAGE_SCOPE
CLI element Explanation
Command(s) az storage account show, az role assignment create
Key flags --name, --resource-group, --query, --output, --assignee-object-id, --assignee-principal-type, --role, --scope
Variables $STORAGE_NAME, $RG, $PRINCIPAL_ID, $STORAGE_SCOPE
Expected result Azure CLI returns provisioning details; confirm the resource name and successful provisioning state before continuing.

Maven dependencies:

<dependencies>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-identity</artifactId>
        <version>1.14.2</version>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-storage-blob</artifactId>
        <version>12.27.0</version>
    </dependency>
</dependencies>

Java implementation

package com.contoso.functions;

import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobContainerClientBuilder;
import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;

import java.util.Optional;

public class ManagedIdentityFunctions {

    @FunctionName("listBlobsWithIdentity")
    public HttpResponseMessage listBlobsWithIdentity(
        @HttpTrigger(
            name = "request",
            methods = {HttpMethod.GET},
            authLevel = AuthorizationLevel.FUNCTION,
            route = "identity/blobs"
        ) HttpRequestMessage<Optional<String>> request
    ) {
        String endpoint = System.getenv("STORAGE_BLOB_ENDPOINT");
        String container = System.getenv("STORAGE_CONTAINER_NAME");

        BlobContainerClient containerClient = new BlobContainerClientBuilder()
            .endpoint(endpoint)
            .containerName(container)
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildClient();

        long count = containerClient.listBlobs().stream().count();
        return request.createResponseBuilder(HttpStatus.OK)
            .body("Blob count: " + count)
            .build();
    }
}

Implementation notes

  • DefaultAzureCredential uses local developer identity during development and managed identity in Azure.
  • RBAC propagation can take a few minutes after assignment.
  • Scope role assignments narrowly to least privilege.
  • Prefer identity-based connection settings for supported bindings.

See Also

Sources