Gateway inbound auth: migrate to Cognito JWT (token-exchange Phase 1) - #778
Merged
Conversation
AgentCore outbound on-behalf-of token exchange can only exchange a *user*
subject token, and the Gateway's IAM/SigV4 inbound auth never carried one.
Switch the Gateway's inbound authorizer to CUSTOM_JWT trusting the platform
Cognito user pool, so the agent's per-user access token reaches the Gateway
and becomes exchangeable for a target-specific token downstream.
One Gateway remains the endstate. AgentCore permits exactly one inbound
authorizer per Gateway (`authorizerType` is a scalar), but outbound
credentials are per-target — so this single Gateway still fronts both
IAM-invoked Lambda targets (arxiv, policy-search) and future
OAuth/token-exchange targets. Splitting Gateways by backend type would
split along the wrong seam.
- `config.gateway.inboundAuth` ('jwt' default | 'iam') with env/context
override and synth-time enum validation. 'iam' is a code-free rollback:
both AuthorizerType and AuthorizerConfiguration are CloudFormation
"no interruption" updates, so flipping does not replace the Gateway or
orphan its registered targets (confirmed via cdk diff change set).
- Cognito user pool + BFF app client passed as construct refs, not SSM:
the pool is a sibling in this stack and CFN resolves SSM template
parameters before any stack resource exists.
- Authorizer validates `allowedClients`, NOT `allowedAudience` — Cognito
*access* tokens carry `client_id` and no `aud` claim, so an audience
check can never match and would 401 every call.
- Construct throws at synth when 'jwt' is selected without Cognito refs,
rather than deploying a Gateway that rejects everything.
- AGENTCORE_GATEWAY_INBOUND_AUTH threaded to the inference runtime from the
same config value, so agent data-plane auth and the deployed authorizer
cannot drift.
Tests: 5 new Gateway construct tests (JWT default, client_id-not-audience,
discovery URL, iam rollback, throw-without-refs). New `mockCognitoRefs`
helper uses `from*` imports so it adds zero resources and existing
resourceCountIs assertions stay meaningful. 480/480 jest, tsc clean.
Refs docs/specs/AGENTCORE_GATEWAY_TOKEN_EXCHANGE_PLAN.md (Phase 1A)
Pairs with the CUSTOM_JWT inbound authorizer: the agent now presents the signed-in user's Cognito access token as a Bearer credential instead of SigV4-signing Gateway calls with the task's IAM identity. `self.auth_token` already held that token, so this threads it through rather than adding a new credential source. Both halves must deploy together — once the authorizer flips, SigV4 is rejected, and until it flips a bearer token is. - `_build_gateway_auth()` selects bearer vs SigV4 from AGENTCORE_GATEWAY_INBOUND_AUTH, which CDK sets from the same `config.gateway.inboundAuth` that builds the authorizer — so the two cannot drift. Unrecognized values fall back to 'jwt' with a warning rather than silently dropping user auth. - Reuses the existing OAuthBearerAuth from integrations/oauth_auth.py instead of adding a second bearer implementation. - Tokens are bound per client instance, never module-level, so one user's credential cannot leak into another user's Gateway client. - Missing token in jwt mode raises GatewayAuthError; GatewayIntegration catches it and degrades to no Gateway tools rather than failing the turn (every call would 401 anyway). Error text points at the headless-grant path, the likely cause for a scheduled run. Verified no non-user caller loses access: scheduled/headless runs already mint a real Cognito access token via CognitoRefreshBearerAuth (and run_agent_headless requires it to start today), and the API-key path calls Bedrock Converse directly without touching the Gateway. Tests: 11 new — auth-mode resolution, the actual Authorization header value, missing/empty token rejection, iam rollback ignoring the token, cross-user token isolation, and graceful degradation. Full backend suite 5419 passed. Refs docs/specs/AGENTCORE_GATEWAY_TOKEN_EXCHANGE_PLAN.md (Phase 1B)
The Gateway inbound-auth default flips to `jwt`, which means the Gateway stops accepting SigV4. In this repo the agent is the only data-plane caller, so the migration is self-contained — but a fork that added its own Lambda, scheduled job, or service calling the Gateway with SigV4 would start getting 401s with nothing in the code to warn them. Documents `CDK_GATEWAY_INBOUND_AUTH` in the per-environment overrides table and adds a "Gateway inbound authentication" section covering the upgrade check, the `iam` escape hatch, and the two things that are *not* affected (registered targets keep working; the authorizer swap never replaces the Gateway). Also notes the infra+backend deploy-together requirement. Docs-only. Astro build clean (51 pages).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 of the AgentCore Gateway token-exchange work (plan). Moves the Gateway's inbound authorizer from IAM/SigV4 to Cognito JWT, and switches the agent to presenting the signed-in user's access token.
This is the prerequisite for on-behalf-of (OBO) token exchange: AgentCore can only exchange a user subject token, and an IAM-authorized Gateway never carried one.
Why one Gateway, not two
AgentCore permits exactly one inbound authorizer per Gateway (
authorizerTypeis a scalar — no "accept either SigV4 or JWT" mode), but outbound credentials are per-target. So a single Gateway still fronts IAM-invoked Lambda targets (arxiv,policy-search) and future OAuth/token-exchange targets for legacy token-service APIs. Splitting Gateways by backend type would split along the wrong seam.Changes
Infrastructure (
d56588bd)config.gateway.inboundAuth—'jwt'(default) or'iam', with env/context override and synth-time enum validationCUSTOM_JWTagainst the platform Cognito poolAGENTCORE_GATEWAY_INBOUND_AUTHthreaded to the inference runtimeAgent (
9c3c11f2)Authorization: Bearer <cognito-access-token>instead of SigV4OAuthBearerAuth; tokens bound per client instanceGatewayAuthError, caught to degrade to no-Gateway-tools rather than failing the turnTwo details worth reviewer attention
allowedClients, notallowedAudience. Cognito access tokens carryclient_idand noaudclaim, so an audience check can never match and would 401 every call. There is a test assertingAllowedAudiencestays unset.No Gateway replacement. Both
AuthorizerTypeandAuthorizerConfigurationare CloudFormation "no interruption" updates. Confirmed against the live dev stack with a real change set — the Gateway shows as[~]modify-in-place, so the physical ID and both registered targets survive:Deploy note
The two commits must deploy together. Once the authorizer flips, SigV4 is rejected; until it flips, a bearer token is. This touches both
infrastructure/**andbackend/**, so merging firesplatform.ymlandbackend.yml. They share thedeploy-<ref>concurrency group so they queue, but order is nondeterministic — expect a window where Gateway tools 401 until both finish. Self-healing.Rollback without a code change: set
CDK_GATEWAY_INBOUND_AUTH=iamand redeploy.Verification
tscclean, 480/480 jest (+5 new)cdk diffagainst live dev confirms in-place Gateway updateNon-user callers verified safe
CognitoRefreshBearerAuthmints one from the stored grant, andrun_agent_headlessalready requires it today/chat/api-converse)Not in this PR
Phases 2–4 (token-service
/v2/oauth/tokenRFC 8693 endpoint, AgentCore OBO connector provisioning, and the Directory pilot target) land separately. The Directory MCP server itself lives in themcp-serversrepo.