# Using Inherited Permissions in Microsoft Entra OAuth OBO Flows for Agent Identities

My earlier two article focused delving the details of setting up the OAUTH OBO (On Behalf Of) configurations. You can find them [here](https://www.azureguru.net/understanding-the-oauth-flow-for-microsoft-entra-obo-on-behalf-of-agent-identities-part-1) and [here](https://www.azureguru.net/understanding-the-oauth-flow-for-microsoft-entra-obo-on-behalf-of-agent-identities-part-2).

In my previous [article](https://www.azureguru.net/understanding-the-oauth-flow-for-microsoft-entra-obo-on-behalf-of-agent-identities-part-2) , delegated permissions for the downstream resource were granted to an individual user.

In such cases we don't need to configure any inheritable permissions . But for scenarios where there are hundreds of users granting individual permissions is not feasible. I highlighted this scenario in my previous [article](https://www.azureguru.net/understanding-the-oauth-flow-for-microsoft-entra-obo-on-behalf-of-agent-identities-part-1#do-we-require-inheritable-permissions-in-this-example).

So, instead of granting downstream resource permissions separately to individual users the administrator should grant the permission to the Blueprint Principal. The child Agent Identities then inherit the eligible permissions during token issuance.

In our example following are the values of different entities :

|  |  |
| --- | --- |
| Principal Id for Azure storage | **59cd3e0a-2bbf-4250-865c-8173b7faed85** |
| App Id for Azure Storage | **e406a681-f3d4-42a8-90b6-c2b029497af1** |
| User Principal Id | **60ff3d98-a75b-44de-8596-b25b21fb55d5** |
| Blue print principal Id | **d9ef5200-ffc1-4fbc-a2e3-ca01326b1d3a** |
| Blue print app Id | **d7efbf70-e40b-4413-b763-b542264764bc** |
| Agent app Identity Id | **a1c52c58-02f0-439b-ac38-a9e3e785ff1c** |

You check [here](https://www.azureguru.net/understanding-the-oauth-flow-for-microsoft-entra-obo-on-behalf-of-agent-identities-part-1#configuring-userimpersonation-delegated-scope-for-azure-storage) on how to retrieve values of principal id for Azure storage and user principal id.

> **Note : Our downstream resource in this example is Azure Storage.**

The user principal already has the required permissions granted on the scope for the above downstream resource.

So for the demo example in this article we will have to first delete that first.

Verify whether the scope is assigned to the user.

Execute the following **GET** request

```python
https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$select=id,clientId,resourceId,principalId,consentType,scope&$filter=principalId eq {user principal Id} and clientid eq {blue print principal Id}
```

In my case the results are empty as I had previously deleted the permission grant for the user on the blueprint.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/f010d3d8-d67a-4a78-9850-df2d4f26c68e.png align="center")

Below, I am showing an example of deleting a permission grant associated with a user for a Azure storage downstream resource but for a different ClientID.

At first, retrieve grants for a resource service principal id.

Execute the following **GET** request where filter is on the downstream resource principal.

```python
https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$filter=resourceId eq {resource principal Id}
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/0631b976-9396-4bef-a8ae-f83af95678d8.png align="center")

Value returned against the **principal Id** is the user principal Id while the `id` value corresponds to the `Grant id` .

> The Grant id returned by the query above will be different across grants. You should be very specific while using these grant id's.

To delete the grant, use the following **DELETE** query

```python
https://graph.microsoft.com/v1.0/oauth2PermissionGrants/{Grant id}
```

In my case the grant id was

`'RP3JDB01lUObYXkdf4yxWgo-zVm_K1BChlyBc7f67YWYPf9gW6feRIWWslsh-1XV'`

So the **DELETE** request will be

```python
https://graph.microsoft.com/v1.0/oauth2PermissionGrants/'RP3JDB01lUObYXkdf4yxWgo-zVm_K1BChlyBc7f67YWYPf9gW6feRIWWslsh-1XV'
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/89a289e7-d25b-487b-b082-8092471d98b7.png align="center")

This deletes the resource permissions that were granted to the individual user principal.

Lets now execute the client app to fetch the OBO token. The token generation should fail with an error.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/fff94630-e8e8-4fbd-abcb-45b104574e35.gif align="center")

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/07b18be3-2da1-424c-bf22-ee86febe39fe.png align="center")

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/b2aca075-2ae0-4d40-8200-7c0e2917a6f6.png align="center")

```json
{"error":"invalid_grant","error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID 'a1c52c58-02f0-439b-ac38-a9e3e785ff1c' named 'Agent Identity'. Send an interactive authorization request for this user and resource. Trace ID: 968e3308-4794-4aaa-9413-2cbfd8e17801 Correlation ID: 04448a5b-9ea3-43ed-b377-36c344708633 Timestamp: 2026-11-08 19:09:47Z","error_codes":[65001],"timestamp":"2026-11-08 19:09:47Z","trace_id":"968e3308-4794-4aaa-9413-2cbfd8e17801","correlation_id":"04448a5b-9ea3-43ed-b377-36c344708633","suberror":"consent_required"}
```

### **Delegated Scope for Azure Storage**

Recall from [part 1](https://www.azureguru.net/understanding-the-oauth-flow-for-microsoft-entra-obo-on-behalf-of-agent-identities-part-1#configuring-userimpersonation-delegated-scope-for-azure-storage) of the series on the topic, where I configured the delegated scope for Azure storage for a user principal.

As we are dealing with blueprint, we have to assign the same delegated scopes for a downstream resource to an blueprint through blueprint principal id .

It means we are declaring that the Blueprint requires the `user_impersonation` delegated permission exposed by Azure Storage.

In simple words it means that allow agent identities under this blue print to request `user_impersonation` permissions on behalf of the signed in users.

Run the following **POST** request

```python
https://graph.microsoft.com/v1.0/oauth2PermissionGrants
```

Request Body

```json
{
  "clientId": Blueprint principal id, 
  "consentType": "AllPrincipals",
  "resourceId": Azure Storage principal id/resource id,
  "scope": "user_impersonation"
}
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/99dc3d44-69f5-4e03-b08f-beae25af359a.png align="center")

Cross verify by executing the following **GET** request

```python
https://graph.microsoft.com/v1.0/applications?$filter=appId eq {Blue Print App Id}&$select=id,appId,displayName,requiredResourceAccess
```

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/b716666e-62ce-4943-beac-b8e4f240db10.png align="center")

Note the id above marked in red

This id is generated when the delegated permission scope is created on the OAuthpermission registration. In our case it is generated for the Blueprint.

Next, we will have to create an `requiredResourceAccess` before we configure inheritablePermissions.

This operation means that blueprint requires Azure storage `user_impersonation` scope through id `03e0da56-190b-40ad-a80c-ea378c433f7f` which corresponds to the `user_impersonation` scope.

```python
https://graph.microsoft.com/v1.0/applications/d7efbf70-e40b-4413-b763-b542264764bc/microsoft.graph.agentIdentityBlueprint
```

```json
{
  "requiredResourceAccess": [
    {
      "resourceAppId": {Azure storage app id},
      "resourceAccess": [
        {
          "id": "03e0da56-190b-40ad-a80c-ea378c433f7f",
          "type": "Scope"
        }
      ]
    }
  ]
}
```

It might be possible that you get access issues while executing the request.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/f6062c1c-c53f-47bc-a44c-29e0c6d00999.png align="center")

To resolve this you will have to Consent to **AgenIdentityBluePrint.ReadWrite.All** permissions

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/c0403e5e-c703-462d-8526-05942677f6c6.png align="center")

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/0f1bfe60-b71e-493d-942b-81a98d463604.png align="center")

Once consented , the status changes to **Unconsent.**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/249e8ebf-433d-422d-a29c-6411247d2232.png align="center")

Re execute the request and now you will get a **204** response.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/b4aef74d-b04e-46bf-9309-486aba7b8dc5.png align="center")

To verify the scope on blue print , execute the following **GET** request

```python
https://graph.microsoft.com/v1.0/oauth2PermissionGrants?$filter=resourceId eq {resource Id} and clientId eq {Blueprint principal Id}
```

> ClientId below corresponds to the **blue print principal i**d and not the **blue print application id**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/0663adc0-b2fa-4214-ba4b-cebf26614bb4.png align="center")

At this stage I am sure you must be very much confused of the above steps.

So here is a simple breakdown :

- **requiredResourceAccess**
  - Declares that the Blueprint requires Azure Storage `user_impersonation`.
- **auth2PermissionGrants**
  - Records tenant-wide delegated consent for the Blueprint Principal to access Azure Storage.
- **inheritablePermissions**
  - Defines which delegated scopes can be inherited by Agent Identities provisioned from the Blueprint.

### Inheritable Permissions

**Check Inheritable permissions**

We have to ensure that there aren't any existing inheritable permissions so that we don't end up accidently overwriting them.

Execute the following **GET** request

```python
https://graph.microsoft.com/v1.0/applications/{Blue Print App Id}/microsoft.graph.agentIdentityBlueprint/inheritablePermissions
```

> Make sure that you are filtering on **Blue Print App Id** and not on **Blue Print Principal Id**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/b17b8f3b-91e0-491f-8e27-b3f7a35e8e15.png align="center")

Value \[\] is empty . It indicates that we don't have any inheritable permissions configured for the BluePrint.

> For more details on why we need inheritable permissions and how to configure and validate them you can refer to my article on the topic [here](https://www.azureguru.net/agent-identity-for-ai-agents-in-microsoft-foundry#inheritable-permissions).
>
> In that example I used PowerShell scripts to configure the permissions but here I will use GraphAPI instead.

Execute the following **POST** request to configure inheritable permissions for the blue print

```python
https://graph.microsoft.com/v1.0/applications/{Blue Print Id}/microsoft.graph.agentIdentityBlueprint/inheritablePermissions
```

```python
{
  "resourceAppId": {Azure storage app id},
  "inheritableScopes": {
    "@odata.type": "#microsoft.graph.enumeratedScopes",
    "scopes": [
      "user_impersonation"
    ]
  },
  "inheritableRoles": {
    "@odata.type": "#microsoft.graph.noRoles",
    "kind": "none"
  }
}
```

It should return `201 Created` response.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/18c36b68-9dea-4695-976e-b7a363e683a7.png align="center")

It means that we have now allowed all the underlying agent under the blue print to inherit the `user_impersonation` scope.

To check the inheritable permissions , re execute the same **GET** request that we executed earlier

```python
https://graph.microsoft.com/v1.0/applications/{Blue print App Id}/microsoft.graph.agentIdentityBlueprint/inheritablePermissions
```

and this time it will return the assigned inheritable permissions in the output.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/749894da-89ef-4cfd-8139-879e520b80db.png align="center")

### Execution

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/a3540407-01fa-4df7-b8c5-fa549a12d25b.gif align="center")

Lets inspect our token :

Final downstream resource **aud** value which in our example is Azure storage and the **appid** value which is our agent identity id which proves that the agent is acting on behalf of the user and **iss** value which is the issuer for my tenant. Also returned are the user details.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/7cb1b755-665e-4080-beed-fc7250bdc417.png align="center")

In this part we have the **oid** which is the user principal id and **scp** with value **user_impersonation** and **xms_par_app_azp** value of blue print id which confirms that this is an agent identity token

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/e4477d74-cc90-45e6-be6c-ed6c7eac1d09.png align="center")

That's all !!!

### Take aways

Granting delegated permissions to individual users works well in scenarios where access to downstream resources needs to be managed at an individual user level than on the granted tenant-level. This approach provides greater flexibility in small environments where there are not many agent identities.

But in a much larger environments this approach can become complicated. Hence its much better and practical to assign the resource delegate accesses scopes to the blue prints which enables the underlying agent identities to inherit the allowed scope from the blue print.

I hope this article has helped you get started with implementing scope-level access control for Microsoft Entra Agent Identities

Thanks for reading !!!
