Skip to content

Commit 5b1d45b

Browse files
committed
Update aud assertion to match AS issuer
1 parent a0bb22a commit 5b1d45b

File tree

2 files changed

+28
-7
lines changed

2 files changed

+28
-7
lines changed

src/mcp/client/auth/extensions/client_credentials.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,18 @@ def _add_client_authentication_jwt(self, *, token_data: dict[str, Any]):
102102
"""Add JWT assertion for client authentication to token endpoint parameters."""
103103
if not self.jwt_parameters:
104104
raise OAuthTokenError("Missing JWT parameters for private_key_jwt flow")
105+
if not self.context.oauth_metadata:
106+
raise OAuthTokenError("Missing OAuth metadata for private_key_jwt flow")
105107

106-
token_url = self._get_token_endpoint()
107-
assertion = self.jwt_parameters.to_assertion(with_audience_fallback=token_url)
108+
# We need to set the audience to the issuer identifier of the authorization server
109+
# https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-01#name-updates-to-rfc-7523
110+
issuer = str(self.context.oauth_metadata.issuer)
111+
assertion = self.jwt_parameters.to_assertion(with_audience_fallback=issuer)
108112

109113
# When using private_key_jwt, in a client_credentials flow, we use RFC 7523 Section 2.2
110114
token_data["client_assertion"] = assertion
111115
token_data["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
112-
# We need to set the audience to the token endpoint, the audience is difference from the one in claims
116+
# We need to set the audience to the resource server, the audience is difference from the one in claims
113117
# it represents the resource server that will validate the token
114118
token_data["audience"] = self.context.get_resource_url()
115119

@@ -119,9 +123,13 @@ async def _exchange_token_jwt_bearer(self) -> httpx.Request:
119123
raise OAuthFlowError("Missing client info")
120124
if not self.jwt_parameters:
121125
raise OAuthFlowError("Missing JWT parameters")
126+
if not self.context.oauth_metadata:
127+
raise OAuthTokenError("Missing OAuth metadata")
122128

123-
token_url = self._get_token_endpoint()
124-
assertion = self.jwt_parameters.to_assertion(with_audience_fallback=token_url)
129+
# We need to set the audience to the issuer identifier of the authorization server
130+
# https://datatracker.ietf.org/doc/html/draft-ietf-oauth-rfc7523bis-01#name-updates-to-rfc-7523
131+
issuer = str(self.context.oauth_metadata.issuer)
132+
assertion = self.jwt_parameters.to_assertion(with_audience_fallback=issuer)
125133

126134
token_data = {
127135
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
@@ -134,6 +142,7 @@ async def _exchange_token_jwt_bearer(self) -> httpx.Request:
134142
if self.context.client_metadata.scope:
135143
token_data["scope"] = self.context.client_metadata.scope
136144

145+
token_url = self._get_token_endpoint()
137146
return httpx.Request(
138147
"POST", token_url, data=token_data, headers={"Content-Type": "application/x-www-form-urlencoded"}
139148
)

tests/client/auth/extensions/test_client_credentials.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import AnyHttpUrl, AnyUrl
66

77
from mcp.client.auth.extensions.client_credentials import JWTParameters, RFC7523OAuthClientProvider
8-
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
8+
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata, OAuthToken
99

1010

1111
class MockTokenStorage:
@@ -75,6 +75,12 @@ async def test_token_exchange_request_jwt_predefined(self, rfc7523_oauth_provide
7575
redirect_uris=None,
7676
scope="read write",
7777
)
78+
rfc7523_oauth_provider.context.oauth_metadata = OAuthMetadata(
79+
issuer=AnyHttpUrl("https://api.example.com"),
80+
authorization_endpoint=AnyHttpUrl("https://api.example.com/authorize"),
81+
token_endpoint=AnyHttpUrl("https://api.example.com/token"),
82+
registration_endpoint=AnyHttpUrl("https://api.example.com/register"),
83+
)
7884
rfc7523_oauth_provider.context.client_metadata = rfc7523_oauth_provider.context.client_info
7985
rfc7523_oauth_provider.context.protocol_version = "2025-06-18"
8086
rfc7523_oauth_provider.jwt_parameters = JWTParameters(
@@ -108,6 +114,12 @@ async def test_token_exchange_request_jwt(self, rfc7523_oauth_provider: RFC7523O
108114
redirect_uris=None,
109115
scope="read write",
110116
)
117+
rfc7523_oauth_provider.context.oauth_metadata = OAuthMetadata(
118+
issuer=AnyHttpUrl("https://api.example.com"),
119+
authorization_endpoint=AnyHttpUrl("https://api.example.com/authorize"),
120+
token_endpoint=AnyHttpUrl("https://api.example.com/token"),
121+
registration_endpoint=AnyHttpUrl("https://api.example.com/register"),
122+
)
111123
rfc7523_oauth_provider.context.client_metadata = rfc7523_oauth_provider.context.client_info
112124
rfc7523_oauth_provider.context.protocol_version = "2025-06-18"
113125
rfc7523_oauth_provider.jwt_parameters = JWTParameters(
@@ -141,7 +153,7 @@ async def test_token_exchange_request_jwt(self, rfc7523_oauth_provider: RFC7523O
141153
assertion,
142154
key="a-string-secret-at-least-256-bits-long",
143155
algorithms=["HS256"],
144-
audience="https://api.example.com/token",
156+
audience="https://api.example.com/",
145157
subject="1234567890",
146158
issuer="foo",
147159
verify=True,

0 commit comments

Comments
 (0)