From ea31747572fb0a3e5f3ff6c5bbd2ba2342508e32 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:54:00 -0500 Subject: [PATCH] Add refresh token support defaults to GoogleProvider Set access_type=offline and prompt=consent by default to ensure refresh tokens are returned by Google. Also expose extra_authorize_params so users can customize authorization behavior. --- src/fastmcp/server/auth/providers/google.py | 18 +++++++++ tests/server/auth/providers/test_google.py | 43 +++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/fastmcp/server/auth/providers/google.py b/src/fastmcp/server/auth/providers/google.py index 28a817561..0f394e869 100644 --- a/src/fastmcp/server/auth/providers/google.py +++ b/src/fastmcp/server/auth/providers/google.py @@ -225,6 +225,7 @@ class GoogleProvider(OAuthProxy): client_storage: AsyncKeyValue | None = None, jwt_signing_key: str | bytes | NotSetT = NotSet, require_authorization_consent: bool = True, + extra_authorize_params: dict[str, str] | None = None, ): """Initialize Google OAuth provider. @@ -252,6 +253,10 @@ class GoogleProvider(OAuthProxy): When True, users see a consent screen before being redirected to Google. When False, authorization proceeds directly without user confirmation. SECURITY WARNING: Only disable for local development or testing environments. + extra_authorize_params: Additional parameters to forward to Google's authorization endpoint. + By default, GoogleProvider sets {"access_type": "offline", "prompt": "consent"} to ensure + refresh tokens are returned. You can override these defaults or add additional parameters. + Example: {"prompt": "select_account"} to let users choose their Google account. """ settings = GoogleProviderSettings.model_validate( @@ -299,6 +304,18 @@ class GoogleProvider(OAuthProxy): settings.client_secret.get_secret_value() if settings.client_secret else "" ) + # Set Google-specific defaults for extra authorize params + # access_type=offline ensures refresh tokens are returned + # prompt=consent forces consent screen to get refresh token (Google only issues on first auth otherwise) + google_defaults = { + "access_type": "offline", + "prompt": "consent", + } + # User-provided params override defaults + if extra_authorize_params: + google_defaults.update(extra_authorize_params) + extra_authorize_params_final = google_defaults + # Initialize OAuth proxy with Google endpoints super().__init__( upstream_authorization_endpoint="https://accounts.google.com/o/oauth2/v2/auth", @@ -314,6 +331,7 @@ class GoogleProvider(OAuthProxy): client_storage=client_storage, jwt_signing_key=settings.jwt_signing_key, require_authorization_consent=require_authorization_consent, + extra_authorize_params=extra_authorize_params_final, ) logger.debug( diff --git a/tests/server/auth/providers/test_google.py b/tests/server/auth/providers/test_google.py index ab634e3be..66d4aa8c3 100644 --- a/tests/server/auth/providers/test_google.py +++ b/tests/server/auth/providers/test_google.py @@ -119,3 +119,46 @@ class TestGoogleProvider: # Provider should initialize successfully with these scopes assert provider is not None + + def test_extra_authorize_params_defaults(self): + """Test that Google-specific defaults are set for refresh token support.""" + provider = GoogleProvider( + client_id="123456789.apps.googleusercontent.com", + client_secret="GOCSPX-test123", + jwt_signing_key="test-secret", + ) + + # Should have Google-specific defaults for refresh token support + assert provider._extra_authorize_params == { + "access_type": "offline", + "prompt": "consent", + } + + def test_extra_authorize_params_override_defaults(self): + """Test that user can override default extra authorize params.""" + provider = GoogleProvider( + client_id="123456789.apps.googleusercontent.com", + client_secret="GOCSPX-test123", + jwt_signing_key="test-secret", + extra_authorize_params={"prompt": "select_account"}, + ) + + # User override should replace the default + assert provider._extra_authorize_params["prompt"] == "select_account" + # But other defaults should remain + assert provider._extra_authorize_params["access_type"] == "offline" + + def test_extra_authorize_params_add_new_params(self): + """Test that user can add additional authorize params.""" + provider = GoogleProvider( + client_id="123456789.apps.googleusercontent.com", + client_secret="GOCSPX-test123", + jwt_signing_key="test-secret", + extra_authorize_params={"login_hint": "user@example.com"}, + ) + + # New param should be added + assert provider._extra_authorize_params["login_hint"] == "user@example.com" + # Defaults should still be present + assert provider._extra_authorize_params["access_type"] == "offline" + assert provider._extra_authorize_params["prompt"] == "consent"