@@ -193,6 +193,8 @@ def __init__(
193193 token_endpoint = f"https://{ base_authority } /{ tenant_id } /oauth2/v2.0/token"
194194
195195 # Initialize OAuth proxy with Azure endpoints
196+ # Remember there's hooks called, such as _prepare_scopes_for_token_exchange
197+ # and _prepare_scopes_for_upstream_refresh
196198 super ().__init__ (
197199 upstream_authorization_endpoint = authorization_endpoint ,
198200 upstream_token_endpoint = token_endpoint ,
@@ -206,7 +208,6 @@ def __init__(
206208 client_storage = client_storage ,
207209 jwt_signing_key = jwt_signing_key ,
208210 require_authorization_consent = require_authorization_consent ,
209- # Advertise full scopes including OIDC (even though we only validate non-OIDC)
210211 valid_scopes = parsed_required_scopes ,
211212 )
212213
@@ -318,16 +319,37 @@ def _build_upstream_authorize_url(
318319 # Let parent build the URL with prefixed scopes
319320 return super ()._build_upstream_authorize_url (txn_id , modified_transaction )
320321
322+ def _prepare_scopes_for_token_exchange (self , scopes : list [str ]) -> list [str ]:
323+ """Prepare scopes for Azure authorization code exchange.
324+
325+ Azure requires scopes during token exchange (AADSTS28003 error if missing).
326+ Azure only allows ONE resource per token request (AADSTS28000), so we only
327+ include scopes for this API plus OIDC scopes.
328+
329+ Args:
330+ scopes: Scopes from the authorization request (unprefixed)
331+
332+ Returns:
333+ List of scopes for Azure token endpoint
334+ """
335+ # Prefix scopes for this API
336+ prefixed_scopes = self ._prefix_scopes_for_azure (scopes or [])
337+
338+ # Add OIDC scopes only (not other API scopes) to avoid AADSTS28000
339+ if self .additional_authorize_scopes :
340+ prefixed_scopes .extend (
341+ s for s in self .additional_authorize_scopes if s in OIDC_SCOPES
342+ )
343+
344+ deduplicated = list (dict .fromkeys (prefixed_scopes ))
345+ logger .debug ("Token exchange scopes: %s" , deduplicated )
346+ return deduplicated
347+
321348 def _prepare_scopes_for_upstream_refresh (self , scopes : list [str ]) -> list [str ]:
322349 """Prepare scopes for Azure token refresh.
323350
324- Azure requires:
325- 1. Fully-qualified custom scopes (e.g., "api://xxx/read" not "read")
326- 2. Microsoft Graph scopes (e.g., "User.Read", "openid") sent as-is
327- 3. Additional scopes from provider config (additional_authorize_scopes)
328-
329- This method transforms base client scopes for Azure while keeping them
330- unprefixed in storage to prevent accumulation.
351+ Azure requires fully-qualified scopes and only allows ONE resource per
352+ token request (AADSTS28000). We include scopes for this API plus OIDC scopes.
331353
332354 Args:
333355 scopes: Base scopes from RefreshToken (unprefixed, e.g., ["read"])
@@ -338,22 +360,19 @@ def _prepare_scopes_for_upstream_refresh(self, scopes: list[str]) -> list[str]:
338360 logger .debug ("Base scopes from storage: %s" , scopes )
339361
340362 # Filter out any additional_authorize_scopes that may have been stored
341- # (they shouldn't be in storage, but clean them up if they are)
342363 additional_scopes_set = set (self .additional_authorize_scopes or [])
343364 base_scopes = [s for s in scopes if s not in additional_scopes_set ]
344365
345- # Prefix base scopes with identifier_uri for Azure using shared helper
366+ # Prefix base scopes with identifier_uri for Azure
346367 prefixed_scopes = self ._prefix_scopes_for_azure (base_scopes )
347368
348- # Add additional scopes (Graph + OIDC) for the Azure request
349- # These are NOT stored in RefreshToken, only sent to Azure
369+ # Add OIDC scopes only (not other API scopes) to avoid AADSTS28000
350370 if self .additional_authorize_scopes :
351- prefixed_scopes .extend (self .additional_authorize_scopes )
371+ prefixed_scopes .extend (
372+ s for s in self .additional_authorize_scopes if s in OIDC_SCOPES
373+ )
352374
353- # Deduplicate while preserving order (in case older tokens have duplicates)
354- # Use dict.fromkeys() for O(n) deduplication with order preservation
355375 deduplicated_scopes = list (dict .fromkeys (prefixed_scopes ))
356-
357376 logger .debug ("Scopes for Azure token endpoint: %s" , deduplicated_scopes )
358377 return deduplicated_scopes
359378
0 commit comments