feat(auth/consent-portal): add consent-dashboard sample for auth-code-flow gateway targets - #2070
Conversation
…-flow gateway targets
Adds 01-features/05-authenticate-and-authorize/07-consent-portal-auth-code-flow-targets:
an end-to-end solution where end users authorize a third-party target themselves on
the AWS-managed consent dashboard, out of band, and the agent never handles their
credential.
user -> FastAPI BFF -> Strands agent on AgentCore Runtime -> AgentCore Gateway
-> GitHub MCP server (AUTHORIZATION_CODE / 3LO target)
- Entra ID and Okta both supported from one deploy path, selected by a required
--entra / --okta profile flag; IdP shape lives in deploy/idps/<name>.json while
tenant values stay in .env.
- Passthrough on the agent->gateway hop: runtime and gateway share a discovery URL
and audience, so the caller's JWT is forwarded unchanged. Not an OBO sample.
- Schema-upfront MCP target (44 GitHub tools), so tools/list works before consent
and only tools/call needs the user's token.
- Optional Lambda RESPONSE interceptor that rewrites the -32042 elicitation URL to
the portal, for steering every MCP client rather than relying on agent-side
handling. Off by default.
- Idempotent numbered deploy scripts and a teardown that verifies what it deleted.
Documents the invariants that otherwise fail late and quietly: three
non-interchangeable callback URLs, the gateway authorizer and portal IdP provider
needing byte-identical discovery URLs, Entra's pairwise sub requiring one shared
audience, and Okta requiring a custom authorization server.
Verified end to end against a real AWS account with both IdPs: portal sign-in,
GitHub Connect, and a live GitHub MCP call using the user's own authorization.
The Bedrock model hop is not exercised (test account daily token quota is zero,
pending a Support increase); README states this in a Validation status callout.
Refs #2069
…ments, drop "3LO" as primary term
Two follow-ups from review.
IAM: neither role uses "Resource": "*" any more. The AgentCore Identity actions
(GetWorkloadAccessToken*, GetResourceOauth2Token, CompleteResourceTokenAuth) do
support resource-level permissions, per the machine-readable AWS service
reference, so both the gateway service role and the portal execution role are now
scoped to
arn:aws:bedrock-agentcore:<region>:<account>:token-vault/*
arn:aws:bedrock-agentcore:<region>:<account>:workload-identity-directory/*
Both families are listed because no supported resource type is marked required.
The vault and directory ids stay wildcarded deliberately — AgentCore owns those
names and mints a workload identity per runtime/gateway, so pinning today's
"default" would break when the service picks another. The child ARNs
(.../oauth2credentialprovider/*, .../workload-identity/*) are intentionally
omitted: an IAM wildcard spans "/", so they are redundant, and IAM Access
Analyzer flags them as such. Both rendered policies now validate with no
Access Analyzer findings.
README also records the condition keys these actions accept
(InboundJwtClaim/{iss,aud,sub,client_id,scope} and userid) as the next step
available to adopters who want to pin an issuer or audience.
Terminology: "3LO" is not an OAuth term, so the primary wording is now
"authorization code flow" throughout, with "(3LO)" kept only once on first use in
the README intro as a signpost for readers who know it by that name.
Refs #2069
|
Latest scan for commit: Security Scan Results |
…env permissions Addresses CodeQL py/clear-text-logging-sensitive-data on 04_create_github_target.py, which reported a callback URL as a leaked password. Root cause was one shared accessor: must_env() served both ordinary config and GITHUB_CLIENT_SECRET, so every value it returned inherited "may be a credential" and taint analysis then flagged the non-secret defaultReturnUrl diagnostic. Adds must_secret_env() for secrets and routes the one secret read through it, so the distinction is visible at each call site and the false positive disappears at the source rather than being suppressed. Also chmod 0600 on .env after every write. It is gitignored, but the default umask would otherwise leave client secrets and the Okta admin token group/world-readable. Refs #2069
CodeQL: 3 alerts reviewed and dismissedAll other checks pass (
|
| Not committable | .env and .env.* are gitignored; verified never committed |
| Not world-readable | chmod 0600 on every write (12154b97) |
| Not in process listings | secrets are never passed on argv; prompted via getpass when absent |
| Never printed | no code path logs a secret — only counts, e.g. "2 freshly minted" |
| Time-limited | the Okta admin token is setup-only and can be blanked once deployed |
The README's Security notes state plainly that this is a sandbox and not a template for production secret handling.
The alternative — writing each secret to AWS Secrets Manager and keeping only the ARN in .env — would remove the finding outright and is the right answer for a real deployment. I have not done it here because it is a design change I cannot validate end to end at the moment (both deployments are torn down, and the consent leg needs a human browser step). Happy to take it on if maintainers would prefer that over a dismissal.
Amazon Bedrock AgentCore Samples Pull Request
Issue number: #2069
Concise description of the PR
A deployed agent cannot complete an authorization code flow on its own: that flow needs a browser, a consent screen, and somewhere to receive the redirect. The AgentCore consent portal supplies all three as a hosted, AWS-managed page. Users connect their own GitHub account there, out of band, before or independently of any conversation with the agent — so the agent never handles a GitHub credential, and there is no consent UI or callback server to write.
What the sample provides:
--entra/--oktaflag. IdP shape lives indeploy/idps/<name>.json; tenant values stay in.env, so adding a provider is a new profile plus one bootstrap script.obo-training/covers token exchange.tools/listworks before anyone consents and onlytools/callneeds the user token.tools/callwith no stored token by returning a-32042URL-mode elicitation rather than an error; the agent turns that into a plain instruction naming the portal, and never echoes the raw authorize URL.User experience
Before: reaching a target that requires the authorization code flow from a deployed agent means building the browser consent leg yourself — a callback endpoint, session binding, and somewhere to store per-user tokens.
After: run
deploy/00…07, sign in atlocalhost:8000, and ask a question. A user who has not connected GitHub gets a portal link instead of an error. Once they connect, the same question returns their own data.The demo that lands the idea is
Who am I on GitHub?— two people asking it get two different answers from one agent, one gateway and one target, because the authorization is per user.Most of the documentation effort went into invariants that fail late and quietly, each written up with the symptom it produces:
redirect_uriat Connectlogin_unavailable, which names no causesubis pairwise per resource, so both sign-ins need one shared audienceCreateConsentPortalrejects the provider, because the org server issues opaque tokenssupportedVersionsmust include2025-11-25-32042to catchobject has no attribute 'create_consent_portal', which reads like a missing featureValidation
Deployed and run end to end in a real AWS account against real tenants, for both IdPs: portal sign-in, GitHub Connect, and a live GitHub MCP call returning the signed-in user’s own data. The optional interceptor was verified live (22 invocations, 2 rewrites — it fires only on
-32042). Teardown was then run for both variants and the account verified empty.The one hop not exercised is the Bedrock model call: the test account’s daily token quota is zero pending a Support increase. Everything the model depends on — runtime IAM, the inference profile, tool discovery — is verified. The README states this in a Validation status callout rather than implying full coverage.
Checklist
Notes for reviewers
Resource: "*". The AgentCore Identity actions do support resource-level permissions, so both roles are scoped totoken-vault/*andworkload-identity-directory/*in this account and region. The ids stay wildcarded because AgentCore owns those names and mints a workload identity per runtime/gateway. Both rendered policies validate with no IAM Access Analyzer findings. The README also records theInboundJwtClaim/*anduseridcondition keys as the next tightening step available to adopters.Secure, because the sample runs onhttp://localhost. Documented, along with the one-line change needed to serve it anywhere else.repo user workflow) are broad for a sample; the README points at the two places to trim them.ruff checkandruff format --checkare clean under the repo’spyproject.toml.Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.