You’re building an application. You need it to read a secret from Azure Key Vault or call the Microsoft Graph API. You go to the Azure portal to grant permissions and are faced with a choice that seems simple but has profound security implications: do you use a Service Principal or a Managed Identity?
They both seem to do the same thing—give your application an identity so it can access resources. So which one do you choose, and why does it matter so much?
Getting this choice right is the difference between managing a pocketful of risky, high-maintenance keys and embracing a modern, automated, "secret-less" future. This guide will demystify these concepts and give you a clear framework for making the right decision every time.
For years, the standard way to let an application authenticate was to create a client secret or a certificate. These are, for all practical purposes, passwords for your application. You generate them, copy them, and paste them into a config file, an environment variable, or a secret vault.
This creates two massive problems:
This is the problem that Managed Identity was born to solve.
To understand the solution, let's use an analogy. Imagine you need a specialized job done in your secure corporate office building. You have two options for hiring help:
A Service Principal is a general-purpose application identity. It's the "who" that represents your app when it needs to access resources.
The traditional contractor model is powerful, but it still has that one major weakness: the key (client secret) you give them. What if you could hire that same flexible, external expert, but instead of giving them a key, you create a diplomatic agreement that allows them to use their own trusted ID from their home country?
That’s exactly what Workload Identity Federation does. It’s a modern, secret-less authentication method for Service Principals. It uses what's called a Federated Credential.
How it Authenticates (The Secret-less Part):
The Key Takeaway: Workload Identity Federation is the most secure way to handle external workloads, combining the flexibility of a Service Principal with the secret-less security of a Managed Identity.
When to Use It: Use it in the exact same scenarios as a traditional Service Principal, but whenever the external platform supports issuing OIDC tokens. It is now the best practice for:
How are Permissions Assigned? This is a critical point: the way you assign permissions does not change. The Service Principal object in Entra ID is still the identity that gets the access. You assign permissions to it in the same two ways:
The only thing that changes is how the application authenticates. The authorization model remains identical.
A Managed Identity is a special, Azure-native identity whose credentials are fully and automatically managed by the Azure platform itself.
Managed Identities come in two flavors:
Once your application has an identity, you need to grant it permissions. When calling an API like Microsoft Graph, you face another fundamental choice: Delegated or Application permissions.
Imagine your application is a person you are hiring to do a job, and the API (Microsoft Graph) is a secure corporate building they need to access.
Mail.ReadWrite but the signed-in user only has permission to read their own mail, the app can only read that user's mail.Mail.Read.All, it can read every single mailbox in the organization. This is why these permissions are much more powerful and almost always require an administrator to grant consent.
A common point of confusion is where you assign these permissions. The answer depends on what you are trying to access. Think of Azure as having two distinct but connected security systems:
This is the world Managed Identities were primarily built for. When you want to grant an identity permission to read a secret from a Key Vault or write a file to a Storage Account, you use Azure RBAC.
This is the world App Registrations were primarily built for. When you want an application to read all users in your directory or send an email, you need to grant it Microsoft Graph API permissions.
Today, you can grant API permissions (like Microsoft Graph) to Managed Identities, which is a powerful pattern for Azure-hosted automation. However, this is primarily done programmatically using PowerShell or the Azure CLI, not through the Entra ID portal GUI.
For example, to grant a Managed Identity permission to read all users in the directory, you would use a PowerShell script to assign the User.Read.All application permission to the Managed Identity's underlying service principal.
# Get the Service Principal of your Managed Identity
$managedIdentity = Get-AzADServicePrincipal -ObjectId "your-mi-object-id"
# Get the Microsoft Graph API's Service Principal
$graphApi = Get-AzADServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
# Get the specific "User.Read.All" application permission
$appRole = $graphApi.AppRoles | Where-Object { $_.Value -eq "User.Read.All" -and $_.AllowedMemberTypes -contains "Application" }
# Assign the permission to your Managed Identity
New-AzureADServiceAppRoleAssignment -ObjectId $managedIdentity.Id -PrincipalId $managedIdentity.Id -ResourceId $graphApi.Id -Id $appRole.Id
To bring these concepts together, the following diagram shows how different identity objects relate to the central concept of a Service Principal and how they receive permissions for either Azure resources (RBAC) or APIs (Graph).
---
config:
theme: base
look: neo
layout: elk
---
flowchart TD
subgraph subGraph0["Authentication Method"]
direction LR
AuthMI["Azure Managed
(Secret-less)"]
AuthWIF["Workload Identity (OIDC)
(Secret-less)"]
AuthSecret["Secret / Certificate
(Secret-based)"]
end
subgraph ManagedChoices["Managed Identity types"]
direction TB
B1["System-assigned MI
(1:1 with resource)"]
B2["User-assigned MI
(1:M with resources)"]
end
subgraph Actors["Resulting Service Principals"]
direction LR
SP_SAMI["Service Principal
(from system-assigned MI)"]
SP_UAMI["Service Principal
(from user-assigned MI)"]
SP_App["Service Principal
(from App Registration)"]
end
subgraph RBACbox["Azure RBAC"]
RBAC["Assign RBAC roles
on Azure resources
(e.g., Key Vault, Storage)"]
end
subgraph GraphPerms["Microsoft Graph Permissions"]
direction LR
GP_App["Application permissions
(e.g., Mail.Read.All)"]
GP_Del["Delegated permissions
(e.g., Mail.Read)
(user present)"]
end
A["Where is your
code running?"] -- Inside Azure --> B["Managed Identity
(preferred)"]
A -- Outside Azure --> C["Secret-based or Secret-less?"]
C -- "Secret-less (Modern)" --> C_WIF["Workload Identity SPN"]
C -- "Secret-based (Legacy)" --> C_Secret["App Registration SPN"]
B --> B1 & B2
B1 --> SP_SAMI & AuthMI
B2 --> SP_UAMI & AuthMI
C_WIF --> SP_App & AuthWIF
C_Secret --> SP_App & AuthSecret
SP_SAMI L_SP_SAMI_RBAC_0@-- Permissions via
Portal or CLI --> RBAC
SP_UAMI L_SP_UAMI_RBAC_0@-- Permission via
Portal or CLI --> RBAC
SP_App L_SP_App_RBAC_0@-- Permission via
Portal or CLI --> RBAC
SP_App L_SP_App_GP_App_0@-- Granted to
application
Portal or CLI --> GP_App
SP_App L_SP_App_GP_Del_0@-- "On behalf of
signed-in user
Portal or CLI" --> GP_Del
SP_UAMI L_SP_UAMI_GP_App_0@== CLI Only ==> GP_App
SP_SAMI L_SP_SAMI_GP_App_0@== CLI Only ==> GP_App
n1["App Identity needed"] --> A
AuthMI@{ shape: rounded}
AuthWIF@{ shape: rounded}
AuthSecret@{ shape: rounded}
B1@{ shape: rounded}
B2@{ shape: rounded}
SP_SAMI@{ shape: rounded}
SP_UAMI@{ shape: rounded}
SP_App@{ shape: rounded}
RBAC@{ shape: rounded}
GP_App@{ shape: rounded}
GP_Del@{ shape: rounded}
A@{ shape: diam}
B@{ shape: rounded}
C@{ shape: diam}
C_WIF@{ shape: rounded}
C_Secret@{ shape: rounded}
n1@{ shape: rounded}
GraphPerms@{ shape: rounded}
RBACbox@{ shape: rounded}
Actors@{ shape: rounded}
ManagedChoices@{ shape: rounded}
SP_SAMI:::identity
SP_UAMI:::identity
SP_App:::identity
classDef identity fill:#cde4ff,stroke:#333,stroke-width:1px
style AuthMI stroke:#2962FF
style AuthWIF stroke:#2962FF
style AuthSecret stroke:#2962FF
style B1 stroke:#2962FF
style B2 stroke:#2962FF
style SP_SAMI stroke:#2962FF
style SP_UAMI stroke:#2962FF
style SP_App stroke:#2962FF
style RBAC fill:#BBDEFB,stroke:#2962FF
style GP_App fill:#BBDEFB,stroke:#2962FF
style GP_Del fill:#BBDEFB,stroke:#2962FF
style A fill:#FFF9C4,stroke:#2962FF,stroke-width:1px
style B fill:#e6f7ff,stroke:#2962FF
style C fill:#ffe6d6,stroke:#2962FF
style C_WIF fill:#e1f5fe,stroke:#2962FF
style C_Secret fill:#fffde7,stroke:#2962FF
style n1 fill:#FFF9C4,stroke:#2962FF,stroke-width:1px
style GraphPerms stroke:#2962FF
style RBACbox stroke:#2962FF
style Actors stroke:#2962FF
style ManagedChoices stroke:#2962FF
style subGraph0 stroke:#2962FF
linkStyle 0 stroke:#AA00FF,fill:none
linkStyle 1 stroke:#AA00FF,fill:none
linkStyle 2 stroke:#AA00FF,fill:none
linkStyle 3 stroke:#AA00FF,fill:none
linkStyle 4 stroke:#AA00FF,fill:none
linkStyle 5 stroke:#AA00FF,fill:none
linkStyle 6 stroke:#AA00FF,fill:none
linkStyle 7 stroke:#AA00FF,fill:none
linkStyle 8 stroke:#AA00FF,fill:none
linkStyle 9 stroke:#AA00FF,fill:none
linkStyle 10 stroke:#AA00FF,fill:none
linkStyle 11 stroke:#AA00FF,fill:none
linkStyle 12 stroke:#AA00FF,fill:none
linkStyle 13 stroke:#AA00FF,fill:none
linkStyle 14 stroke:#00C853,fill:none
linkStyle 15 stroke:#00C853,fill:none
linkStyle 16 stroke:#00C853,fill:none
linkStyle 17 stroke:#00C853,fill:none
linkStyle 18 stroke:#00C853,fill:none
linkStyle 19 stroke:#D50000,fill:none
linkStyle 20 stroke:#D50000,fill:none
linkStyle 21 stroke:#AA00FF,fill:none
L_SP_SAMI_RBAC_0@{ animation: none }
L_SP_UAMI_RBAC_0@{ animation: none }
L_SP_App_RBAC_0@{ animation: none }
L_SP_App_GP_App_0@{ animation: none }
L_SP_App_GP_Del_0@{ animation: none }
L_SP_UAMI_GP_App_0@{ animation: slow }
L_SP_SAMI_GP_App_0@{ animation: slow }
Choosing the right identity model is a foundational step in building secure, modern cloud applications. While the details can be complex, the decision usually boils down to one simple question: "Where is my code running?"
| If Your Code is Running... | The Identity You Should Use is... | Because... |
|---|---|---|
| Inside of Azure (VM, App Service, Functions, etc.) | Managed Identity | It's the most secure, secret-less, and automated option. |
| Outside of Azure (On-prem, other clouds, CI/CD) | Service Principal | It's the flexible, general-purpose identity designed for external workloads. |
By defaulting to Managed Identities for all your Azure-to-Azure communication and reserving Service Principals for external workloads, you drastically reduce your attack surface and eliminate the operational nightmare of managing secrets.