Skip to content

Commit dd876b5

Browse files
authored
Merge pull request #2439 from jlowin/oidc-proxy-extra-params
Add extra_authorize_params and extra_token_params to OIDCProxy
2 parents 24204cd + e0f0f81 commit dd876b5

File tree

2 files changed

+118
-3
lines changed

2 files changed

+118
-3
lines changed

src/fastmcp/server/auth/oidc_proxy.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ def __init__(
222222
token_endpoint_auth_method: str | None = None,
223223
# Consent screen configuration
224224
require_authorization_consent: bool = True,
225+
# Extra parameters
226+
extra_authorize_params: dict[str, str] | None = None,
227+
extra_token_params: dict[str, str] | None = None,
225228
) -> None:
226229
"""Initialize the OIDC proxy provider.
227230
@@ -259,6 +262,11 @@ def __init__(
259262
When True, users see a consent screen before being redirected to the upstream IdP.
260263
When False, authorization proceeds directly without user confirmation.
261264
SECURITY WARNING: Only disable for local development or testing environments.
265+
extra_authorize_params: Additional parameters to forward to the upstream authorization endpoint.
266+
Useful for provider-specific parameters like prompt=consent or access_type=offline.
267+
Example: {"prompt": "consent", "access_type": "offline"}
268+
extra_token_params: Additional parameters to forward to the upstream token endpoint.
269+
Useful for provider-specific parameters during token exchange.
262270
"""
263271
if not config_url:
264272
raise ValueError("Missing required config URL")
@@ -335,10 +343,24 @@ def __init__(
335343
if redirect_path:
336344
init_kwargs["redirect_path"] = redirect_path
337345

346+
# Build extra params, merging audience with user-provided params
347+
# User params override audience if there's a conflict
348+
final_authorize_params: dict[str, str] = {}
349+
final_token_params: dict[str, str] = {}
350+
338351
if audience:
339-
extra_params = {"audience": audience}
340-
init_kwargs["extra_authorize_params"] = extra_params
341-
init_kwargs["extra_token_params"] = extra_params
352+
final_authorize_params["audience"] = audience
353+
final_token_params["audience"] = audience
354+
355+
if extra_authorize_params:
356+
final_authorize_params.update(extra_authorize_params)
357+
if extra_token_params:
358+
final_token_params.update(extra_token_params)
359+
360+
if final_authorize_params:
361+
init_kwargs["extra_authorize_params"] = final_authorize_params
362+
if final_token_params:
363+
init_kwargs["extra_token_params"] = final_token_params
342364

343365
super().__init__(**init_kwargs) # ty: ignore[invalid-argument-type]
344366

tests/server/auth/test_oidc_proxy.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,3 +783,96 @@ def test_custom_token_verifier_with_audience_allowed(
783783
validate_proxy(mock_get, proxy, oidc_config)
784784
assert proxy._extra_authorize_params == {"audience": "test-audience"}
785785
assert proxy._extra_token_params == {"audience": "test-audience"}
786+
787+
def test_extra_authorize_params_initialization(self, valid_oidc_configuration_dict):
788+
"""Test extra authorize params initialization."""
789+
with patch(
790+
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
791+
) as mock_get:
792+
oidc_config = OIDCConfiguration.model_validate(
793+
valid_oidc_configuration_dict
794+
)
795+
mock_get.return_value = oidc_config
796+
797+
proxy = OIDCProxy(
798+
config_url=TEST_CONFIG_URL,
799+
client_id=TEST_CLIENT_ID,
800+
client_secret=TEST_CLIENT_SECRET,
801+
base_url=TEST_BASE_URL,
802+
jwt_signing_key="test-secret",
803+
extra_authorize_params={
804+
"prompt": "consent",
805+
"access_type": "offline",
806+
},
807+
)
808+
809+
validate_proxy(mock_get, proxy, oidc_config)
810+
811+
assert proxy._extra_authorize_params == {
812+
"prompt": "consent",
813+
"access_type": "offline",
814+
}
815+
# Token params should be empty since we didn't set them
816+
assert proxy._extra_token_params == {}
817+
818+
def test_extra_token_params_initialization(self, valid_oidc_configuration_dict):
819+
"""Test extra token params initialization."""
820+
with patch(
821+
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
822+
) as mock_get:
823+
oidc_config = OIDCConfiguration.model_validate(
824+
valid_oidc_configuration_dict
825+
)
826+
mock_get.return_value = oidc_config
827+
828+
proxy = OIDCProxy(
829+
config_url=TEST_CONFIG_URL,
830+
client_id=TEST_CLIENT_ID,
831+
client_secret=TEST_CLIENT_SECRET,
832+
base_url=TEST_BASE_URL,
833+
jwt_signing_key="test-secret",
834+
extra_token_params={"custom_param": "custom_value"},
835+
)
836+
837+
validate_proxy(mock_get, proxy, oidc_config)
838+
839+
# Authorize params should be empty since we didn't set them
840+
assert proxy._extra_authorize_params == {}
841+
assert proxy._extra_token_params == {"custom_param": "custom_value"}
842+
843+
def test_extra_params_merge_with_audience(self, valid_oidc_configuration_dict):
844+
"""Test that extra params merge with audience, with user params taking precedence."""
845+
with patch(
846+
"fastmcp.server.auth.oidc_proxy.OIDCConfiguration.get_oidc_configuration"
847+
) as mock_get:
848+
oidc_config = OIDCConfiguration.model_validate(
849+
valid_oidc_configuration_dict
850+
)
851+
mock_get.return_value = oidc_config
852+
853+
proxy = OIDCProxy(
854+
config_url=TEST_CONFIG_URL,
855+
client_id=TEST_CLIENT_ID,
856+
client_secret=TEST_CLIENT_SECRET,
857+
base_url=TEST_BASE_URL,
858+
audience="original-audience",
859+
jwt_signing_key="test-secret",
860+
extra_authorize_params={
861+
"prompt": "consent",
862+
"audience": "overridden-audience", # Should override the audience param
863+
},
864+
extra_token_params={"custom": "value"},
865+
)
866+
867+
validate_proxy(mock_get, proxy, oidc_config)
868+
869+
# User's extra_authorize_params should override audience
870+
assert proxy._extra_authorize_params == {
871+
"audience": "overridden-audience",
872+
"prompt": "consent",
873+
}
874+
# Token params should have both audience (from audience param) and custom
875+
assert proxy._extra_token_params == {
876+
"audience": "original-audience",
877+
"custom": "value",
878+
}

0 commit comments

Comments
 (0)