From ae6793dd4f209455387bcc418a5c775eec0b16c3 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:01:35 +0200 Subject: [PATCH] fix: OAuth token refresh ignores the client_secret_basic auth method (#32507) When Open WebUI authenticates to the OAuth provider with client_secret_basic, either because OAUTH_TOKEN_ENDPOINT_AUTH_METHOD is set to it or because an MCP tool server's OAuth provider only offers that method, sign-in sent the client ID and secret in an HTTP Basic header but the token refresh always put them in the request body. Providers that only accept Basic rejected every refresh with invalid_client, so once the access token expired Open WebUI deleted the user's saved OAuth tokens, and model connections and tool servers set to forward the user's OAuth token stopped working until the user signed in again. The refresh now authenticates the same way sign-in does for client_secret_basic, with the credentials only in the Basic header. Setups using client_secret_post, or leaving the setting unset, still send the credentials in the request body exactly as before. Fixes #32506 --- backend/open_webui/utils/oauth.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 76f1ebb346..ee690088b6 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -1186,8 +1186,14 @@ class OAuthClientManager: if should_send_oauth_resource(client_info): refresh_data['resource'] = client_info.resource + client_auth = None if hasattr(client, 'client_secret') and client.client_secret: - refresh_data['client_secret'] = client.client_secret + if client.client_kwargs.get('token_endpoint_auth_method') == 'client_secret_basic': + client_auth = aiohttp.BasicAuth(client.client_id, client.client_secret) + # Some providers reject a body client_id next to Basic as a second credential (#19415) + del refresh_data['client_id'] + else: + refresh_data['client_secret'] = client.client_secret # Add scope if available in client kwargs (some providers require it on refresh) if ( @@ -1202,6 +1208,7 @@ class OAuthClientManager: async with session_http.post( token_endpoint, data=refresh_data, + auth=client_auth, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as r: @@ -1538,9 +1545,15 @@ class OAuthManager: 'refresh_token': token_data['refresh_token'], 'client_id': client.client_id, } + client_auth = None # Add client_secret if available (some providers require it) if hasattr(client, 'client_secret') and client.client_secret: - refresh_data['client_secret'] = client.client_secret + if client.client_kwargs.get('token_endpoint_auth_method') == 'client_secret_basic': + client_auth = aiohttp.BasicAuth(client.client_id, client.client_secret) + if not OAUTH_ACCESS_TOKEN_REQUEST_INCLUDE_CLIENT_ID: + del refresh_data['client_id'] + else: + refresh_data['client_secret'] = client.client_secret # Add scope if available in client kwargs (some providers require it on refresh) if ( @@ -1555,6 +1568,7 @@ class OAuthManager: async with session_http.post( token_endpoint, data=refresh_data, + auth=client_auth, headers={'Content-Type': 'application/x-www-form-urlencoded'}, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as r: