Build1 publisher3 min readPublished
Cognito rejects RFC 8707, so it cannot be the authorization server for an MCP client
A dev.to writeup documents Amazon Cognito killing a spec-compliant MCP connector at the first redirect, and the only fix on offer is a proxy that strips one parameter and nothing else.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- MCP's authorization spec wants access tokens to be audience-bound, and the mechanism it points at is RFC 8707 Resource Indicators: the client appends a resource parameter to the /authorize and /token requests naming the server it wants a token for, and the authorization server is supposed to bind the resulting token to that resource.
- Cognito does not implement RFC 8707. It does not bind the token to the resource; it rejects the request for carrying an unrecognized parameter.
- The connector's first hop, the redirect to /authorize, dies before the user ever sees a login box.
- A spec-compliant MCP client sends the resource parameter unconditionally; Claude's connector does, and it cannot be turned off from the client side.
- The rejection cannot be fixed in Cognito's configuration and cannot be fixed in the client.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A writeup published on dev.to documents Amazon Cognito refusing the first request a spec-compliant MCP client makes: Cognito does not implement RFC 8707 Resource Indicators, and rather than ignoring the `resource` parameter it rejects the request for carrying an unrecognized parameter [2]. According to that account, the redirect to `/authorize` dies before the user is shown a login box [3], which means a team standardizing agent OAuth on Cognito has no working flow at hop one.
The collision is structural. MCP's authorization spec wants access tokens audience-bound, and the mechanism it points at is RFC 8707: the client appends a `resource` parameter to `/authorize` and `/token` naming the server it wants a token for, and the authorization server binds the issued token to that resource [1]. A spec-compliant client sends it unconditionally, Claude's connector included, and the author notes you do not get to switch it off from the client side [4]. There is also no Cognito setting that accepts or ignores the parameter [5]. Both ends are fixed, so anything that makes this work has to sit between them [18].
The fix described is a thin, same-origin OAuth proxy in front of Cognito's `/authorize` and `/token`, forwarding everything through untouched except for stripping `resource` on the way in [6]. The warning attached to it is the useful part: strip `resource` and nothing else, because `code_challenge` and `code_challenge_method` have to survive verbatim or you break PKCE and trade one failure for another [7]. The author's verification checklist encodes exactly that, as "/oauth/authorize strips the RFC 8707 resource param while preserving code_challenge" [8].
Stripping the parameter also strips the guarantee. The audience binding RFC 8707 was supposed to provide now has to be enforced at the resource server [9], and MCP's language is not soft about it: servers MUST validate that access tokens were issued specifically for them as the intended audience, per RFC 8707 Section 2, and MUST NOT accept or transit any other tokens [10].
Here Cognito diverges again from habit. A Cognito access token has no `aud` claim at all; the client it was minted for is named in `client_id`, and the token carries `token_use: access` where an ID token carries `token_use: id` [11]. So the check that satisfies the MCP requirement is a `client_id` comparison against your configured client plus a requirement that `token_use` equals `access` [12]. Skip the `token_use` check and an ID token minted for the same client sails through the access-token path [13]. Skip the `client_id` check and any access token from the same user pool is accepted as yours, including one issued to a completely different app client [14]. The author's framing is worth keeping: the pool is the trust boundary Cognito gives you for free, the client is the one you have to draw yourself [15]. Alongside that, pin one algorithm and one key type, RS256 and RSA, compared against the values in the attacker-supplied token header rather than inferred from it, since algorithm confusion needs a negotiable `alg` field to exist [16].
Watch whether Cognito ever accepts or ignores `resource`, because until it does, every MCP deployment behind it carries a hand-written proxy in the auth path. This is one team's week of work on a project called cogDepot, and the author says none of it appears in the happy-path docs [17]; treat the checklist as a starting point for your own tests, not as coverage.