07/08/2026
Microsoft Entra App Registrations: Client Credentials, App Roles, and Workload Identity Federation
Build a minimal Microsoft Entra client-credentials setup using separate app registrations for a client and an API. See how app roles, application permissions, /.default, access token claims, and later workload identity federation fit together.
Microsoft Entra app registrations are powerful, but that flexibility comes with a fair amount of terminology and configuration. Client applications, APIs, app roles, application permissions, credentials, consent, and token claims all play different roles, and it is easy to lose sight of how they connect.
This article deliberately strips the setup down to one simple application-to-application scenario:
A Books Reader application calls a Books API using the OAuth 2.0 client credentials flow.
The goal is not to cover every Microsoft Entra feature. Instead, the goal is to make the relationship between the client application, the API, their app registrations, the configured permissions, and the resulting access token as clear as possible.
We start with a client secret because it makes the client credentials flow easy to understand. Later, the secret will be removed and replaced by an Azure Managed Identity combined with a federated identity credential. That removes the need to store or rotate secrets while keeping the same client app registration, API app registration, app-role assignment, and authorization model.
---
config:
layout: elk
---
flowchart LR
Client["Books Reader<br/>Client Application"]
subgraph ClientReg["Client App Registration"]
ClientId["Client ID"]
Secret["Client Secret<br/>(replaced later by federation)"]
Permission["Assigned Application Permission:<br/><b>Books.Read</b>"]
end
subgraph ApiReg["API App Registration"]
ApiId["Application ID URI<br/>api://..."]
Role["Exposed App Roles:<br/>Books.Read<br/>Books.Write"]
end
Api["Books API<br/>Back-end Application"]
Client -->|"uses application identity defined by"| ClientReg
Role -.->|"selected role is assigned to client"| Permission
Client -->|"1. Request access token<br/>client_id + secret<br/>scope = api://.../.default"| ClientReg
ClientReg -->|"2. Access token for Books API<br/>aud = API<br/>appid/azp = Client<br/>roles = Books.Read"| Client
Client -->|"3. Bearer access token"| Api
Api -.->|"identity and authorization model defined by"| ApiReg
This diagram shows the minimal setup for application-to-application authentication using Microsoft Entra ID and the OAuth 2.0 client credentials flow.
Microsoft Entra ID itself is intentionally omitted from the diagram to keep the relationship between the applications and app registrations visible. In reality, Microsoft Entra ID is the authorization server: the client authenticates to Entra ID, and Entra ID issues the access token.
The important distinction is that the two app registrations have different responsibilities.
The Books API app registration defines the protected resource and the permissions that exist. It identifies the API through its Application ID URI and exposes app roles such as Books.Read and Books.Write.
The Books Reader client app registration defines the identity of the calling application and which of those API permissions have been assigned to that client. In this example, the client is assigned only Books.Read.
So the relationship is:
Books API App Registration
exposes:
Books.Read
Books.Write
|
| selected permission is assigned
v
Books Reader Client App Registration
application permissions:
Books.Read
The client does not automatically receive every app role exposed by the API. The API registration defines which roles are available, while the client registration is assigned a specific subset of those roles.
In this example:
API exposes:
Books.Read
Books.Write
Client is assigned:
Books.Read
As a result, the Books Reader should receive:
roles = Books.Read
not:
roles = Books.Read, Books.Write
Requesting /.default
To obtain an access token, the Books Reader authenticates as its client app registration and requests:
scope = api://<api-id>/.default
There are two important parts to this value.
The resource portion:
api://<api-id>
identifies the Books API as the intended target of the access token.
The /.default portion tells Microsoft Entra ID to use the permissions that have already been statically configured and consented for this client on that resource.
It does not mean:
give this client every app role exposed by the API
Instead, in this client credentials scenario it effectively means:
give this client the application permissions that have already
been assigned to it for this API
So even though the Books API exposes both:
Books.Read
Books.Write
the Books Reader receives only:
Books.Read
because that is the application permission assigned to the Books Reader client.
Conceptually:
API App Registration
exposes Books.Read
exposes Books.Write
|
| assign Books.Read
v
Client App Registration
has Books.Read
|
| request api://books/.default
v
Microsoft Entra ID
|
v
Access token
aud = Books API
roles = Books.Read
This distinction is important because /.default refers to the target API, while the actual permissions included in the token depend on the permissions assigned to the calling client for that API.
The resulting access token
The resulting access token is intended for the Books API.
Relevant claims typically include:
aud: identifies the Books API as the audience of the tokenappidorazp: identifies the calling client applicationroles: contains the application roles assigned to the client, such asBooks.Read
The Books Reader then sends the token to the Books API:
Authorization: Bearer <access-token>
The API validates the token and can authorize the operation based on the roles claim.
For example, an endpoint requiring read access can require:
Books.Read
A client that was assigned only Books.Read would therefore not be authorized for an endpoint requiring:
Books.Write
Why separate client and API app registrations?
For a simple demo it may seem tempting to use a single app registration for everything, but keeping the client and API registrations separate makes the security model much clearer.
The API registration answers:
What resource is being protected?
What application permissions does it expose?
The client registration answers:
Which application is calling?
Which API permissions has this application been granted?
That separation also becomes useful as soon as multiple applications call the same API.
For example:
Books API
exposes:
Books.Read
Books.Write
Reporting Client
assigned:
Books.Read
Administration Client
assigned:
Books.Read
Books.Write
Both clients request:
api://<api-id>/.default
but their resulting roles claims differ because they have different app-role assignments.
Replacing the client secret with federation
The client secret in this first version is intentionally temporary.
Initially, the Books Reader authenticates to Microsoft Entra ID using:
client_id + client_secret
This is useful for understanding and testing the client credentials flow, but a long-lived secret has operational disadvantages. It must be stored securely, rotated, and replaced before it expires.
Once the Books Reader runs as an Azure App Service, the App Service can instead be given a system-assigned Managed Identity.
That Managed Identity represents the actual Azure workload that is running.
A federated identity credential can then be configured on the existing Books Reader client app registration so that the Managed Identity is trusted to authenticate as that application.
This extra indirection is intentional: the Managed Identity represents the Azure workload, while the client app registration remains the stable logical identity of the application and owns the API app-role assignments. This decouples the application’s identity and authorization from the Azure resource that hosts it, allowing the workload to move or be replaced without changing the application’s API permissions. If that separation is not needed, the API app role could instead be assigned directly to the Managed Identity’s service principal.
The authentication chain becomes:
Azure App Service
|
| Managed Identity
v
Managed Identity service principal
|
| trusted through federated credential
v
Books Reader Client App Registration
|
| request token for api://books/.default
v
Microsoft Entra ID
|
v
Books API access token
roles = Books.Read
At that point, the client secret can be removed entirely.
The important point is that the authorization model does not change.
Before federation:
client secret
-> Client App Registration
-> Books.Read
-> Books API
After federation:
Managed Identity
-> federated credential
-> Client App Registration
-> Books.Read
-> Books API
Only the mechanism used to authenticate the client application changes.
The Books Reader client app registration still represents the same logical application.
It still has the same application permission:
Books.Read
The Books API still exposes the same app roles.
And the resulting Books API access token still contains:
roles = Books.Read
This separation is useful because each component has one clear responsibility:
Managed Identity
identifies which Azure workload is running
Client App Registration
identifies which application is calling
API App Registration
identifies which API is being accessed
and defines which application roles exist
App-role assignment
determines what this particular client may do
The result is the same application-to-application authorization model without storing a long-lived client secret.
Why app roles instead of scopes?
This scenario uses the OAuth 2.0 client credentials flow.
There is no signed-in user. The Books Reader application calls the Books API using its own application identity.
That makes application permissions, implemented in Microsoft Entra ID as app roles, the appropriate authorization mechanism.
Delegated scopes serve a different purpose.
They describe permissions granted to an application acting on behalf of a signed-in user.
For example, an interactive application could request a delegated scope such as:
Books.Read
while acting on behalf of Alice.
The resulting authorization would represent a combination of:
application + signed-in user
That is different from the client credentials scenario shown here, where the caller is simply:
Books Reader application
and no user is involved.
An API can expose both delegated scopes and application app roles if it supports both types of callers.
For example:
Books API
Delegated scopes:
Books.Read
Application app roles:
Books.Read
Books.Write
An interactive application could use the delegated scope, while a background service could use the application role.
For the client credentials flow shown in this article, the relevant model is:
client credentials
+
application permissions / app roles
+
/.default
Admin consent
Application permissions are granted directly to an application rather than to a signed-in user.
For that reason, assigning an application permission such as:
Books.Read
normally requires tenant administrator consent.
Conceptually, the administrator is approving:
Allow the Books Reader application
to call the Books API
with the Books.Read application permission.
Once the app role has been assigned and admin consent has been granted, Microsoft Entra ID can include that role in access tokens issued to the Books Reader.
The complete relationship is therefore:
Books API App Registration
defines Books.Read
|
v
Books Reader Client App Registration
is assigned Books.Read
|
v
Administrator grants consent
|
v
Client requests api://books/.default
|
v
Microsoft Entra ID issues token
aud = Books API
roles = Books.Read
|
v
Books API authorizes Books.Read
That is the core model used throughout the rest of this article.