mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 21:44:18 +02:00
Bind ID-JAG audience to the issuer identifier
This commit is contained in:
parent
aaaca09a91
commit
62afdca775
4 changed files with 19 additions and 18 deletions
|
|
@ -720,7 +720,7 @@ For each ID-JAG presented at the token endpoint, the proxy checks that:
|
|||
- the JOSE header `typ` is `oauth-id-jag+jwt`;
|
||||
- the `iss` claim is one of the configured `trusted_issuers`;
|
||||
- the signature verifies against the issuer's published keys;
|
||||
- the `aud` claim identifies this authorization server;
|
||||
- the `aud` claim identifies this authorization server — configure your identity provider to mint assertions whose `aud` is the `issuer` value published at `/.well-known/oauth-authorization-server`, which is your `issuer_url` when you set one and your `base_url` otherwise;
|
||||
- the signed `client_id` claim matches the client presenting the assertion — an assertion the IdP minted for one client cannot be redeemed by another;
|
||||
- the signed `resource` claim names this server — an assertion minted for a different MCP server behind the same IdP is rejected;
|
||||
- `exp` (and `iat`/`nbf`, when present) place the assertion within a short lifetime and its validity window; and
|
||||
|
|
|
|||
|
|
@ -99,10 +99,12 @@ class IdentityAssertion(BaseModel):
|
|||
audience: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Expected `aud` value on the ID-JAG. When omitted, the audience is the "
|
||||
"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."
|
||||
"Expected `aud` value on the ID-JAG. When omitted, the audience is this "
|
||||
"server's issuer identifier — the `issuer` published in its authorization "
|
||||
"server metadata, which is `issuer_url` when set and `base_url` otherwise "
|
||||
"— 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."
|
||||
),
|
||||
)
|
||||
required_scopes: list[str] | None = Field(
|
||||
|
|
|
|||
|
|
@ -688,18 +688,17 @@ class OAuthProxy(OAuthProvider, ConsentMixin):
|
|||
allowed_redirect_uri_patterns=self._allowed_client_redirect_uris,
|
||||
)
|
||||
|
||||
# Identity assertion (SEP-990 ID-JAG): the audience the ID-JAG must be
|
||||
# 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.
|
||||
# Identity assertion (SEP-990 ID-JAG): per RFC 7523 §3 the `aud` must
|
||||
# identify this authorization server, and an authorization server is
|
||||
# identified by its issuer — the same value published as `issuer` in
|
||||
# the authorization server metadata, which is `issuer_url` (defaulting
|
||||
# to `base_url`).
|
||||
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.base_url),
|
||||
audience=str(self.issuer_url),
|
||||
)
|
||||
# ID-JAG access tokens are self-contained (no upstream token or JTI
|
||||
# mapping to delete), so revocation tracks their jtis here until the
|
||||
|
|
|
|||
|
|
@ -200,16 +200,16 @@ class TestOAuthProxyIssuerIdentity:
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
"issuer_url, expected",
|
||||
[(ISSUER_URL, BASE_URL_ISSUER), (None, BASE_URL_ISSUER)],
|
||||
[(ISSUER_URL, ISSUER), (None, BASE_URL_ISSUER)],
|
||||
)
|
||||
def test_identity_assertion_audience_stays_on_base_url(
|
||||
def test_identity_assertion_audience_is_issuer_identifier(
|
||||
self, issuer_url: str | None, expected: str
|
||||
):
|
||||
"""The ID-JAG audience is intentionally not moved to issuer_url.
|
||||
"""SEP-990: an ID-JAG is bound to the server's advertised issuer.
|
||||
|
||||
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.
|
||||
RFC 7523 §3 requires the `aud` to identify the authorization server,
|
||||
and an authorization server is identified by its issuer — the value
|
||||
published as `issuer` in the authorization server metadata.
|
||||
"""
|
||||
proxy = OAuthProxy(
|
||||
upstream_authorization_endpoint="https://upstream.example.com/authorize",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue