From beb38e255f115636d9152bd5e289121a6e899842 Mon Sep 17 00:00:00 2001 From: Jian-Min Lin Date: Thu, 11 Jun 2026 22:17:16 +0800 Subject: [PATCH] fix(oauth): use Protected Resource Metadata scopes in MCP DCR flow The Dynamic Client Registration flow seeded the registration request `scope` from the Authorization Server's `scopes_supported` (RFC 8414), which is a full catalog of every scope the AS can grant across all resources. Per RFC 9728 and the MCP Scope Selection Strategy, the resource-specific Protected Resource Metadata `scopes_supported` is the correct, least-privilege source. The PRM is already fetched in this function; this change prefers its `scopes_supported` and keeps the AS `scopes_supported` only as a fallback when the PRM advertises none. Mirrors the static-credentials fix in #24690. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/open_webui/utils/oauth.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index f2e1aa14f6..d1aeaec1a7 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -435,6 +435,17 @@ async def get_oauth_client_info_with_dynamic_client_registration( # Attempt to fetch OAuth server metadata to get registration endpoint & scopes resource_metadata = await get_protected_resource_metadata(oauth_server_url) resource = resource_metadata.resource + + # Prefer the resource-specific scopes from the Protected Resource Metadata + # (RFC 9728 Section 2) over the Authorization Server's scopes_supported + # (RFC 8414 Section 2). The AS scopes_supported is a full catalog of every + # scope the server can grant across all resources, whereas the PRM + # scopes_supported represents what this specific resource requires - making + # it the correct, least-privilege source. This mirrors the static-credentials + # flow (see #24690). + if resource_metadata.scopes_supported: + oauth_client_metadata.scope = ' '.join(resource_metadata.scopes_supported) + discovery_urls = resource_metadata.get_discovery_urls(oauth_server_url) for url in discovery_urls: async with aiohttp.ClientSession(trust_env=True) as session: