Obo token flow

Francesco Bruni 20 Reputation points
2024-08-01T07:20:21.04+00:00

Dears,

I got an issue.

I got an application that use oAuth2 code flow to get user groups. In order to retrieve this data, I use MS graph scopes.
I'd like using this token to get another token for a custom api of another application.

The other application exposes an API that gets authorized on the first one and granted by admins.

In order to get the second token, I'm testing the OBO: I use the client/secret of the first app, with the token retrieved in the first call and set the scope of the custom api.

But assertion fails and the second token cannot be retrieved.
If I pass the custom scope API in the first call, everything works. It's supposed to work this way?

best,

FB

Microsoft Security | Microsoft Entra | Microsoft Entra ID

Answer accepted by question author
Anonymous
2024-08-01T21:13:55.62+00:00

Hi @Francesco Bruni , the OBO flow is used when a middle-tier web API needs to call another web API on behalf of the user as follows:

  1. The user signs in to the client application and obtains an access token.
  2. The client application sends the access token to the middle-tier web API.
  3. The middle-tier web API uses the access token to request a new access token from Azure AD for the downstream web API.
  4. Azure AD validates the access token and issues a new access token for the downstream web API.

It looks like you are skipping step 2 and using the access token obtained by the client application to request a new access token for the downstream web API. This won't work because the access token obtained by the client application is not a valid token for the downstream API.

You need to pass the access token obtained by the client application to the middle-tier web API and use it to request a new access token for the downstream web API. The new access token will be valid for the downstream web API because it was obtained on behalf of the user.

You also need to make sure that the access token obtained by the client application has the necessary scopes to call the middle-tier web API and that the new access token requested has the necessary scopes to call the downstream API.

Please let me know if you have any questions and I can help you further.

If this answer helps you please mark "Accept Answer" so other users can reference it.

Thank you,

James

Was this answer helpful?

1 person found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. Tuomas Poukkula 0 Reputation points
    2026-10-07T15:37:38.65+00:00

    Hi FB,

    You have hit a classic architectural hurdle with the Microsoft Entra ID On-Behalf-Of (OBO) flow. The behavior you are seeing is exactly how the protocol is designed to work, and the root cause lies in the Audience (aud claim) of your initial token.

    Why the assertion fails in your OBO attempt: When you perform the initial Auth Code flow using MS Graph scopes, Entra ID mints an Access Token specifically for MS Graph. The audience (aud) of that token is [https://graph.microsoft.com](https://graph.microsoft.com).

    The fundamental rule of the OBO flow in Entra ID is that the middle-tier API (App 1) can only exchange a token where App 1 itself is the audience. You cannot use a token minted for MS Graph as an assertion to get a token for App 2. Entra ID rejects this because App 1 is effectively trying to trade a token that doesn't belong to it.

    Why it works when you pass the custom API scope in the first call: When you pass App 2's scope in the initial frontend call, you are bypassing the OBO flow entirely. You are simply doing a direct delegation from the Frontend to App 2. However, in a true middle-tier architecture, this defeats the purpose of having App 1 broker the requests.

    The Architectural Fix (The Correct OBO Pattern)

    To make this work deterministically, you need to restructure your token requests so App 1 acts as a true middle-tier broker.

    Step 1: Expose an API on App 1 In Entra ID, go to App 1 -> Expose an API and create a scope (e.g., api://<App1-Client-ID>/access_as_user).

    Step 2: Configure API Permissions

    • In App 1's App Registration, grant Delegated permissions to App 2's Custom API.

    In App 1's App Registration, grant Delegated permissions to MS Graph (for your user groups).

    Ensure admin consent is granted for both if required.

    Step 3: The Initial Call (Frontend to App 1) Your client application must authenticate the user and request a token using App 1's scope (the one created in Step 1).

    Result: You receive a token where aud = App 1.

    Step 4: The OBO Calls (App 1 to Downstream APIs) Now that App 1 holds a token where it is the intended audience, it can use this token as the assertion in the OBO flow. App 1 will actually perform two separate OBO requests:

    OBO Request A: Use the App 1 token to request a token for MS Graph scopes (to retrieve user groups).

    OBO Request B: Use the exact same App 1 token to request a token for App 2's custom API scopes.

    This pattern ensures strict boundary control, correct audiences, and allows App 1 to orchestrate multiple downstream APIs using a single frontend authentication event.

    Hope this clarifies the mechanics of the flow!Hi FB,

    You have hit a classic architectural hurdle with the Microsoft Entra ID On-Behalf-Of (OBO) flow. The behavior you are seeing is exactly how the protocol is designed to work, and the root cause lies in the Audience (aud claim) of your initial token.

    Why the assertion fails in your OBO attempt: When you perform the initial Auth Code flow using MS Graph scopes, Entra ID mints an Access Token specifically for MS Graph. The audience (aud) of that token is [https://graph.microsoft.com](https://graph.microsoft.com).

    The fundamental rule of the OBO flow in Entra ID is that the middle-tier API (App 1) can only exchange a token where App 1 itself is the audience. You cannot use a token minted for MS Graph as an assertion to get a token for App 2. Entra ID rejects this because App 1 is effectively trying to trade a token that doesn't belong to it.

    Why it works when you pass the custom API scope in the first call: When you pass App 2's scope in the initial frontend call, you are bypassing the OBO flow entirely. You are simply doing a direct delegation from the Frontend to App 2. However, in a true middle-tier architecture, this defeats the purpose of having App 1 broker the requests.

    The Architectural Fix (The Correct OBO Pattern)

    To make this work deterministically, you need to restructure your token requests so App 1 acts as a true middle-tier broker.

    Step 1: Expose an API on App 1 In Entra ID, go to App 1 -> Expose an API and create a scope (e.g., api://<App1-Client-ID>/access_as_user).

    Step 2: Configure API Permissions

    In App 1's App Registration, grant Delegated permissions to App 2's Custom API.

    In App 1's App Registration, grant Delegated permissions to MS Graph (for your user groups).

    Ensure admin consent is granted for both if required.

    Step 3: The Initial Call (Frontend to App 1) Your client application must authenticate the user and request a token using App 1's scope (the one created in Step 1).

    Result: You receive a token where aud = App 1.

    Step 4: The OBO Calls (App 1 to Downstream APIs) Now that App 1 holds a token where it is the intended audience, it can use this token as the assertion in the OBO flow. App 1 will actually perform two separate OBO requests:

    OBO Request A: Use the App 1 token to request a token for MS Graph scopes (to retrieve user groups).

    OBO Request B: Use the exact same App 1 token to request a token for App 2's custom API scopes.

    This pattern ensures strict boundary control, correct audiences, and allows App 1 to orchestrate multiple downstream APIs using a single frontend authentication event.

    Hope this clarifies the mechanics of the flow!

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.