@@ -229,6 +229,7 @@ def __init__(
229229 callback_handler : Callable [[], Awaitable [tuple [str , str | None ]]] | None = None ,
230230 timeout : float = 300.0 ,
231231 client_metadata_url : str | None = None ,
232+ validate_resource_url : Callable [[str , str | None ], Awaitable [str | None ]] | None = None ,
232233 ):
233234 """Initialize OAuth2 authentication.
234235
@@ -243,6 +244,11 @@ def __init__(
243244 advertises client_id_metadata_document_supported=true, this URL will be
244245 used as the client_id instead of performing dynamic client registration.
245246 Must be a valid HTTPS URL with a non-root pathname.
247+ validate_resource_url: Optional callback to override resource URL validation.
248+ Called with (server_url, prm_resource) where prm_resource is the resource
249+ from Protected Resource Metadata (or None if not present). Must return the
250+ resource URL to use, or None to omit it. If not provided, default validation
251+ rejects mismatched resources per RFC 8707.
246252
247253 Raises:
248254 ValueError: If client_metadata_url is provided but not a valid HTTPS URL
@@ -263,6 +269,7 @@ def __init__(
263269 timeout = timeout ,
264270 client_metadata_url = client_metadata_url ,
265271 )
272+ self ._validate_resource_url_callback = validate_resource_url
266273 self ._initialized = False
267274
268275 async def _handle_protected_resource_response (self , response : httpx .Response ) -> bool :
@@ -476,12 +483,17 @@ async def _handle_oauth_metadata_response(self, response: httpx.Response) -> Non
476483 metadata = OAuthMetadata .model_validate_json (content )
477484 self .context .oauth_metadata = metadata
478485
479- def _validate_resource_match (self , prm : ProtectedResourceMetadata ) -> None :
486+ async def _validate_resource_match (self , prm : ProtectedResourceMetadata ) -> None :
480487 """Validate that PRM resource matches the server URL per RFC 8707."""
481- if not prm .resource :
488+ prm_resource = str (prm .resource ) if prm .resource else None
489+
490+ if self ._validate_resource_url_callback is not None :
491+ await self ._validate_resource_url_callback (self .context .server_url , prm_resource )
492+ return
493+
494+ if not prm_resource :
482495 return
483496 default_resource = resource_url_from_server_url (self .context .server_url )
484- prm_resource = str (prm .resource )
485497 # Normalize: Pydantic AnyHttpUrl adds trailing slash to root URLs
486498 # (e.g. "https://example.com/") while resource_url_from_server_url may not.
487499 if not default_resource .endswith ("/" ):
@@ -533,7 +545,7 @@ async def async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.
533545 prm = await handle_protected_resource_response (discovery_response )
534546 if prm :
535547 # Validate PRM resource matches server URL (RFC 8707)
536- self ._validate_resource_match (prm )
548+ await self ._validate_resource_match (prm )
537549 self .context .protected_resource_metadata = prm
538550
539551 # todo: try all authorization_servers to find the OASM
0 commit comments