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¶
DefaultAzureCredentialuses 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.