iTranslated by AI
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) forapi://<BACKEND>/access_as_user→ Send to the backend API -
api_app (Flask)
Request Graph delegated scopes (e.g., User.Read) usingmsal.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_userrequired for OBO.
1-1. Register the Backend API App
-
Create it via App registrations as well.

-
Note the Application (client) ID and Directory (tenant) ID.

1-2. Expose the Backend API → Create a Custom Scope
- Configuring the custom scope.
- Expose an API → "Add a scope"
- Set the scope name to
access_as_user - Set the consent display name and description to something descriptive, such as "Search using user access permissions."
- Save and continue
1-3. Add Graph API Permissions (Scopes) Allowed for the Backend API
- Graph API permissions settings.
- API permissions → Add a permission
- Check the Graph API permissions you want to use (only
User.Readis used in the sample implementation). - Click Add permissions.
- (If using an organizational tenant) Grant admin consent.
1-4. Create a Client Secret for the Backend API
- Client secret configuration.
- Certificates & secrets → New client secret
- Set the description and expiration, then click Add.
- 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
- App registrations → New registration
- Enter any name
- Select "Accounts in this organizational directory only (Single tenant)"
- 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)
- Click Register
2-2. Grant Scopes to the Frontend
- Granting backend API scopes
- API permissions → Add a permission
- "<API App Name>" → check access_as_user
- Click Add permissions
- (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.
- 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.

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_requirederrors -
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
- Microsoft identity platform and OAuth 2.0 OBO flow (Official documentation) (Microsoft Learn)
- MSAL for Python documentation (Microsoft Learn)
- MSAL Python OBO sample (GitHub) (GitHub)
- Microsoft Graph permissions reference (Microsoft Learn)
Discussion