Give an app a managed identity and use it from TypeScript

Replace secrets with a managed identity — choose system-assigned or user-assigned, enable it on App Service, Functions, Container Apps or a VM with az, grant the data-plane role on the target, select the right identity in TypeScript with ManagedIdentityCredential or DefaultAzureCredential, and avoid the delays and lifecycle traps (24-hour token cache, resource moves, tenant transfer).

markdownguidev1ZM1W-BQ8Mstage: buildtrust: officialservice: managed-identityinterface: cliinterface: sdkmanaged identitysystem-assigned managed identityuser-assigned managed identityaz webapp identity assignaz functionapp identity assignaz containerapp identity assignaz vm identity assignaz identity createManagedIdentityCredential clientIdIDENTITY_ENDPOINT IDENTITY_HEADERIMDS 169.254.169.254managed identity role assignmentKey Vault Secrets User managed identityprincipalId vs clientIdmanaged identity group membership delay

Created 21 Oct 2026 · Modified 23 Oct 2026 · 1 version

A managed identity is a service principal that Azure creates and rotates for you; your code asks the host for a token, and the target service checks a role assignment. Three steps: enable the identity, assign the role on the target, select that identity in code.

1. System-assigned or user-assigned

System-assignedUser-assigned
LifecycleDeleted with its resourceIndependent; delete it yourself
SharingOne resource onlyMany resources
Role assignments survive redeploying the appNo, if the app is deleted and recreated (new principal ID)Yes
Microsoft's recommendation—Recommended for Microsoft services

Use user-assigned when several apps share access, when infrastructure-as-code recreates resources, or when the role must exist before the app does.

2. Enable it

# user-assigned: create once, attach anywhere
az identity create --name id-orders --resource-group rg-app
az webapp identity assign --name app-orders --resource-group rg-app --identities <identity-resource-id>
az functionapp identity assign --name fn-orders --resource-group rg-app --identities <identity-resource-id>
az containerapp identity assign --name ca-orders --resource-group rg-app --user-assigned <identity-resource-id>
az vm identity assign --name vm-orders --resource-group rg-app --identities <identity-resource-id>

# system-assigned: omit --identities (Container Apps: --system-assigned)
az webapp identity assign --name app-orders --resource-group rg-app

The output gives two IDs. principalId (object ID) is what role assignments use. clientId is what your code passes to pick the identity.

3. Grant the role on the target

Control-plane roles such as Contributor or Owner don't open the data: reading secrets, blobs or messages needs the service's data role.

az role assignment create \
  --assignee-object-id <principalId> --assignee-principal-type ServicePrincipal \
  --role "Key Vault Secrets User" \
  --scope $(az keyvault show --name kv-orders --query id -o tsv)

Allow a few minutes before the first call. Each service family in this hub names its data roles.

4. Use it in code

import { ManagedIdentityCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";

// A user-assigned identity must be named by its client ID (or resource ID).
// A system-assigned identity: new ManagedIdentityCredential() with no arguments.
const credential = new ManagedIdentityCredential({ clientId: process.env.APP_IDENTITY_CLIENT_ID ?? "" });

const secrets = new SecretClient("https://kv-orders.vault.azure.net", credential);
const dbPassword = await secrets.getSecret("db-password");
console.log(dbPassword.properties.version);

If you use DefaultAzureCredential instead, pass managedIdentityClientId. Otherwise it falls back to the AZURE_CLIENT_ID environment variable, or to the system-assigned identity (reference/azure-identity/credential-chain.md). A resource with several user-assigned identities and no client ID chosen can't tell which one you mean.

Under the hood: App Service, Functions and Container Apps expose IDENTITY_ENDPOINT and IDENTITY_HEADER to the process (send the header as X-IDENTITY-HEADER). VMs use IMDS at http://169.254.169.254/metadata/identity/oauth2/token with Metadata: true. The SDK handles both, so call them yourself only from languages without an SDK, and never through a proxy.

Traps

  • Entra group and app-role changes reach a managed identity slowly. Groups and app roles are claims in the token, the platform caches managed identity tokens for about 24 hours per resource, and the refresh can't be forced. So adding the identity to a group that holds the role can take hours to work. Microsoft recommends assigning permissions directly to a user-assigned identity instead (an Azure RBAC assignment made directly to the identity takes minutes, like any other).
  • A VM moved while running fails to start after its next stop, because its identity reference is stale (reference/managed-identity/errors.md).
  • A subscription moved to another Microsoft Entra tenant breaks every managed identity in it. Re-create user-assigned identities, re-enable system-assigned ones, and re-assign roles.
  • Federated credentials on a user-assigned identity let GitHub Actions or Kubernetes use it too (guides/entra/github-actions-oidc.md).

Next

Failures: reference/managed-identity/errors.md. Picking roles and scopes: reference/rbac/role-assignments-and-scopes.md.

Read this document as markdown — the same bytes an agent receives.