Back Articles

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 token
  • appid or azp: identifies the calling client application
  • roles: contains the application roles assigned to the client, such as Books.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

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.

Steps

  1. Create the App Registration for the API
  2. Create App Registration for Client
  3. Request and inspect an access token
  4. Secretless API token retrieval using managed identity federation
  5. Putting the token to use; creating the back-end API.