A Developer's Guide to Microsoft Entra ID Application Identities

1. Introduction

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.

2. The Core Problem: The Burden of Secrets

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.

3. The Two Types of Application Identity

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:

Service Principals: The Flexible External Contractor

A Service Principal is a general-purpose application identity. It's the "who" that represents your app when it needs to access resources.

Level Up: The Modern Contractor (Workload Identity Federation)

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.

The only thing that changes is how the application authenticates. The authorization model remains identical.

Managed Identities: The Trusted Built-in Robot

A Managed Identity is a special, Azure-native identity whose credentials are fully and automatically managed by the Azure platform itself.

Quick Guide: System-Assigned vs. User-Assigned Managed Identities

Managed Identities come in two flavors:

4. The Two Types of API Permissions

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.

Delegated Permissions: The Personal Assistant

Application Permissions: The Automated Cleaning Service

alt text

5. Putting It All Together: The Two Worlds of Permissions

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:

  1. The Azure Resource Manager (ARM) World: This is about controlling access to the infrastructure itself—things you create in your Azure subscription like virtual machines, storage accounts, and key vaults. The security system here is Azure RBAC (Role-Based Access Control).
  2. The Microsoft Entra ID World: This is about controlling access to data and directory services exposed via APIs, most notably the Microsoft Graph API. The security system here is OAuth 2.0 API Permissions and Consent.

Granting Permissions to Azure Resources (The ARM World)

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.

Granting Permissions to APIs like Microsoft Graph (The Entra ID World)

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.

The Modern Way: Granting API Permissions to Managed Identities

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

6. Visualizing the Identity and Permission Model

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 }

7. Summary: Your Practical Rule of Thumb

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.