Repository navigation
Document tenant-specific authority selection for on-behalf-of token redemption #980
Description
MSAL client type
Confidential
Problem Statement
Location (AzureAD/microsoft-authentication-library-for-python @ default branch c53d5ea):
msal/application.py:2721-2758—acquire_token_on_behalf_of()and its docstringmsal/application.py:2680-2684— the existing runtime hint inacquire_token_for_client()
def acquire_token_on_behalf_of(self, user_assertion, scopes, claims_challenge=None, forwarded_client_claims=None, **kwargs):
"""Acquires token using on-behalf-of (OBO) flow.
The current app is a middle-tier service which was called with a token
representing an end user.
The current app can use such token (a.k.a. a user assertion) to request
another token to access downstream web API, on behalf of that user.
See `detail docs here <https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow>`_ .
... if self.authority.tenant.lower() in ["common", "organizations"]:
warnings.warn(
"Using /common or /organizations authority "
"in acquire_token_for_client() is unreliable. "
"Please use a specific tenant instead.", DeprecationWarning)Problem
A middle-tier web API commonly reuses a ConfidentialClientApplication that was configured with https://login.microsoftonline.com/common or /organizations (for example, copied from a multi-tenant sign-in setup) and then calls acquire_token_on_behalf_of(). Nothing at the Python entry point tells the developer which tenant the OBO redemption should target, which is where guest-user and Conditional Access scenarios become hard to diagnose.
The acquire_token_on_behalf_of() docstring links to the generic protocol page and the consent section, but says nothing about which authority/tenant to use. By contrast, acquire_token_for_client() already warns at runtime that /common or /organizations "is unreliable. Please use a specific tenant instead." There is no similar hint, in code or docs, for OBO.
The MSAL.NET documentation has an explicit section, "Important Note on On-Behalf-Of (OBO) Flow with Guest Users":
When performing the On-Behalf-Of (OBO) flow, especially with guest users, it is important to target the specific tenant, denoted by the tid claim from the client token. Do not use /common or /organizations in OBO, because the token will be for the user's home tenant.
MSAL Python currently has no equivalent guidance.
This is a documentation/usability request. I am not reporting a failing request or a security problem in MSAL Python itself.
Reproduction
There is no failure to reproduce; the gap is in the documentation. Reading help(msal.ConfidentialClientApplication.acquire_token_on_behalf_of) on c53d5ea shows no guidance on authority or tenant selection, and calling it on an application configured with /common or /organizations emits no hint, unlike acquire_token_for_client().
Environment: checked on dev at c53d5ea47f77.
Proposed solution
Suggested fix
Add guidance to the acquire_token_on_behalf_of() docstring (and, if appropriate, the MSAL Python docs on learn.microsoft.com or a sample) that:
- Recommends a tenant-specific authority for OBO redemption rather than
/commonor/organizations, mirroring the MSAL.NET note. - Explains that, for guest users, the tenant to target is the one in the incoming token's
tidclaim (the resource tenant), not the user's home tenant. - Shows a short Python example that takes
tidfrom the already validated incoming access token (that is, after the web API's own JWT validation, not from an unverified decode) and builds a per-tenantConfidentialClientApplication, e.g.:
# `claims` = claims of the incoming access token, AFTER your API has validated it
tenant_id = claims["tid"]
cca = msal.ConfidentialClientApplication(
client_id, client_credential=secret,
authority=f"https://login.microsoftonline.com/{tenant_id}",
token_cache=cache,
)
result = cca.acquire_token_on_behalf_of(incoming_token, scopes=["https://graph.microsoft.com/.default"])Out of scope: MSAL automatically trusting or decoding tid from an unvalidated assertion. A runtime DeprecationWarning, like the one in acquire_token_for_client(), could be considered separately, but documentation is the main request here, since a warning alone would not explain which tenant to use.
This docstring change covers points 1–3 above, adding a note with the MSAL.NET guidance and the example to acquire_token_on_behalf_of():
diff --git a/msal/application.py b/msal/application.py
--- a/msal/application.py
+++ b/msal/application.py
@@ -2731,6 +2731,24 @@ class ConfidentialClientApplication(ClientApplication): # server-side web app
See how to gain consent upfront for your middle-tier app from this article.
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#gaining-consent-for-the-middle-tier-application
+ .. note::
+ Use a tenant-specific authority for OBO rather than ``/common`` or
+ ``/organizations``, which would get a token for the user's home tenant.
+ This matters especially for guest users: target the tenant in the
+ ``tid`` claim of the incoming token (the resource tenant).
+ Read ``tid`` only after your web API has validated the incoming token,
+ for example::
+
+ # `claims` = claims of the incoming access token, AFTER your API has validated it
+ tenant_id = claims["tid"]
+ cca = msal.ConfidentialClientApplication(
+ client_id, client_credential=secret,
+ authority=f"https://login.microsoftonline.com/{tenant_id}",
+ token_cache=cache,
+ )
+ result = cca.acquire_token_on_behalf_of(
+ incoming_token, scopes=["https://graph.microsoft.com/.default"])
+
:param str user_assertion: The incoming token already received by this app
:param list[str] scopes: Scopes required by downstream API (a resource).
:param claims_challenge:This is a documentation-only change, so there is no test for it; on d73407a the updated acquire_token_on_behalf_of() docstring parses as reStructuredText without warnings (docutils 0.23, Python 3.13.18).
I can submit a PR for this.
Related: checked #53 (original OBO support request, implemented; does not cover tenant selection), #909 (closed unmerged; switched two OBO integration tests from /organizations to a tenant-specific authority because of Conditional Access failures in CI, a practical example of the same pitfall), and #147 (web API token-validation example, not OBO authority selection).
Activity