iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🔐

OAuth 2.0 OBO Flow with Python and Microsoft Entra ID [Minimal Implementation Sample]

に公開

Introduction

In microservice or BFF (Backend for Frontend) architectures, the requirement to "call a downstream API on behalf of a user" is very common.
In Microsoft Entra ID (formerly Azure AD), using the OAuth 2.0 On-Behalf-Of (OBO) flow enables API integration while securely delegating user credentials.

This article explains the following:

  • How the OBO flow works
  • A minimal implementation using Python (Flask + MSAL)
  • Processing sequence diagram (Mermaid support)
  • Common pitfalls and solutions
  • Security best practices

What is the OBO Flow?

The OBO flow is a mechanism for "a service (backend) to call another service (downstream API) on behalf of a user."

Typical scenario:

  • Frontend (SPA or Web App) authenticates the user and accesses the backend API.
  • The backend API uses the received user access token to call Microsoft Graph or an internal API (in practice, it issues and uses a new AT for the downstream API via OBO).

Minimal Implementation in Python (Overview)

  • frontend_app (Flask)
    Sign in with the authorization code flow → Obtain an AT (Access Token) for api://<BACKEND>/access_as_user → Send to the backend API
  • api_app (Flask)
    Request Graph delegated scopes (e.g., User.Read) using msal.ConfidentialClientApplication.acquire_token_on_behalf_of(...) → Call /v1.0/me

Refer to the GitHub repository (https://github.com/naokky-tech/sample-oboflow) for the implementation code.


Sample Implementation

OBO Flow Processing Sequence


✅ Azure Portal Configuration Steps

Purpose

  • Register a frontend app (public / SPA allowed)
  • Register a backend API app (confidential) within a single tenant and issue the custom scope access_as_user required for OBO.

1-1. Register the Backend API App

  • Create it via App registrations as well.
    API App Overview

  • Note the Application (client) ID and Directory (tenant) ID.
    App Overview

1-2. Expose the Backend API → Create a Custom Scope

  • Configuring the custom scope.
    Expose an API – access_as_user scope
  1. Expose an API → "Add a scope"
  2. Set the scope name to access_as_user
  3. Set the consent display name and description to something descriptive, such as "Search using user access permissions."
  4. Save and continue

1-3. Add Graph API Permissions (Scopes) Allowed for the Backend API

  • Graph API permissions settings.
    API permissions – Adding Graph API permissions
  1. API permissionsAdd a permission
  2. Check the Graph API permissions you want to use (only User.Read is used in the sample implementation).
  3. Click Add permissions.
  4. (If using an organizational tenant) Grant admin consent.

1-4. Create a Client Secret for the Backend API

  • Client secret configuration.
    Certificates & secrets – Client secret
  1. Certificates & secretsNew client secret
  2. Set the description and expiration, then click Add.
  3. Note the Value displayed immediately after generation (it cannot be viewed again).

0 If you forget to note it down, please recreate the secret.
0 Paste it into BACKEND_CLIENT_SECRET= in your .env file.


2-1. Register a New App (Frontend)

  • Frontend app registration
    New app registration
  1. App registrationsNew registration
  2. Enter any name
  3. Select "Accounts in this organizational directory only (Single tenant)"
  4. The Redirect URI can be left blank (can be added later) *In the demo, the frontend app is run on a local machine, so register it as "http://localhost:5000/auth/redirect" (adjust according to the implementation)
  5. Click Register

2-2. Grant Scopes to the Frontend

  • Granting backend API scopes
    API permissions – adding access_as_user
  1. API permissionsAdd a permission
  2. "<API App Name>" → check access_as_user
  3. Click Add permissions
  4. (If using an organizational tenant) Grant admin consent

✅ Sample App Implementation in Python

Please clone the repository from Git and try it out.

1. Clone the Repository

git clone https://github.com/naokky-tech/sample-oboflow.git
cd sample-oboflow

2. Follow the Execution Steps

Please refer to Git for detailed instructions.
https://github.com/naokky-tech/sample-oboflow

  • Create and activate a virtual environment
  • Install dependent packages
  • Configure the .env file
  • Start and verify operation

Once running, you can confirm that information is being retrieved based on the User.Read permissions as shown below.
API permissions – adding access_as_user


Pitfalls of OBO Flow

Cases Where OBO Cannot Be Used

  • Apps using custom signing keys
  • Wildcard Reply URL + id_token
  • Using App-only tokens for assertion (OBO is only for user-based identity)

Common Pitfalls and Solutions

Error Symptom Solution
Passing an ID token invalid_grant Pass the access token (aud=backend)
Requesting Graph scope from the frontend Audience mismatch Use access_as_user on the frontend; request Graph on the backend
Missing Graph permissions on backend insufficient privileges Grant User.Read to the backend + admin consent
Running OBO in a SPA Auth error Execute on a confidential client (backend)
Returning OBO tokens to the client Security risk Return data only

Security Best Practices

  • Do not relay tokens (return only data from the backend)
  • Conditional Access and MFA support → Handle interaction_required errors
  • Prevent audience mismatches → Specify scopes using the fully qualified name (api://<client-id>/<scope>)

Summary

  • OBO is a core pattern for achieving secure, user-centric API delegation.
  • It can be quickly implemented using Python + MSAL.
  • ID token misuse / scope configuration errors / insufficient permissions or consent are typical pitfalls.
  • When designing, clearly define "who obtains which token with which scope."

Reference Links

Discussion