Decision rules:

Use managed identity (system-assigned where the resource supports it, user-assigned when several resources share one identity or you need pre-provisioning) when:
- The caller runs on Azure (App Service, Functions, AKS, VMs, Container Apps). The platform injects the identity; there is nothing to store.
- The target SDK supports Entra auth (all current Azure SDKs do via DefaultAzureCredential / azidentity).
- You want audit: Entra sign-in logs show which identity accessed what, unlike a shared key.

Keep a connection string or key only when:
- Running against a local emulator (Azurite, Cosmos DB emulator) that has no Entra.
- A third-party tool cannot do Entra auth (some BI tools, legacy drivers).
- Break-glass access where identity infra itself might be down.

Migration path that does not break prod:

1. Create the managed identity and grant the data-plane roles (Storage Blob Data Contributor, Key Vault Secrets User, etc.) while the old connection string still works.
2. Deploy code that tries identity first and falls back to the connection string, behind a flag.
3. Flip the flag in staging, verify, then prod.
4. Delete the connection string from config. If something still needed it, step 2's fallback would have logged it.

Traps:

- **System-assigned identity dies with the resource.** Delete the app, the identity is gone, and role assignments referencing it dangle. User-assigned survives and is better for anything you might recreate.
- **One identity per trust boundary.** Do not share a single user-assigned identity across unrelated apps; a compromised app inherits all its roles.
- **Local dev gap.** Managed identity does not exist on your laptop. DefaultAzureCredential's dev-tool steps (az login etc.) bridge this; make sure local runs use those and prod uses the managed identity step, which is exactly what the chain order gives you for free.