Azure Managed Identity vs Service Principal: Which to Use
Originally published on DevToolHub. Most Azure managed identity vs service principal debates start from a false split. A managed identity is a service principal. Microsoft's docs say Azure creates a service principal o
Originally published on DevToolHub.
Most Azure managed identity vs service principal debates start from a false split. A managed identity is a service principal. Microsoft's docs say Azure creates a service principal of a special type in Entra ID for every managed identity. The real question is who looks after the credential: Azure or you. Below you get the rule for choosing, the commands for both, and the gotchas that appear after the first deploy.
I built this from Microsoft Learn and the Azure CLI reference as of November 3, 2026. I haven't run every command against a live tenant, so test them in a sandbox subscription first.
Azure Managed Identity vs Service Principal: What Is the Difference?
A service principal is the identity an application uses to sign in to Entra ID and receive Azure permissions. A managed identity is a service principal whose credential Azure creates, stores and rotates for you. You can never read it. With a regular service principal, you create a secret or certificate yourself. You store it and replace it before it expires. That credential handling is the whole difference.
There are two flavors of managed identity:
- System-assigned identities live on a single Azure resource, such as a VM or an App Service. Delete the resource and Azure deletes the identity.
- User-assigned identities are standalone resources. You create them, attach them to one or more resources, and delete them yourself.
Managed identities cost nothing extra, according to the same overview page.
Azure Managed Identity vs Service Principal: Side by Side
| Managed identity | Service principal (app registration) | |
|---|---|---|
| Credential | None you can see or leak | Client secret, certificate or federated credential |
| Where code can run | Azure compute that supports managed identities | Anywhere |
| Expiry risk | None | Secrets last 24 months at most |
| Lifecycle | System-assigned: tied to the resource. User-assigned: independent | Until you delete the app |
| Sharing | User-assigned can attach to many resources | Anything that holds the credential |
| Best fit | Workloads running on Azure | CI/CD, on-premises, other clouds |
When Should You Use a Managed Identity?
Use a managed identity whenever your code runs on Azure compute that supports one. It removes the secret. Nothing sits in an environment variable waiting to leak, and nothing expires on a Friday night. Microsoft's passwordless guidance recommends managed identities plus Azure RBAC as the default path for Azure-to-Azure connections.
In code, DefaultAzureCredential from the Azure Identity libraries handles both environments. Locally it can use the account you signed in with through the Azure CLI. On Azure it picks up the managed identity of the host. Your code doesn't change between the two.
Should You Pick System-Assigned or User-Assigned?
Microsoft's own best-practice page says user-assigned identities fit a broader range of scenarios. Pick user-assigned when:
- Resources come and go quickly. Many VMs with system-assigned identities can hit the Entra object-creation rate limit and return HTTP 429.
- Several resources need the same access. In Microsoft's example, four VMs sharing one user-assigned identity need two role assignments. Four system-assigned identities need eight.
- Access must exist before the resource does. A system-assigned identity appears with its resource, so you can't grant it a role in advance.
Pick system-assigned when permissions should disappear with the resource. It also fits audit logs that must name the specific resource, not just the identity.
When Do You Still Need a Service Principal?
You need a service principal when your workload runs somewhere a managed identity can't attach: GitHub Actions runners, on-premises servers, or another cloud. Even then, avoid a stored secret. Microsoft says client secrets are less secure than certificate or federated credentials and should not be used in production.
Workload identity federation is the secretless route. Your app registration or user-assigned identity trusts tokens from an external provider such as GitHub. The workload then swaps that token for an Entra access token. Two details worth knowing: system-assigned identities can't hold federated credentials, and an app or user-assigned identity can have at most 20 of them.
How Do You Set Up Each One?
A managed identity for an App Service
โ ๏ธ Note: leave --scope off az webapp identity assign. The CLI reference lists Contributor as the default for --role. Grant the narrow role yourself in the next step.
This creates one user-assigned identity and attaches it to one web app:
az identity create --name app-uami --resource-group rg-app --location centralus
az webapp identity assign -g rg-app -n my-web-app --identities app-uami
Next, grant it read access to one storage account. Passing --assignee-object-id skips the Microsoft Graph lookup. The reference says pairing it with --assignee-principal-type avoids propagation-latency errors.
PRINCIPAL_ID=$(az identity show --name app-uami --resource-group rg-app --query principalId --output tsv)
az role assignment create \
--assignee-object-id "$PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" \
--scope /subscriptions/<sub-id>/resourceGroups/rg-app/providers/Microsoft.Storage/storageAccounts/<account>
A service principal for GitHub Actions, no secret
az ad sp create-for-rbac creates a password credential by default. Turn that off and scope the role to one resource group:
az ad sp create-for-rbac --name gh-deploy --create-password false \
--role Reader --scopes /subscriptions/<sub-id>/resourceGroups/rg-app
Then add a federated credential on the app registration under Certificates & secrets. The issuer is https://token.actions.githubusercontent.com and the audience is api://AzureADTokenExchange. In the workflow, grant id-token: write and log in with the official action:
permissions:
id-token: write
contents: read
steps:
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
Our Terraform and GitHub Actions deployment guide shows this login in a full pipeline. The service principal setup guide covers the portal steps.
What Breaks in an Azure Managed Identity vs Service Principal Setup?
Role changes take hours to reach a managed identity. Azure caches managed identity tokens per resource URI for around 24 hours. You can't force a refresh. If the app still gets a 403 after a role or group change, the old token is the likely cause. Microsoft's fix is to put permissions directly on a user-assigned identity instead of going through an Entra group.
Deleting an identity doesn't delete its role assignments. They linger as "Identity not found". They also count against the 5,000 role assignments per subscription. Microsoft's cleanup command removes assignments whose ObjectType is Unknown:
Get-AzRoleAssignment | Where-Object {$_.ObjectType -eq "Unknown"} | Remove-AzRoleAssignment
โ ๏ธ Note: that command runs against your current subscription and deletes every matching assignment. Run the Get-AzRoleAssignment half on its own first and read the list.
A wrong federated-credential subject fails silently. Entra accepts a bad subject when you create the credential. The failure shows up at token exchange. Matching is case-sensitive, wildcards aren't supported, and the issuer, subject and audience must match the token exactly.
GitHub changed the default subject format. For repositories created after July 15, 2026, GitHub's OIDC reference says the default sub claim includes owner and repository IDs. One example is repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main. Older repositories keep the name-based format unless you opt in. A credential written in the old format won't match a new repository's token. Copy the subject from the docs, not from an older workflow.
Anyone who can run code on the resource can use its identity. Microsoft's example: a user can run code in a Logic App but has no storage access. That user can still read the storage account through the app's managed identity. Grant code-execution roles sparingly.
Parallel creation can collide. In ARM templates, create federated credentials under the same parent identity one at a time. Parallel creation can fail with a 409 conflict.
Azure Managed Identity vs Service Principal: Which Should You Choose?
Work down this list and stop at the first match:
- Code runs on Azure compute that supports managed identities. Use a managed identity. Pick user-assigned if resources share access or churn often.
- Code runs in GitHub Actions, Kubernetes elsewhere, or another cloud. Use a federated credential on an app registration or a user-assigned identity.
-
Code runs on your laptop. Sign in with
az loginand letDefaultAzureCredentialuse that. Don't create a service principal just for local work. - None of the above works. Use a service principal with a certificate. Keep expiry under 12 months and set a reminder to rotate it.
For secrets that still need a vault, see our comparison of AWS, Azure and GCP secret managers. For the wider picture, start with the Azure security best practices guide.
Frequently Asked Questions
Q: Is a managed identity the same as a service principal?
A: Almost. Every managed identity has a service principal of a special type behind it in Entra ID. The difference is the credential: Azure manages it for a managed identity, while you manage it for a regular service principal.
Q: Can a managed identity be used outside Azure?
A: Not directly. A managed identity serves workloads running on Azure resources. For GitHub Actions or other clouds, federate an app registration or a user-assigned identity with the external provider.
Q: How long does a client secret last?
A: At most 24 months, and Microsoft recommends under 12. az ad sp create-for-rbac defaults to one year through its --years option.
Q: Why does my managed identity still get a 403 after I added a role?
A: The role may not have propagated yet, or the cached token predates it. Azure caches tokens for roughly 24 hours and can't refresh them early. Restarting the app doesn't clear that cache, so the practical fix is to wait. To avoid the delay next time, grant roles directly to a user-assigned identity instead of through a group.
Q: Should I use system-assigned or user-assigned identities?
A: Use user-assigned when resources are short-lived or share access. Use system-assigned when permissions should end with the resource, or per-resource audit logs matter.
Quick Summary:
- A managed identity is a service principal whose credential Azure manages and rotates for you
- Use a managed identity on Azure compute, and federation for GitHub Actions and other clouds
- Client secrets cap at 24 months and Microsoft says not to use them in production
- Managed identity tokens are cached about 24 hours, so role changes can take hours to apply
- Deleted identities leave role assignments behind, and the limit is 5,000 per subscription
- New GitHub repositories use a different OIDC
subformat, so check your federated credential's subject
Whichever way the Azure managed identity vs service principal choice goes for you, start small. Pick one workload that still uses a client secret and move it to a managed identity or a federated credential. Then delete the secret.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.