mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Keep ID-JAG audience on base_url, out of scope for issuer identity
This commit is contained in:
parent
a7248480e2
commit
98ac0402df
4 changed files with 39 additions and 11 deletions
|
|
@ -100,10 +100,9 @@ class IdentityAssertion(BaseModel):
|
|||
default=None,
|
||||
description=(
|
||||
"Expected `aud` value on the ID-JAG. When omitted, the audience is the "
|
||||
"authorization server's own issuer identifier — its `issuer_url`, which "
|
||||
"defaults to `base_url` — and that is where the ID-JAG's `aud` must point "
|
||||
"per SEP-990. Override only when the IdP mints assertions bound to a "
|
||||
"different audience identifier."
|
||||
"authorization server's own issuer URL (its base URL), which is where the "
|
||||
"ID-JAG's `aud` must point per SEP-990. Override only when the IdP mints "
|
||||
"assertions bound to a different audience identifier."
|
||||
),
|
||||
)
|
||||
required_scopes: list[str] | None = Field(
|
||||
|
|
|
|||
|
|
@ -689,15 +689,17 @@ class OAuthProxy(OAuthProvider, ConsentMixin):
|
|||
)
|
||||
|
||||
# Identity assertion (SEP-990 ID-JAG): the audience the ID-JAG must be
|
||||
# bound to is this authorization server's own issuer identifier, which
|
||||
# is `issuer_url` (defaulting to `base_url`) — the same value advertised
|
||||
# as `issuer` in the authorization server metadata.
|
||||
# bound to is this authorization server's own issuer URL (base_url).
|
||||
# Deliberately left on base_url while the metadata issuer moves to
|
||||
# issuer_url: changing it would reject assertions an IdP is already
|
||||
# minting, and recovering needs IdP-side reconfiguration rather than a
|
||||
# re-authorization. Tracked separately.
|
||||
self._identity_assertion: IdentityAssertion | None = identity_assertion
|
||||
self._identity_assertion_validator: IdentityAssertionValidator | None = None
|
||||
if identity_assertion is not None:
|
||||
self._identity_assertion_validator = IdentityAssertionValidator(
|
||||
config=identity_assertion,
|
||||
audience=str(self.issuer_url),
|
||||
audience=str(self.base_url),
|
||||
)
|
||||
# ID-JAG access tokens are self-contained (no upstream token or JTI
|
||||
# mapping to delete), so revocation tracks their jtis here until the
|
||||
|
|
|
|||
22
risk.py
Normal file
22
risk.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from fastmcp.server.auth.oauth_proxy import OAuthProxy
|
||||
from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
|
||||
|
||||
def mk(**kw):
|
||||
p = OAuthProxy(
|
||||
upstream_authorization_endpoint="https://idp.example.com/authorize",
|
||||
upstream_token_endpoint="https://idp.example.com/token",
|
||||
upstream_client_id="cid", upstream_client_secret="sec",
|
||||
token_verifier=StaticTokenVerifier(tokens={"t": {"client_id": "c"}}),
|
||||
jwt_signing_key="x"*64,
|
||||
**kw,
|
||||
)
|
||||
p.get_routes("/mcp")
|
||||
return p
|
||||
|
||||
# Case A: issuer_url NOT set -> must be identical before/after
|
||||
a = mk(base_url="https://example.com")
|
||||
print("A no issuer_url -> jwt iss:", a.jwt_issuer.issuer)
|
||||
|
||||
# Case B: issuer_url set differently -> this is what changes
|
||||
b = mk(base_url="https://example.com/api", issuer_url="https://example.com")
|
||||
print("B issuer_url set -> jwt iss:", b.jwt_issuer.issuer)
|
||||
|
|
@ -200,12 +200,17 @@ class TestOAuthProxyIssuerIdentity:
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
"issuer_url, expected",
|
||||
[(ISSUER_URL, ISSUER), (None, BASE_URL_ISSUER)],
|
||||
[(ISSUER_URL, BASE_URL_ISSUER), (None, BASE_URL_ISSUER)],
|
||||
)
|
||||
def test_identity_assertion_audience_is_issuer_identifier(
|
||||
def test_identity_assertion_audience_stays_on_base_url(
|
||||
self, issuer_url: str | None, expected: str
|
||||
):
|
||||
"""SEP-990: an ID-JAG is bound to the server's advertised issuer."""
|
||||
"""The ID-JAG audience is intentionally not moved to issuer_url.
|
||||
|
||||
Changing it would reject assertions an IdP is already minting, and the
|
||||
operator could only recover by reconfiguring the IdP. Tracked as
|
||||
separate work from the issuer identity fix.
|
||||
"""
|
||||
proxy = OAuthProxy(
|
||||
upstream_authorization_endpoint="https://upstream.example.com/authorize",
|
||||
upstream_token_endpoint="https://upstream.example.com/token",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue