AWS has published Part 2 of its series on governing AI agent access with Okta Cross App Access and Amazon Bedrock AgentCore. Part 1 proved the mechanism with one agent and two MCP servers. Part 2 scales it: AgentCore Gateway becomes the single front door, Cedar policies decide which agent can call which tool, and a second agent on Amazon EKS joins under its own Okta identity. The post also publishes measurements, which makes it more useful than most reference architectures.
TL;DR: what changes
| Part 1 | Why it stops scaling | Part 2 |
|---|---|---|
| Agent holds each MCP server's runtime ARN and calls it directly | N agents by M servers becomes a wiring matrix | Agents reach one Gateway endpoint that holds the target registry |
| Agent's role holds token-exchange permission and reads the provider secret | Every agent gets a credential path to every resource | Gateway redeems the grant; agent role holds no exchange or secret permissions |
| Each MCP server repeats the same groups check | The rule drifts and cannot be audited in one place | One default-deny Cedar policy set evaluated at the Gateway |
Agents that act on your behalf need rules. See how explainx helps secure your agents →
How the flow works
The post walks through a sequence. A user signs in, and a backend for frontend exchanges the ID token for an Identity Assertion JWT Authorization Grant (ID-JAG) issued to the task agent. The agent forwards the ID-JAG to its Gateway as the bearer token of its MCP session. The Gateway's inbound authorizer accepts only that agent's ID-JAGs.
Policy in AgentCore then evaluates Cedar policies for each tool listing and call before the Gateway invokes a target. For calls Cedar permits, the Gateway has AgentCore Identity redeem the ID-JAG at Okta's custom authorization server. A denied call never redeems the grant. The MCP server still checks the user's groups and scope, as in Part 1, and records each decision as an authz.check span.
Cedar: bind tools to agents and scopes
AWS recommends attaching a policy engine in ENFORCE mode and writing permit-only policies. The post shows a policy that permits create and update task actions only when the principal's client_id matches the agent and the scope claim contains xaa:write as a whole entry, so a scope like xaa:write-admin does not match.
Several details are worth noting:
- Cedar exposes JWT claims as tags. A policy reading a missing tag errors and is skipped. A skipped permit grants nothing, so default deny holds, but a skipped forbid blocks nothing, so guard each tag with hasTag.
- Check that every policy is ACTIVE before attaching the engine. If policies failed to create, the Gateway denies every call.
- Give each target a static tool schema so the tool names Cedar references are fixed. In the authors' tests, policies naming tools could not attach to dynamically listed targets.
- The Gateway pages tools/list by target, so clients must follow the pagination cursor or tools from later targets disappear.
- Because a credential provider authenticates as one Okta agent, give each agent its own Gateway and provider.
A second agent on EKS
The reporting agent runs the same code on Amazon EKS with its own Okta registration, restricted to xaa:read, with its own Gateway. EKS Pod Identity gives the pod an IAM role that can invoke models and write logs and traces, nothing more. Outside AgentCore runtime no authorizer fronts the pod, so the agent validates the ID-JAG itself before any model call: signature, issuer, audience, token type, expiry and its own client_id.
Two gotchas from the authors: the EKS Auto Mode general-purpose node pool runs only amd64, so build multi-architecture images, and the AgentCore Python SDK binds to localhost unless DOCKER_CONTAINER=true is set.
Agent observability trace connecting each allow and deny to the control that decided it in the Okta Cross App Access setup
Observability: one query joins Okta and AWS
AgentCore Runtime, Gateway, Identity and Policy emit OpenTelemetry spans for each hop. Your own services add theirs with the AWS Distro for OpenTelemetry. Okta's System Log is streamed to CloudWatch through Amazon EventBridge; in the authors' tests events arrived about one second after Okta published them. A CloudWatch Omni query then joins each ID-JAG grant to the decisions it led to.
In the example, Okta granted the task agent xaa:read and xaa:write for Alice, and the MCP server denied her create_task 16.7 seconds later while allowing list_tasks. One request appears as a single trace from the agent's tool call through the Gateway checks to the MCP server's deny.
What the validation showed
AWS ran checks with three users through two Gateways:
| Caller and path | Tools listed | create_task |
|---|---|---|
| Alice, agent A | 6 | Denied by the MCP server: requires xaa-writers |
| Bob, agent A | 6 | Created |
| Carol, agent A | 6 | Denied: no groups claim |
| Bob, agent B (reporting) | 4, Cedar hid the write tools | Refused by the Gateway: denied by default |
Requests with the wrong agent's ID-JAG, an Okta access token instead of an ID-JAG, or no token stopped at the front door with HTTP 403 or 401. Agent A's ID-JAG sent to agent B on EKS was refused in 98 milliseconds, before any model call. The takeaway AWS draws: the same user got different authority through different agents, and Okta decided it.
What it costs in latency
From 24 hours of Gateway A spans, medians and 90th percentiles included Gateway inbound auth at 34 ms (p90 69), Cedar per-call authorization at 47 ms (p90 56), and GetResourceOauth2Token at 70 ms (p90 439). The identity and policy spans for one tool call sum to about 0.2 seconds at their medians, against a 5,565 ms median for the MCP server target itself. These are the authors' numbers on their setup, not a general benchmark.
Cross App Access or per-user consent
AWS compares two lanes. Where the target's authorization server accepts an ID-JAG, an Okta administrator decides which agent acts for which users, with no consent prompt. Where it does not, as with GitHub, each user's own OAuth consent applies. Revocation differs: with XAA, Okta stops the next ID-JAG and issued tokens expire within about 5 minutes. In the consent lane, a stored grant outlived a user's suspension until disconnected, and an inbound token valid for an hour kept working after suspension. AWS's advice is to choose per target application, not once for the enterprise.
Why two layers of authorization remain
A detail that is easy to miss: Cedar does not replace the MCP server's own check. Per the post, Cedar binds tools to the agent and the scope Okta delegated, while the MCP server still decides each user's authority from the groups claim. That is why Alice could list tasks but not create them even though Cedar allowed the tool for agent A. Two questions get answered in two places. Can this agent call this tool at all? That is the Gateway, centrally and in one policy set. Can this user do this action inside the tool? That is the server, close to the data.
The split also explains the agent B result. Cedar hid the write tools from the reporting agent, so Bob saw four tools instead of six, and the agent told him it had no tool to create tasks. The refusal came from policy at the front door, before any server code ran. For a security team this is the practical gain: the list of what an agent can possibly do is readable in one policy set rather than spread over many repositories.
A checklist before you copy the pattern
- Register each agent as its own identity in the identity provider, with the narrowest scopes it needs.
- Give each agent its own Gateway and credential provider, since a provider authenticates as one Okta agent.
- Use static tool schemas so policies can name tools, and test that every policy is ACTIVE before enforcing.
- Remove token-exchange and secret permissions from the agent's execution role.
- Turn on trace delivery and stream the identity provider's log, so each allow and deny can be tied to a user, an agent and a control.
- Decide per target application whether it supports the grant or needs per-user consent, and document how revocation behaves for each.
- Treat the AWS code as a sample and review it with your security team before production use.
What it means for teams running agents
The pattern is general. The agent is an identity in your identity provider, the target is an OAuth 2.0 authorization server, and the Gateway is where authorization is decided. Nothing is specific to the media example the post uses. Our own coverage of related designs includes AWS trust in depth for agent autonomy, the AWS Agent Registry, Anthropic's enterprise-managed auth for MCP with Okta and agents that pay without private keys on AgentCore.
Caveats: the code is sample code for non-production use, setup takes about 90 minutes beyond Part 1, and the EKS control plane costs $0.10 per hour plus nodes. For a control that checks actions at runtime, AgentBeam, the agent security platform from the explainx.ai team, stops AI agents before they take dangerous actions. To see another agent hardening guide, read the Claude Code remote sessions on AgentCore sample.
Related reading
- AWS trust in depth for agent autonomy
- AWS Agent Registry and runtime tool discovery
- Anthropic Claude enterprise managed auth for MCP with Okta
- AgentCore payments with Anchor Browser and x402
- Claude Code remote sessions on Bedrock AgentCore
- Cedar policy language
Details are accurate as of November 6, 2026 and reflect the AWS post published that day.
