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
This commit is contained in:
Classic298 2026-10-16 01:01:35 +02:00 • committed by GitHub
parent ec39df7da5
commit ae6793dd4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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: