March 28, 2026 · Identity · 14 min read

How AgentCore Identity Actually Works

This is Part 2 of a three-part series on agent identity. Part 1 covered the problem space: three identities, three tokens, and the confused deputy problem. This post covers how AgentCore Identity actually works under the hood. Part 3 covers patterns, security, and lessons from the field.

What AgentCore Identity Actually Is

AgentCore Identity is not a user identity provider. It doesn't authenticate humans. It doesn't issue user JWTs. It doesn't manage user pools. That's what Cognito, Okta, Auth0, or any OIDC provider does.

AgentCore Identity is the agent identity and credential management layer. It sits between your user identity provider and the third-party services your agents need to access.

Here's what it includes:

Agent Identity Directory. A registry of all your agent identities (workload identities). One per account, cross-service visibility.

Workload Access Token Service. Generates WATs. Runtime calls this automatically. Self-hosted agents call it via the SDK. Three APIs depending on your situation:

APIWhen to Use
GetWorkloadAccessTokenAgent as itself, no user context (autonomous/M2M)
GetWorkloadAccessTokenForJWTCaller has a JWT, validates signature, binds iss+sub
GetWorkloadAccessTokenForUserIdNo JWT available (e.g., ALB OIDC already stripped it), pass user ID string

Token Vault. Secure storage for OAuth 2.0 access/refresh tokens, API keys, and OAuth client secrets. Encrypted with AWS KMS (service-managed or customer-managed key). Bound to agent + user key pair. Auto token refresh, no re-consent until refresh token expires.

Credential Providers. Three types:

JWT Authorizer. The inbound authentication layer that validates user JWTs from any OIDC-compatible identity provider.

How it relates to Cognito:

Cognito (or Okta, or Auth0)
  = "Who is Alice?" 
  = User authentication, issues JWT, manages user pools

AgentCore Identity
  = "Alice wants Agent X to access her Google Drive"
  = Agent identity, credential vault, OAuth delegation, token binding

Three Separate Pieces of Software

With that context, it helps to understand that there are three separate pieces of software involved in a typical deployment, running on different infrastructure, with different jobs.

Your Web App (The Front Door). This is the application the user interacts with in their browser. It handles user login (Okta, Cognito, whatever IdP you use), the chat UI, sending requests to AgentCore Runtime with the user's JWT, and the OAuth callback endpoint for session binding. The web app is the only piece that has a direct relationship with the user's browser. AgentCore Runtime never talks to the browser directly.

AgentCore Runtime (The Platform). AWS's managed hosting platform. You deploy your agent code to it, and it handles all the identity plumbing before your code ever executes. It's like a building's front desk. The visitor (human JWT) shows their ID to the front desk (Runtime). The front desk verifies it, creates a visitor badge that says "Alice, escorted by Calendar Agent" (WAT), and hands that badge to the employee (your agent code).

Your Agent Code (The Application). The code you write. LLM orchestration, tool calls, business logic. Its job in the identity flow is simple: receive the WAT from Runtime, use it to call the Token Vault, use the retrieved credentials to call third-party APIs. Never worry about JWT validation or token generation.

def handle_agent_request(event):
    workload_access_token = event["headers"]["WorkloadAccessToken"]
    
    identity_client = IdentityClient("us-east-1")
    drive_token = identity_client.get_resource_oauth2_token(
        workload_access_token=workload_access_token,
        provider_name="google-drive"
    )
    
    files = google_drive_client.list_files(access_token=drive_token)
    return files

If you're self-hosting (not on Runtime), you do the identity plumbing yourself: validate the JWT, call GetWorkloadAccessTokenForJWT to get a WAT, then proceed as normal.

RuntimeGatewaySelf-hosted
JWT validationAutomaticAutomaticYou do it
Workload identity creationAutomaticAutomaticManual
WAT generationAutomaticAutomaticYou call the SDK
Your code receivesWATWATRaw user JWT (you convert it)

Inbound Authorization: Two Modes

How does AgentCore Runtime know who is calling it? There are two completely different ways, and you can only pick one per Runtime or Gateway deployment.

Mode 1: IAM SigV4 (Default). Works like any standard AWS API call. The caller signs the HTTP request with AWS credentials. Use this when the caller is another AWS service, not a human's browser. Lambda-to-agent calls, Step Functions orchestrating agents, one microservice calling another.

If there's no human JWT, how does the agent know which user it's acting for? The caller passes the user ID explicitly via the X-Amzn-Bedrock-AgentCore-Runtime-User-Id header. This requires the InvokeAgentRuntimeForUser IAM permission.

Mode 2: Custom JWT Bearer Token. Use this when end users are involved. The caller sends a JWT from an external identity provider in the Authorization header. Runtime validates the JWT and extracts the user identity.

Why only one mode per Runtime? SigV4 signs the entire HTTP request. JWT uses a Bearer token in the Authorization header. The header can only carry one scheme. Different trust models: SigV4 trusts IAM principals, JWT trusts an external OIDC issuer. Mixing creates ambiguous authorization.

The practical tip: create different runtime versions for different auth modes. One Runtime with SigV4 for internal service-to-agent calls, another with JWT for customer-facing apps. Same agent code, different front doors.

When JWT inbound auth is configured, here's what happens:

  1. Fetch OIDC config from the discovery URL (JWKS public keys)
  2. Validate JWT signature using the IdP's public keys
  3. Check expiry
  4. Validate claims (aud, client_id, scope, custom claims)
  5. Extract user identity from iss + sub
  6. Generate WAT via GetWorkloadAccessTokenForJWT
  7. Deliver WAT to your agent code via the WorkloadAccessToken header

Runtime caches the JWKS from the IdP. Keys refresh periodically and on-demand when an incoming JWT references an unknown key ID.

Outbound OAuth: How Agents Access Third-Party Services

Now the interesting part: how the agent actually gets access to things like Google Calendar, GitHub, Salesforce.

Pattern 1: Machine-to-Machine (Client Credential Flow). The simpler case. The agent needs to access a resource that isn't user-specific. Maybe a company-wide analytics API, or a shared Jira board. No user consent needed.

  1. Agent presents WAT + provider name to Identity
  2. Identity uses stored client credentials to call the authorization server
  3. Access token returned and cached in Token Vault
@requires_access_token(
    provider_name="my-provider",
    scopes=[],
    auth_flow="M2M",
)
async def call_service(*, access_token: str):
    # Use token to call resource server

Pattern 2: User-Delegated Access (Authorization Code Flow). The agent needs to access user-specific data. Your Google Calendar. Your GitHub repos.

  1. Agent calls GetResourceOauth2Token with WAT, provider, scopes, and Session Binding URL
  2. Identity returns authorization URL + session URI
  3. User navigates to auth URL, grants consent at the IdP (e.g., Google)
  4. IdP sends auth code to AgentCore Identity callback URL
  5. Identity redirects user to your Session Binding URL with session URI
  6. Your app verifies user session, calls CompleteResourceTokenAuth
  7. Identity exchanges code for token, stores in Token Vault

After that one-time consent, the user never sees the consent screen again. If the access token expires, AgentCore automatically uses the stored refresh token. No re-consent until the refresh token itself expires.

Session Binding: A Critical Security Detail

When AgentCore generates that authorization URL, what happens if the wrong person clicks it? Three attack vectors without session binding:

Session binding prevents all of these. Here's how it works:

  1. Agent calls GetResourceOauth2Token (includes Session Binding URL)
  2. Identity returns auth URL + session URI
  3. User navigates to auth URL, grants consent at IdP
  4. IdP sends auth code to AgentCore callback URL (auto-generated, you don't manage this)
  5. Identity redirects user to your Session Binding URL + session URI
  6. Your endpoint verifies user's active browser session (via cookies from their login)
  7. Your endpoint calls CompleteResourceTokenAuth (session URI + userId)
  8. Identity exchanges code for token, stores in Token Vault

The critical security property: the userId at the agent workload must match the userId at the session binding service, both cryptographically verified. The authorization URL and session ID are only valid for 10 minutes.

Two URLs to keep straight:

Scoping Access: Which Agents Can Access What

AgentCore Runtime is a single managed service. You deploy multiple agents to it. Each agent deployment gets its own workload identity, its own IAM role, and its own compute isolation.

There's one token vault per account per region. Alice's Google Drive token stored in us-east-1 isn't available in eu-west-1.

AgentCore uses standard IAM identity-based policies to control access. The IAM role controls whether you can call the vault API at all. The WAT then controls which specific credentials you get back. Two layers of access control working together.

What's Next

Now you know how the pieces fit together. Part 3 covers the three deployment scenarios (autonomous, user-initiated, on-behalf-of), security properties, and practical lessons from the field.

Read Part 3: Patterns, Security, and Lessons from the Field

← Back to all posts