
Enforce layered, end-to-end access control for AI agents with Amazon Bedrock AgentCore, Amazon Cognito, and Cedar
Learn how to implement three independent, deterministic authorization controls for AI agents—identity propagation with Amazon Cognito, per-agent tool restrictions with a Gateway interceptor, and per-user tool policies with Cedar—all enforced at the Amazon Bedrock AgentCore Gateway, outside the model's reasoning.
As organizations move generative AI agents from prototype to production, a hard question surfaces: who is allowed to do what? An agent that can read device telemetry is useful; an agent that can silently change a device's Wi‑Fi security settings on behalf of the wrong user is a liability. Because an agent's behavior is driven by a large language model (LLM), you can't rely on prompt instructions alone to enforce access—a determined prompt can talk its way around guidance. Authorization has to be deterministic and enforced outside the model's reasoning.
In this post, you learn how to implement layered, end‑to‑end access control for an AI agent using Amazon Bedrock AgentCore , Amazon Cognito , and Cedar policies. You build three independent, complementary controls that together answer the "user → agent → tool" authorization question:
- Identity (OAuth 2.0/JWT) – Authenticate the user with Amazon Cognito and propagate that identity, end to end, to the gateway that fronts the tools.
- Agent‑level control (Gateway interceptor) – Restrict which tools an agent can call, independent of the user.
- User‑level control (AgentCore policy engine and Cedar) – Restrict which tools a specific user can call, independent of the agent.
You use a device management assistant as the running example: an agent that can list devices, read settings and Wi‑Fi networks, list users, query activity, and update Wi‑Fi SSID and security settings through tools exposed by an AgentCore Gateway.
Use case: a device management assistant
Consider a conversational assistant that helps teams operate a fleet of smart‑home and IoT devices. Through natural language, it can list devices, read device and Wi‑Fi settings, list users, query activity, and update Wi‑Fi SSID and security settings—each backed by a tool on the AgentCore Gateway.
Not everyone should have the same power. Reading telemetry is low‑risk; changing a network's security type is high‑risk. This post uses two personas to make the controls concrete:
- A support analyst investigates issues but must not change anything. They're routed to a read‑only agent (Control 2: agent → tools).
- A field technician fixes connectivity and can update a Wi‑Fi SSID, but must not weaken a network's security settings. They use the full agent, with a user‑level rule that forbids the security‑update tool (Control 3: user → tools).
Both personas sign in through Amazon Cognito, and their identity flows all the way to the gateway (Control 1), where the rules are enforced. The rest of this post shows how to build each control.
The challenge
A production agent typically sits behind a chain of hops: a user in a browser calls a frontend, the frontend invokes the agent in AgentCore Runtime, the agent calls tools through an AgentCore Gateway, and the gateway invokes AWS Lambda functions that read and write data. Each hop is a place where authorization can be lost or bypassed:
- If the user's identity isn't carried through to the gateway, you can only enforce coarse, machine‑to‑machine rules—you can't say "user A can't do X."
- If you enforce tool access only in the agent's prompt, the LLM can be coaxed into calling a restricted tool anyway.
- If different agents share one gateway, you need a way to give each agent a different slice of the tool surface.
The solution is to enforce authorization at the gateway—the deterministic choke point in front of the tools—and to do it at two granularities (per agent and per user), anchored by a verifiable user identity.
Solution overview
The following diagram shows the request flow and where each control is applied.

The three controls are independent and layered, providing defense in depth:
| Layer | Mechanism | Question it answers | Where enforced |
|---|---|---|---|
| Identity | Amazon Cognito OAuth 2.0 and JWT | Who is calling? | Runtime and Gateway inbound authorizers |
| Agent → tools | Gateway request interceptor (Lambda) and allowlist | Which tools can this agent use? | Gateway, before the tool runs |
| User → tools | AgentCore policy engine and Cedar | Which tools can this user use? | Gateway, before the tool runs |
Because all three controls are enforced at the gateway rather than in the prompt, they hold even under adversarial prompting.
Prerequisites
To follow along, you need the following:
- An AWS account with access to Amazon Bedrock AgentCore in a supported AWS Region
- An Amazon Cognito user pool and an app client configured for the authorization code grant, with an OpenID Connect (OIDC) discovery URL
- An AgentCore Runtime hosting your agent (this example uses the Strands Agents SDK) and an AgentCore Gateway that exposes your tools over the Model Context Protocol (MCP)
- The latest AgentCore SDK (
bedrock-agentcore) and a recent version of the AWS SDK for Python (boto3) - Familiarity with OAuth 2.0 (JWT access tokens and claims) and basic AWS Identity and Access Management (IAM)
Control 1: Authenticate the user and propagate identity end to end
Use a single Amazon Cognito user pool and app client across the frontend, the runtime, and the gateway. A single trust anchor means one OIDC discovery URL and one set of allowed clients—and, crucially, the same user access token validates at every hop, so the user's identity reaches the gateway intact.
Configure the AgentCore Runtime and Gateway with a JWT (OIDC) inbound authorizer. For Amazon Cognito access tokens, validate the
client_id claim with allowedClients (don't set allowedAudience, because Cognito access tokens don't carry an aud claim):1
2
3
4
5
6
auth_config = {
"customJWTAuthorizer": {
"allowedClients": [COGNITO_APP_CLIENT_ID],
"discoveryUrl": f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/.well-known/openid-configuration",
}
}
An AgentCore Runtime uses either IAM SigV4 or JWT bearer inbound authentication—not both. For user‑facing OAuth applications, choose JWT bearer so the user's identity flows through.
Next, allow the runtime to read the inbound
Authorization header, and have the agent forward the same user token when it calls the gateway. This is what makes per‑user authorization possible downstream:1
2
3
4
5
6
7
# At runtime creation: let the container read the user's JWT
requestHeaderConfiguration = {"requestHeaderAllowlist": ["Authorization"]}
# In the agent, reuse the inbound user token for the outbound MCP/Gateway call
auth_header = context.request_headers.get("Authorization", "")
user_jwt = auth_header[7:] if auth_header.lower().startswith("bearer ") else auth_header
headers = {"Authorization": f"Bearer {user_jwt}"}
The frontend invokes the runtime over HTTPS with the user's access token:
1
2
3
4
5
6
7
8
url = (
f"https://bedrock-agentcore.{REGION}.amazonaws.com"
f"/runtimes/{quote(agent_arn, safe='')}/invocations?qualifier=DEFAULT"
)
headers = {
"Authorization": f"Bearer {user_access_token}",
"Accept": "text/event-stream",
}
Note: The runtime'sserverProtocolmust beHTTPfor an agent that is invoked over HTTP and consumes MCP tools from the gateway. Setting it toMCPtells the platform that the container itself is an MCP server, and the agent's HTTP entry point never receives requests.
Route users to agents (optional)
You can run more than one agent against the same gateway and choose which agent a user gets. Store a
username → agent ARN map in AWS Systems Manager Parameter Store , a capability of AWS Systems Manager, and select the agent in the frontend based on the JWT's username claim:1
2
3
4
# SSM parameter: /device-mgmt/frontend/user-agent-mapping
# {"analyst": "<read-only-agent-arn>", "technician": "<full-agent-arn>"}
claims = decode_jwt_claims(access_token) # runtime already validated the token
agent_arn = user_agent_map.get(claims.get("username"), DEFAULT_AGENT_ARN)
Control 2: Restrict tools per agent with a Gateway interceptor
An AgentCore Gateway interceptor is a Lambda function that the gateway invokes before it routes a tool call. Each agent identifies itself with a custom
x-agent-id header; the interceptor looks up the agent in an allowlist (stored in Parameter Store) and denies any tool that isn't on the list:1
2
3
4
5
6
7
8
9
10
11
12
def lambda_handler(event, context):
req = event["mcp"]["gatewayRequest"]["body"]
headers = event["mcp"]["gatewayRequest"]["headers"]
agent_id = headers.get("x-agent-id", "unknown")
if req.get("method") == "tools/call":
allowed = get_allowlist().get(agent_id) # {agent_id: [tool, ...]}
tool = req["params"]["name"]
if allowed is not None and tool not in allowed:
return deny(req, f"Agent '{agent_id}' is not authorized to call {tool}")
return passthrough(req)
Attach the interceptor to the gateway and allow the custom header to reach it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
client.update_gateway(
gatewayIdentifier=GATEWAY_ID,
# ... existing name/role/authorizer ...
interceptorConfigurations=[{
"interceptor": {"lambda": {"arn": INTERCEPTOR_LAMBDA_ARN}},
"interceptionPoints": ["REQUEST"],
"inputConfiguration": {"passRequestHeaders": True},
}],
)
client.update_gateway_target(
gatewayIdentifier=GATEWAY_ID,
targetId=TARGET_ID,
name=TARGET_NAME,
targetConfiguration=target_config,
metadataConfiguration={"allowedRequestHeaders": ["x-agent-id"]},
)
With a read‑only agent registered as
device-readonly-agent (the support analyst's agent) and an allowlist that grants it only the read tools, any attempt to call update_wifi_ssid or update_wifi_security is denied at the gateway—regardless of what the model decides to do.Control 3: Restrict tools per user with the policy engine and Cedar
For user‑level rules, attach an AgentCore policy engine to the gateway and author Cedar policies. Cedar is deny by default, so you begin with a permit for all authenticated users, then add targeted
forbid statements. Because the user's JWT reaches the gateway (Control 1), Cedar can evaluate the username claim as a principal tag:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Permit all authenticated users (Cedar is deny by default)
permit(
principal is AgentCore::OAuthUser,
action,
resource == AgentCore::Gateway::"<gateway-arn>"
);
// Forbid the field technician from changing Wi-Fi security
forbid(
principal is AgentCore::OAuthUser,
action == AgentCore::Action::"DeviceLambdaTarget___update_wifi_security",
resource == AgentCore::Gateway::"<gateway-arn>"
) when {
principal.hasTag("username") && principal.getTag("username") == "technician"
};
Attach the engine in
ENFORCE mode, preserving the interceptor configuration in the same update call:1
2
3
4
5
6
client.update_gateway(
gatewayIdentifier=GATEWAY_ID,
# ... name/role/authorizer ...
interceptorConfigurations=existing_interceptor_configs, # keep Control 2
policyEngineConfiguration={"arn": POLICY_ENGINE_ARN, "mode": "ENFORCE"},
)
When an enforced user is forbidden a tool, the policy engine removes it from that user's available toolset—the agent can't even see, let alone call, the restricted tool.
Test the controls
You can validate each layer independently:
- Identity – A signed‑in user's request reaches the gateway with the real
usernameclaim (confirmed in the interceptor logs asuser=<username>), not an anonymous machine token. - Agent → tools – The support analyst's read‑only agent successfully calls
list_devices, but a forcedupdate_wifi_ssidreturnsAccess Denied: Agent 'device-readonly-agent' is not authorized to call .... - User → tools – The field technician can still call
update_wifi_ssidand the read tools, butupdate_wifi_securityis removed from their toolset and can't be invoked.
Security best practices
- Validate tokens server side at every hop – Check signature, issuer, expiry, and the appropriate client and scope claims.
- Prefer deterministic, gateway‑side enforcement over prompt instructions for anything security‑relevant.
- Keep secrets out of code and
.envfiles – Store configuration in AWS Systems Manager Parameter Store (useSecureStringfor secrets, or AWS Secrets Manager when you need rotation) and grant least‑privilegessm:GetParameter*andkms:Decryptpermissions. - Apply least privilege to IAM roles – Scope the runtime execution role to only the services it calls, and make sure it can write its own Amazon CloudWatch logs (the runtime log group is
/aws/bedrock-agentcore/runtimes/<runtime-id>-DEFAULT). - Fail closed – If a user token is absent or invalid, deny the request rather than falling back to a privileged path.
- Pin dependencies and container images – Use exact versions and immutable image tags (never
:latest) for reproducible, auditable deployments. - Log and alert on authorization decisions, and never log tokens or secrets.
Clean up
To avoid ongoing charges, delete the resources you created:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Detach Cedar + interceptor, then remove policies/engine and the read-only runtime
aws bedrock-agentcore-control delete-policy \
--policy-engine-id <engine-id> \
--policy-id <policy-id>
aws bedrock-agentcore-control delete-policy-engine \
--policy-engine-id <engine-id>
aws bedrock-agentcore-control delete-agent-runtime \
--agent-runtime-id <readonly-runtime-id>
# Remove SSM parameters, the interceptor Lambda, the gateway, target, and
# Cognito resources you created
Conclusion
By combining Amazon Cognito OAuth identity, an AgentCore Gateway interceptor, and an AgentCore policy engine with Cedar, you enforce authorization for AI agents at three independent granularities—who is calling, which agent is calling, and which tools that user can use—deterministically, at the gateway, outside the model's reasoning. This layered model lets you safely expand what your agents can do while keeping a tight, auditable boundary on what each user and each agent is actually allowed to do.
To get started, explore the Amazon Bedrock AgentCore documentation and the amazon-bedrock-agentcore-samples repository on GitHub.
About the author
Godwin Sahayaraj Vincent is a Senior Solutions Architect at AWS, where he helps telco customers design and operate secure generative AI applications. He focuses on agentic AI, IoT, and cloud‑native architectures.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article