From 4815a39839a4dcaf38bf23531763acfda2e83e7d Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:51:41 +0200 Subject: [PATCH] refac: apply OAuth role management on the token exchange endpoint The endpoint now resolves the user's role through the same role management path the OAuth callback uses, and persists it when it differs. Where role management is enabled and no roles resolve, the exchange is denied instead of falling back to DEFAULT_USER_ROLE, which would demote an existing user. Deployments whose provider returns roles only in the ID token, or whose OAUTH_ROLES_CLAIM, OAUTH_ALLOWED_ROLES or OAUTH_ADMIN_ROLES configuration is incomplete, will see token exchange rejected where it previously issued a session. --- backend/open_webui/routers/auths.py | 13 +++++++++++++ backend/open_webui/utils/oauth.py | 10 +++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 86de689458..15f9c0c429 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -1705,4 +1705,17 @@ async def token_exchange( detail='User not found. Please sign in via the web interface first.', ) + role = await oauth_manager.get_user_role(user, user_data, deny_on_missing_roles=True) + if user.role != role: + await Users.update_user_role_by_id(user.id, role, db=db) + user.role = role + await publish_event( + request, + EVENTS.USER_ROLE_UPDATED, + actor=user, + subject_id=user.id, + source='oauth', + data={'role': role, 'provider': provider}, + ) + return await create_session_response(request, user, db, source='oauth') diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index fce053dae8..9ae58de945 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -1498,7 +1498,7 @@ class OAuthManager: log.error(f'Exception during token refresh for provider {provider}: {e}') return None - async def get_user_role(self, user, user_data): + async def get_user_role(self, user, user_data, deny_on_missing_roles=False): auth_config = await get_oauth_runtime_config() user_count = await Users.get_num_users() if user and user_count == 1: @@ -1551,6 +1551,14 @@ class OAuthManager: log.debug('Accepted user roles: %s', oauth_allowed_roles) log.debug('Accepted admin roles: %s', oauth_admin_roles) + # No ID token here to backfill claims from, so falling back to DEFAULT_USER_ROLE would demote the user. + if not oauth_roles and deny_on_missing_roles: + log.warning('OAuth role management enabled but no roles resolved for the user; denying token exchange') + raise HTTPException( + status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + # If roles are present in the token, they must match; otherwise deny access if oauth_roles: matched = False