diff --git a/fastmcp_slim/fastmcp/server/auth/oauth_proxy/consent.py b/fastmcp_slim/fastmcp/server/auth/oauth_proxy/consent.py index a42e6dfab..753aae198 100644 --- a/fastmcp_slim/fastmcp/server/auth/oauth_proxy/consent.py +++ b/fastmcp_slim/fastmcp/server/auth/oauth_proxy/consent.py @@ -13,9 +13,8 @@ import hmac import json import secrets import time -from base64 import urlsafe_b64encode -from typing import TYPE_CHECKING, Any -from urllib.parse import urlencode, urlparse +from typing import TYPE_CHECKING +from urllib.parse import urlparse from pydantic import AnyUrl from starlette.requests import Request @@ -277,43 +276,6 @@ class ConsentMixin: return False return hmac.compare_digest(actual, expected_token) - def _build_upstream_authorize_url( - self: OAuthProxy, txn_id: str, transaction: dict[str, Any] - ) -> str: - """Construct the upstream IdP authorization URL using stored transaction data.""" - query_params: dict[str, Any] = { - "response_type": "code", - "client_id": self._upstream_client_id, - "redirect_uri": f"{str(self.base_url).rstrip('/')}{self._redirect_path}", - "state": txn_id, - } - - scopes_to_use = transaction.get("scopes") or self.required_scopes or [] - if scopes_to_use: - query_params["scope"] = " ".join(scopes_to_use) - - # If PKCE forwarding was enabled, include the proxy challenge - proxy_code_verifier = transaction.get("proxy_code_verifier") - if proxy_code_verifier: - challenge_bytes = hashlib.sha256(proxy_code_verifier.encode()).digest() - proxy_code_challenge = ( - urlsafe_b64encode(challenge_bytes).decode().rstrip("=") - ) - query_params["code_challenge"] = proxy_code_challenge - query_params["code_challenge_method"] = "S256" - - # Forward resource indicator if present in transaction - if self._forward_resource: - if resource := transaction.get("resource"): - query_params["resource"] = resource - - # Extra configured parameters - if self._extra_authorize_params: - query_params.update(self._extra_authorize_params) - - separator = "&" if "?" in self._upstream_authorization_endpoint else "?" - return f"{self._upstream_authorization_endpoint}{separator}{urlencode(query_params)}" - async def _handle_consent( self: OAuthProxy, request: Request ) -> HTMLResponse | RedirectResponse: diff --git a/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py b/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py index db4f9b4ca..1037439bc 100644 --- a/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py +++ b/fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py @@ -26,6 +26,7 @@ from collections import OrderedDict from collections.abc import AsyncIterator from contextlib import asynccontextmanager from typing import Any, Literal +from urllib.parse import urlencode import anyio import httpx2 @@ -789,6 +790,40 @@ class OAuthProxy(OAuthProvider, ConsentMixin): return code_verifier, code_challenge + def _build_upstream_authorize_url( + self, txn_id: str, transaction: dict[str, Any] + ) -> str: + """Construct the upstream IdP authorization URL using stored transaction data.""" + query_params: dict[str, Any] = { + "response_type": "code", + "client_id": self._upstream_client_id, + "redirect_uri": f"{str(self.base_url).rstrip('/')}{self._redirect_path}", + "state": txn_id, + } + + scopes_to_use = transaction.get("scopes") or self.required_scopes or [] + if scopes_to_use: + query_params["scope"] = " ".join(scopes_to_use) + + proxy_code_verifier = transaction.get("proxy_code_verifier") + if proxy_code_verifier: + challenge_bytes = hashlib.sha256(proxy_code_verifier.encode()).digest() + proxy_code_challenge = ( + urlsafe_b64encode(challenge_bytes).decode().rstrip("=") + ) + query_params["code_challenge"] = proxy_code_challenge + query_params["code_challenge_method"] = "S256" + + if self._forward_resource: + if resource := transaction.get("resource"): + query_params["resource"] = resource + + if self._extra_authorize_params: + query_params.update(self._extra_authorize_params) + + separator = "&" if "?" in self._upstream_authorization_endpoint else "?" + return f"{self._upstream_authorization_endpoint}{separator}{urlencode(query_params)}" + # ------------------------------------------------------------------------- # Client Registration (Local Implementation) # -------------------------------------------------------------------------