diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index dc128e88d51..e2216a911e1 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -1908,6 +1908,17 @@ async def auth_callback(request: Request, state: Optional[str] = None): """Verify login""" verbose_proxy_logger.info(f"Starting SSO callback with state: {state}") + oauth_error = request.query_params.get("error") + oauth_error_description = request.query_params.get("error_description") + if oauth_error: + oauth_error_detail = f"OAuth error: {oauth_error}" + if oauth_error_description: + oauth_error_detail += f", error_description: {oauth_error_description}" + raise HTTPException( + status_code=400, + detail=oauth_error_detail, + ) + # Check if this is a CLI login (state starts with our CLI prefix) from litellm.constants import LITELLM_CLI_SESSION_TOKEN_PREFIX from litellm.proxy._types import LiteLLM_JWTAuth diff --git a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py index 976048b9521..3b123a80cd7 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py +++ b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py @@ -2711,6 +2711,7 @@ class TestCLIKeyRegenerationFlow: # Mock request mock_request = MagicMock(spec=Request) + mock_request.query_params = {} cli_state = f"{LITELLM_CLI_SESSION_TOKEN_PREFIX}:cli-new-session-key-456" @@ -2784,6 +2785,26 @@ class TestCLIKeyRegenerationFlow: received_response=None, ) + @pytest.mark.asyncio + async def test_auth_callback_raises_on_oauth_error(self): + """Test that auth_callback returns a 401 when the provider redirects with an OAuth error""" + from litellm.proxy.management_endpoints.ui_sso import auth_callback + + mock_request = MagicMock(spec=Request) + mock_request.query_params = { + "error": "access_denied", + "error_description": "User denied consent", + } + + with pytest.raises(HTTPException) as exc_info: + await auth_callback(request=mock_request, state="test-state") + + assert exc_info.value.status_code == 400 + assert ( + exc_info.value.detail + == "OAuth error: access_denied, error_description: User denied consent" + ) + def test_get_redirect_url_does_not_include_existing_key_in_url(self): """Test that redirect URL generation does NOT include existing_key in URL""" from litellm.proxy.management_endpoints.ui_sso import SSOAuthenticationHandler