Getting Started with Amazon Bedrock AgentCore Identity
Introduction
AI agents that access sensitive data need identity and access control. A customer-facing assistant must know who is calling to decide what information to reveal. An internal tool must distinguish between employee access levels. Without authentication, any user could access any data — including confidential business information.
Amazon Bedrock AgentCore Identity provides inbound authentication for agents hosted on AgentCore Runtime. It validates JWT tokens directly at the Runtime endpoint, rejects unauthorized requests before they reach your code, and makes user claims (name, email, groups) available to the agent for fine-grained authorization decisions.
This article is part of a series I am writing about different aspects of Amazon Bedrock AgentCore. Here, we focus on how to use AgentCore Identity to implement role-based access control (RBAC) for agent users. For a broader understanding of AgentCore and its components, check out the other articles in this series:
- Getting Started with Amazon Bedrock AgentCore Harness and Runtime
- Getting Started with Amazon Bedrock AgentCore Code Interpreter
- Getting Started with Amazon Bedrock AgentCore Gateway
- Getting Started with Observability for Amazon Bedrock AgentCore
- Getting Started with Amazon Bedrock AgentCore Web Search Tool
- Getting Started with Amazon Bedrock AgentCore Browser Tool
- Getting Started with Amazon Bedrock AgentCore Memory
- Getting Started with Amazon Bedrock AgentCore Knowledge Bases
Why AgentCore Runtime Instead of Harness?
In previous articles, we used the Harness (managed, declarative) to deploy our car dealership agent. The Harness is great for most use cases, but it does not give you access to the raw JWT token. You cannot write custom code that inspects user claims and conditionally registers tools.
For inbound authentication with RBAC, we need the Runtime: we bring our own container with custom code that reads the Authorization header, decodes the token, and decides which tools to expose based on the caller's Cognito group. The platform validates the token (signature, expiration, audience, groups) before it reaches our code — we only handle the business logic.
What We Are Building
We extend the used car dealership scenario from the series. The agent now has two access levels:
- Customers can search the public vehicle inventory (make, model, year, price, mileage).
- Employees can do everything customers can, plus access internal cost data (acquisition cost, repair cost, margin target).
The access control mechanism is deliberately blunt: a customer’s agent session is built without the cost tool at all. There is no code path that could leak internal data, no matter how the user phrases the question.
Architecture
Authorization happens at two layers:
- Platform layer — AgentCore Identity rejects tokens that do not carry
cognito:groups ∈ {customer, employee}. - Agent layer — the code registers tools conditionally based on the group. The restricted tool simply does not exist for customers.
How the Token Reaches the Agent
Understanding the request flow is key to making this architecture work:
- The user authenticates with Cognito and receives an ID token — a JWT containing
cognito:groups,name,email, andphone_number. - The frontend sends the request to the AgentCore Runtime endpoint with the header
Authorization: Bearer <id-token>. - AgentCore Identity intercepts the request and validates the token: checks the signature against Cognito’s public keys, verifies the
audclaim matches the allowed audience, and evaluates thecustom_claimrule (group membership). - If validation passes, the platform strips the Authorization header by default — the container never sees it unless you explicitly configure the Request Header Allowlist to forward it.
- With the allowlist configured, the
Authorizationheader reaches the agent code. The agent decodes the JWT (without re-verifying the signature, since the platform already did that) and readscognito:groupsto decide which tools to register.
This last point is critical: without the Request Header Allowlist, the agent receives the request but has no way to know who the caller is. Every user would be treated as a customer by default, regardless of their actual group membership. The allowlist is what bridges the platform-level authentication with the application-level authorization. For more details on how custom headers work in AgentCore Runtime, see Pass custom headers to Amazon Bedrock AgentCore Runtime.
The following flowchart summarizes this decision logic, from authentication through the two authorization checkpoints (JWT validation at the platform and group evaluation in the agent code):
What the ID Token Looks Like
The Cognito ID token is a standard JWT with three parts (header.payload.signature). The payload is what the agent reads after decoding. Here is a real example from our demo user Jane (employee):
{
"sub": "b4c844b8-e0e1-70da-7962-0f839370f537",
"cognito:groups": ["employee"],
"email_verified": true,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX",
"cognito:username": "jane",
"aud": "2bnnv2ng361p25ktnc1clc3ebo",
"token_use": "id",
"name": "Jane Doe",
"phone_number": "+12025550102",
"email": "jane.doe@example.com",
"exp": 1785989421,
"iat": 1785985821
}The fields that matter for our RBAC logic:
cognito:groups— An array listing the user's group memberships. This is the authorization signal. The agent checks if"employee"is in this array to decide whether to register the restricted tool. A customer's token would show["customer"]here.aud— The App Client ID. The AgentCore authorizer validates this againstallowed_audienceto ensure the token was issued for this application.name,email,phone_number— Profile attributes that the agent uses to personalize responses (and that would be needed for escalation notifications in a production scenario).iss— The issuer URL, which the authorizer resolves from the Discovery URL to fetch public keys for signature verification.
You can decode any ID token yourself by pasting it into https://jwt.io or running JSON.parse(atob(token.split('.')[1])) in the browser console.
Prerequisites
- AWS account with permissions to create Cognito, DynamoDB, ECR, IAM, and AgentCore resources
- AWS CLI configured
- Docker with buildx (for ARM64 image)
- Python 3 (for the local test proxy)
- AWS Region where AgentCore is available (us-east-1, us-west-2, ap-southeast-2, or eu-central-1)
- Model access enabled for Amazon Nova 2 Lite in the Bedrock console
Step 1: Clone the Repository
The companion repository contains the agent code, frontend, and helper scripts that we will use throughout this tutorial. Clone it now:
git clone https://github.com/biagolini/PythonAwsBedrockAgentCoreIdentity.git
cd PythonAwsBedrockAgentCoreIdentityWe will use the following folders in upcoming steps:
agent/— Container image source (Step 4)seed/— Scripts to create users and populate DynamoDB (Steps 2 and 3)frontend/— Browser-based chat UI for testing (Step 7)
Step 2: Create the Cognito User Pool
We need an identity provider that issues JWT tokens with group membership. Cognito does this natively via the cognito:groups claim in the ID token.
2.1 Create the User Pool
2.1.1. Navigate to Amazon Cognito > User pools and click Create user pool.
2.1.2. You will see the “Set up resources for your application” page. Fill it in as follows:
Define your application:
- Application type: Select Single-page application (SPA). This creates the App Client without a client secret, which is what we need for browser-based authentication.
- Name your application: Enter
agentcore-identity-demo.
Configure options:
- Options for sign-in identifiers: Check Username only (leave Email and Phone number unchecked).
- Self-registration: Uncheck Enable self-registration (we will create users manually via CLI).
- Required attributes for sign-up: Select
email,name, andphone_numberfrom the dropdown.
Add a return URL:
- Leave the Return URL field empty (we are not using the hosted UI — our frontend authenticates via the Cognito API directly).
2.1.3. Click Create user directory.
2.1.4. A page will open showing an overview with code examples for React, Angular, and JavaScript. Scroll to the bottom and click Go to overview.
After creation, note your User Pool ID (shown at the top of the pool details page, format: us-east-1_XXXXXXXXX).
2.2 Configure the App Client
The “Create user directory” step automatically created an App Client (without a client secret, since we chose SPA). Now we need to enable the USER_PASSWORD_AUTH flow, which is required for programmatic login from the frontend.
2.2.1. Click the hamburger menu (☰) in the top-left corner to expand the navigation panel. Then go to App integration > App clients.
2.2.2. Click on the app client that was created (named agentcore-identity-demo).
2.2.3. Click Edit in the Authentication flows section.
2.2.4. Enable ALLOW_USER_PASSWORD_AUTH. You will see other flows already enabled by default (ALLOW_USER_AUTH, ALLOW_USER_SRP_AUTH, ALLOW_REFRESH_TOKEN_AUTH) — keep them as they are. Click Save changes.
2.2.5. Copy the Client ID (shown at the top of the app client page). You can also confirm that Client secret is empty, which means no SECRET_HASH is needed for authentication — you will need the Client ID later for the .env file and the Runtime authorizer configuration.
2.3 Create Groups
2.3.1. In the left navigation panel, click Groups, then click Create group.
2.3.2. Create the two groups, one at a time. For each group, fill in the Group name and click Create group:
- First group:
employee - Second group:
customer
Leave Precedence and IAM role empty for both groups. Here’s what they do (not needed for this demo):
- Precedence — A numeric priority used when a user belongs to multiple groups. The group with the lowest number is considered the “preferred” group, and its IAM role (if any) is used for the
cognito:preferred_roleclaim. Since our agent reads all groups from the array, precedence does not affect our RBAC logic. See GroupType API reference. - IAM role — An IAM role that Cognito can assign to users in this group when they authenticate through a Cognito Identity Pool (federated identities). We are not using Identity Pools — our agent reads the
cognito:groupsclaim directly from the ID token and makes its own authorization decisions in code.
2.4 Create Demo Users
Create three users via the AWS CLI. Set your User Pool ID once at the top, then run the commands:
# Set your User Pool ID here (from Step 1.1.4)
export POOL_ID=us-east-1_XXXXXXXXX
# Customer user
aws cognito-idp admin-create-user \
--user-pool-id $POOL_ID \
--username john \
--user-attributes Name=name,Value="John Smith" Name=email,Value="john.smith@example.com" Name=email_verified,Value=true Name=phone_number,Value="+12025550101" Name=phone_number_verified,Value=true \
--message-action SUPPRESS
aws cognito-idp admin-set-user-password \
--user-pool-id $POOL_ID \
--username john \
--password "Customer1Pass!" \
--permanent
aws cognito-idp admin-add-user-to-group \
--user-pool-id $POOL_ID \
--username john \
--group-name customer
# Employee user
aws cognito-idp admin-create-user \
--user-pool-id $POOL_ID \
--username jane \
--user-attributes Name=name,Value="Jane Doe" Name=email,Value="jane.doe@example.com" Name=email_verified,Value=true Name=phone_number,Value="+12025550102" Name=phone_number_verified,Value=true \
--message-action SUPPRESS
aws cognito-idp admin-set-user-password \
--user-pool-id $POOL_ID \
--username jane \
--password "Employee1Pass!" \
--permanent
aws cognito-idp admin-add-user-to-group \
--user-pool-id $POOL_ID \
--username jane \
--group-name employee
# User with no group (to test platform-level rejection)
aws cognito-idp admin-create-user \
--user-pool-id $POOL_ID \
--username alex \
--user-attributes Name=name,Value="Alex Nobody" Name=email,Value="alex.nobody@example.com" Name=email_verified,Value=true Name=phone_number,Value="+12025550103" Name=phone_number_verified,Value=true \
--message-action SUPPRESS
aws cognito-idp admin-set-user-password \
--user-pool-id $POOL_ID \
--username alex \
--password "NoGroup1Pass!" \
--permanent2.5 Note Your Discovery URL
The AgentCore JWT authorizer needs the OIDC discovery URL to fetch Cognito’s public keys and validate tokens. You can find it in the User Pool’s Overview page, under the OpenID Connect configuration URL field.
The URL follows this pattern:
https://cognito-idp.<region>.amazonaws.com/<pool-id>/.well-known/openid-configurationYou can verify it works by pasting the URL in your browser — it should return a JSON document with issuer, jwks_uri, and other OIDC metadata.
Save this URL — you will need it in Step 5 when creating the AgentCore Runtime authorizer.
Step 3: Create DynamoDB Tables
We need two tables: one public (vehicles) and one restricted (vehicle costs).
3.1 Create the Vehicles Table
3.1.1. Navigate to DynamoDB > Tables and click Create table.
3.1.2. Table name: agentcore-identity-vehicles
3.1.3. Partition key: vehicle_id (String)
3.1.4. Use default settings (on-demand capacity). Click Create table.
3.1.5. After the table is created, click on it to open its details. Go to the Indexes tab and click Create index.
3.1.6. Configure the Global Secondary Index:
- Index name:
body-type-index - Partition key: Enter
body_typein the Attribute field, Data typeString - Sort key: Leave empty
- Index capacity: Keep defaults (follows base table — on-demand)
- Warm throughput: Keep default values
- Attribute projections: Select All (all table attributes are projected into the index)
Click Create index and wait for its status to become Active.
3.2 Create the Vehicle Costs Table
3.2.1. Create another table:
- Table name:
agentcore-identity-vehicle-costs - Partition key:
vehicle_id(String) - Default settings (on-demand)
This table does not need a Global Secondary Index — the agent only queries it by vehicle_id (the partition key).
3.3 Populate with Sample Data
From the cloned repository, configure and run the seed scripts:
cd seed
cp .env.example .env
# Edit .env: set your POOL_ID and confirm the table names
./seed_users.sh
./seed_data.shThis creates the 3 demo users (with group assignments) and inserts 8 vehicles with their corresponding internal cost records.
Step 4: Create the ECR Repository and Push the Agent Image
We need an ECR repository to store the agent container image. The process is the same as in Getting Started with Amazon Bedrock AgentCore Harness and Runtime, using the agentcore/ namespace to group all agent-related repositories.
4.1 Create the Repository
4.1.1. Navigate to Amazon ECR > Repositories and click Create repository.
Get Carlos Biagolini’s stories in your inbox
Join Medium for free to get updates from this writer.
4.1.2. Visibility: Private
4.1.3. Repository name: agentcore/identity-agent
4.1.4. Leave all other settings at their defaults and click Create repository.
4.2 Build and Push the Agent Image
From the cloned repository:
cd agent
export AWS_REGION=us-east-1
export AWS_ACCOUNT_ID=<your-account-id>
export REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
export REPO_URI="${REGISTRY}/agentcore/identity-agent"
# Authenticate Docker with ECR
aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $REGISTRY
# Build ARM64 image and push
docker buildx build --platform linux/arm64 --tag ${REPO_URI}:latest --push .Copy the full image URI (e.g., 123456789012.dkr.ecr.us-east-1.amazonaws.com/agentcore-identity-agent:latest) — you need it for the Runtime creation.
Step 5: Create the IAM Execution Role
The AgentCore Runtime needs a role to pull the image, write logs, invoke the model, and read DynamoDB.
5.1 Create the Role
5.1.1. Navigate to IAM > Roles and click Create role.
5.1.2. Trusted entity: Custom trust policy. Paste:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": { "Service": "bedrock-agentcore.amazonaws.com" },
"Condition": {
"StringEquals": { "aws:SourceAccount": "<your-account-id>" }
}
}]
}5.1.3. Name it agentcore-identity-runtime-role.
5.2 Attach Policies
After creation, add an inline policy with the following permissions (replace account ID and region):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECR",
"Effect": "Allow",
"Action": ["ecr:GetAuthorizationToken"],
"Resource": "*"
},
{
"Sid": "ECRImage",
"Effect": "Allow",
"Action": ["ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"],
"Resource": "arn:aws:ecr:us-east-1:<account-id>:repository/agentcore/identity-agent"
},
{
"Sid": "Logs",
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:DescribeLogStreams", "logs:DescribeLogGroups"],
"Resource": "arn:aws:logs:us-east-1:<account-id>:log-group:/aws/bedrock-agentcore/runtimes/*"
},
{
"Sid": "XRay",
"Effect": "Allow",
"Action": ["xray:PutTraceSegments", "xray:PutTelemetryRecords", "xray:GetSamplingRules", "xray:GetSamplingTargets"],
"Resource": "*"
},
{
"Sid": "Metrics",
"Effect": "Allow",
"Action": ["cloudwatch:PutMetricData"],
"Resource": "*",
"Condition": { "StringEquals": { "cloudwatch:namespace": "bedrock-agentcore" } }
},
{
"Sid": "BedrockNova",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": [
"arn:aws:bedrock:*::foundation-model/amazon.nova-*",
"arn:aws:bedrock:us-east-1:<account-id>:inference-profile/*amazon.nova-*"
]
},
{
"Sid": "DynamoDB",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:Query", "dynamodb:Scan"],
"Resource": [
"arn:aws:dynamodb:us-east-1:<account-id>:table/agentcore-identity-vehicles",
"arn:aws:dynamodb:us-east-1:<account-id>:table/agentcore-identity-vehicles/index/*",
"arn:aws:dynamodb:us-east-1:<account-id>:table/agentcore-identity-vehicle-costs"
]
},
{
"Sid": "WorkloadIdentity",
"Effect": "Allow",
"Action": ["bedrock-agentcore:GetWorkloadAccessToken", "bedrock-agentcore:GetWorkloadAccessTokenForJWT"],
"Resource": [
"arn:aws:bedrock-agentcore:us-east-1:<account-id>:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:us-east-1:<account-id>:workload-identity-directory/default/workload-identity/*"
]
}
]
}Note: The Bedrock permissions are restricted to
amazon.nova-*on purpose. Amazon-owned models are billed as native AWS usage, while third-party models (Anthropic, Meta) are billed through AWS Marketplace, which most promotional credits do not cover.
Step 6: Create the AgentCore Runtime
This is the central step — where AgentCore Identity is configured.
6.1 Create the Runtime
6.1.1. Navigate to Amazon Bedrock AgentCore > Runtime and click Create runtime.
6.1.2. Runtime details:
- Name:
agentcore_identity_demo(letters, numbers, and underscores only — no hyphens).
6.1.3. Agent source:
- Source type: Select ECR Container.
- Image URI: Paste your image URI from Step 4 (e.g.
<your-account-id>.dkr.ecr.us-east-1.amazonaws.com/agentcore/identity-agent:latest).
6.1.4. Permissions:
- IAM permissions: Select Use another role and choose the role created in Step 5 (e.g.
agentcore-identity-runtime-role).
6.1.5. Inbound Auth:
- Protocol: Select HTTP.
- Inbound Auth Type: Select Use JSON Web Tokens (JWT).
- JWT schema configuration: Select Use existing Identity provider configurations.
- Discovery URL: Paste your Cognito OIDC URL from Step 2.5.
6.1.6. JWT Authorization Configuration:
- Check Allowed audiences and enter your Cognito App Client ID (from Step 2.2.5).
- Uncheck Allowed clients (leave empty — ID tokens do not carry a
client_idclaim, so enabling this would reject every request). - Uncheck Allowed scopes.
- Check Custom claims and configure: Name =
cognito:groups, String type =String Array, Operator =Contains any, Value =customer,employee. - Uncheck Allowed gateways.
6.1.7. Additional configurations (VPC security):
- VPC security: Select None (public endpoint).
6.1.8. Advanced configurations:
- Security: Select Public.
- Environment variables: Click Add new variable three times and add:
VEHICLES_TABLE=agentcore-identity-vehicles,VEHICLE_COSTS_TABLE=agentcore-identity-vehicle-costs,BEDROCK_MODEL_ID=us.amazon.nova-2-lite-v1:0. - Lifecycle configurations: Keep defaults (Session idle timeout: 15 minutes, Runtime lifecycle: 8 hours).
6.1.9. Click Create runtime and wait for the status to reach Ready.
6.2 Configure the Request Header Allowlist
After the runtime is created, you need to forward the Authorization header to the container. As explained in the architecture section, without this configuration the agent code cannot read the caller's identity from the JWT — the platform strips the header after validation.
As of August 2026, during the development of this POC, we did not find this option in the AWS console UI. The AWS documentation on custom headers describes how to configure it via the AgentCore CLI (agentcore.json) or the AWS SDK (update_agent_runtime), but does not mention a console-based configuration. Since this may change in future console updates, check the documentation link above for the latest instructions.
To configure it via the AWS CLI, run the following command (replacing placeholders with your values from previous steps):
aws bedrock-agentcore-control update-agent-runtime \
--agent-runtime-id <your-runtime-id> \
--role-arn arn:aws:iam::<your-account-id>:role/agentcore-identity-runtime-role \
--agent-runtime-artifact '{"containerConfiguration":{"containerUri":"<your-image-uri>"}}' \
--network-configuration '{"networkMode":"PUBLIC"}' \
--authorizer-configuration '{"customJWTAuthorizer":{"discoveryUrl":"<your-discovery-url>","allowedAudience":["<your-client-id>"],"customClaims":[{"inboundTokenClaimName":"cognito:groups","inboundTokenClaimValueType":"STRING_ARRAY","authorizingClaimMatchValue":{"claimMatchValue":{"matchValueStringList":["customer","employee"]},"claimMatchOperator":"CONTAINS_ANY"}}]}}' \
--request-header-configuration '{"requestHeaderAllowlist":["Authorization"]}' \
--environment-variables '{"VEHICLES_TABLE":"agentcore-identity-vehicles","VEHICLE_COSTS_TABLE":"agentcore-identity-vehicle-costs","BEDROCK_MODEL_ID":"us.amazon.nova-2-lite-v1:0"}' \
--region us-east-1Important: The
update-agent-runtimecommand is a full PUT operation. You must include all fields (role, artifact, network, authorizer, environment variables) even if they have not changed. Omitting a field will reset it to empty.
Wait for the runtime status to return to Ready:
aws bedrock-agentcore-control get-agent-runtime \
--agent-runtime-id <your-runtime-id> \
--query "status" --output text \
--region us-east-16.3 Copy the Invoke URL
The invoke URL is not displayed directly in the console. You build it from the Runtime ARN (visible in the runtime details page under “Agent and tool details”) using this pattern:
https://bedrock-agentcore.<region>.amazonaws.com/runtimes/<url-encoded-arn>/invocations?qualifier=DEFAULTTo URL-encode the ARN, replace : with %3A and / with %2F. For example, if your ARN is:
arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/agentcore_identity_demo-XXXXXXXXThe invoke URL becomes:
https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-east-1%3A123456789012%3Aruntime%2Fagentcore_identity_demo-XXXXXXXX/invocations?qualifier=DEFAULTSave this URL — it goes in your .env file.
Step 7: Test with the Frontend
7.1 Configure the Frontend
From the cloned repository:
cd frontend
cp .env.example .envEdit .env with the values you collected:
INVOKE_URL=https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-east-1%3A123456789012%3Aruntime%2Fagentcore_identity_demo-XXXXXXXX/invocations?qualifier=DEFAULT
COGNITO_CLIENT_ID=your-client-id-from-step-1
AWS_REGION=us-east-17.2 Start the Proxy
python3 proxy.pyOpen http://localhost:8080 in your browser.
7.3 Test Each Scenario
Sign in as john (customer, password: Customer1Pass!)
Try these messages:
What SUVs do you have available under $30,000?→ Should list vehicles from public table.What was the acquisition cost of the Ford Explorer?→ Should politely decline (the tool doesn't exist).
Sign in as jane (employee, password: Employee1Pass!)
Try the same messages to see the difference in behavior:
What SUVs do you have available under $30,000?→ Should list vehicles (same as customer).What was the acquisition cost of the Ford Explorer?→ Should return $21,000 and the margin target (employee has access).
Sign in as alex (no group, password: NoGroup1Pass!)
- Any message → Should immediately show
Error 401: Authorization denied. The platform-levelcustom_claimrule rejected the token before the container was invoked.
Step 8: Clean Up
To avoid ongoing charges, delete the resources in this order:
- AgentCore Runtime — Amazon Bedrock AgentCore > Runtime > select and delete
- ECR Repository — Amazon ECR > Repositories > select and delete (including images)
- DynamoDB Tables — DynamoDB > Tables > delete both tables
- Cognito User Pool — Amazon Cognito > User Pools > select and delete
- IAM Role — IAM > Roles > search for
agentcore-identity-runtime-roleand delete
How the Agent Code Works
The agent code (agent/agent.py) implements the RBAC logic. Here's what happens on each request:
# 1. Extract the JWT from the Authorization header (already validated by platform)
claims = jwt.decode(token, options={"verify_signature": False})
# 2. Resolve the caller's role from Cognito groups
groups = claims.get("cognito:groups", [])
is_employee = "employee" in groups
# 3. Register tools conditionally
tools = [search_vehicles] # everyone gets this
if is_employee:
tools.append(get_vehicle_costs) # only employees get this
# 4. Build the agent with the appropriate tools
agent = Agent(model=model, system_prompt=prompt, tools=tools)The restricted tool (get_vehicle_costs) is not "denied when called" — it is absent from the customer's session entirely. There is no code path that could leak internal data regardless of prompt injection attempts.
Key Takeaways
- AgentCore Identity validates tokens at the platform level — signature, expiration, audience, and custom claims are all checked before your code runs. Invalid requests never reach the container.
custom_claimprovides defense in depth — even if your agent code had a bug, a user without a recognized group would still be blocked at the platform level.- The ID token carries everything — Cognito groups (
cognito:groups), display name, email, and phone number are all in the same token. No extra API calls needed. allowed_audience(notallowed_clients) — Cognito ID tokens carry the client ID in theaudclaim, notclient_id. Usingallowed_clientswould reject every ID token.request_header_allowlistis mandatory — Without addingAuthorizationto the allowlist, the platform strips the header after validation and the agent cannot read user claims.- Amazon Nova keeps costs predictable — First-party models are billed as native AWS usage (covered by promotional credits). Third-party models go through AWS Marketplace.
References
Stay Connected
If you found this article helpful and want to learn more about AWS, cloud architecture, AI, infrastructure as code, and cloud security, follow the author for future content and tutorials:
- LinkedIn: https://www.linkedin.com/in/biagolini
- Medium: https://medium.com/@biagolini
- GitHub: https://github.com/biagolini
- YouTube (PT-BR): https://www.youtube.com/@BiagoliniTech
- AWS Builder Center (PT-BR): https://builder.aws.com/community/@cbiagolini
Stay tuned for more practical walkthroughs and solutions for cloud infrastructure and DevOps challenges.
Happy building on AWS! 🚀☁️










