Skip to content

Managed Identity (Passwordless Access)

Use system-assigned managed identity with DefaultAzureCredential so your Spring Boot app accesses Azure resources without embedded secrets.

Prerequisites

  • App Service app deployed
  • Permission to assign RBAC roles on target resources
  • Azure Identity Java SDK available in app dependencies

Main Content

Why managed identity first

Managed identity removes credential rotation burden from application code:

  • no client secret in App Settings
  • no secret exposure in CI logs
  • centralized RBAC control per environment

Architecture

flowchart TD
    A[Spring Boot App Service] --> B[DefaultAzureCredential]
    B --> C[Managed Identity endpoint]
    C --> D[Microsoft Entra token]
    D --> E[Azure Resource API]

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: Key Vault Secrets User, Storage Blob Data Reader, 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? App Service's managed identity
Role Definition What permission? Key Vault Secrets User, Storage Blob Data Reader
Scope On which resource? A specific Key Vault, Storage account, 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.

Add dependency (pom.xml)

<dependency>
  <groupId>com.azure</groupId>
  <artifactId>azure-identity</artifactId>
  <version>1.12.2</version>
</dependency>

Enable system-assigned identity on web app

az webapp identity assign \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --output json

Masked output example:

{
  "principalId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "tenantId": "<tenant-id>",
  "type": "SystemAssigned"
}

Retrieve principal ID for role assignment

export APP_PRINCIPAL_ID=$(az webapp identity show \
  --resource-group "$RG" \
  --name "$APP_NAME" \
  --query principalId \
  --output tsv)

Assign least-privilege RBAC role

Example: grant Key Vault Secrets User on one vault scope:

export KV_ID="/subscriptions/<subscription-id>/resourceGroups/$RG/providers/Microsoft.KeyVault/vaults/<vault-name>"

az role assignment create \
  --assignee-object-id "$APP_PRINCIPAL_ID" \
  --assignee-principal-type ServicePrincipal \
  --role "Key Vault Secrets User" \
  --scope "$KV_ID" \
  --output json

For Azure SQL/Cosmos/Storage, change role and scope accordingly.

Use DefaultAzureCredential in Spring Boot

import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;

@Bean
public DefaultAzureCredential defaultAzureCredential() {
    return new DefaultAzureCredentialBuilder().build();
}

When running on App Service, this resolves to managed identity. Locally, it can use Azure CLI or developer credentials.

Token request example

TokenRequestContext context = new TokenRequestContext()
    .addScopes("https://vault.azure.net/.default");

AccessToken token = credential.getToken(context).block();

Local development parity

Use the same code locally after:

az login
az account set --subscription "<subscription-id>"

No branching logic is needed between local and cloud identity paths.

RBAC propagation delay

New role assignments can take several minutes to become effective. Temporary 403 responses immediately after assignment are common.

Scope narrowly

Assign roles at the smallest scope possible (resource level preferred over subscription level).

Platform architecture

For platform architecture details, see Platform: How App Service Works.

Verification

  • Identity exists on App Service (az webapp identity show)
  • Role assignment present on intended scope
  • App performs token-based call successfully without secrets

Troubleshooting

ManagedIdentityCredential authentication unavailable

Verify the app is running on Azure App Service with identity enabled; local runs rely on alternate credentials in the chain.

403 Forbidden despite assigned role

Check role name, scope, and principal ID correctness; wait for propagation and retry.

Works locally, fails in Azure

Likely local credential succeeded but cloud identity lacks RBAC. Re-check Azure role assignments for app principal.

Run It in the Portal

Portal view: Identity blade (canonical managed-identity enablement surface)

Identity blade for a Web App with two tabs — System assigned (active) and User assigned. A descriptive header explains that a system-assigned managed identity is restricted to one per resource, tied to the lifecycle of the resource, allows RBAC permissions to be granted in Azure, and is authenticated with Microsoft Entra ID so no credentials need to be stored in code. The command bar shows Save, Discard, Refresh, Troubleshoot, and Got feedback? actions. The Status control is a two-state toggle currently set to Off, with On as the alternative position. No Object (principal) ID, Permissions, or Azure role assignments are shown because the identity is not yet enabled.

The Identity blade is the canonical Portal surface for the managed-identity flow this recipe walks through. With System assigned active and the Status toggle moved from Off to On, App Service creates the Entra ID principal that the recipe then uses to grant downstream RBAC roles for Storage, Key Vault, SQL, or any other Entra-aware service. The descriptive header on this blade restates the lifecycle guarantee the recipe relies on — the identity is tied to the resource — and the User assigned tab is the alternative path the recipe covers for identities shared across multiple apps. Use this blade as step 1 of the recipe before issuing any downstream role assignments via the CLI and before initializing DefaultAzureCredentialBuilder().build() in the Spring Boot app.

See Also

Sources