---
title: "AgentCore Identity — workload identities, the token vault, and on-behalf-of without writing the auth code"
date: 2026-05-29
service: "Amazon Bedrock AgentCore"
component: "Identity"
tags: [agentcore, identity, oauth2, workload-identity, token-vault, on-behalf-of, credential-provider, jwt, 2lo, 3lo, api-key, rfc8693, rfc7523]
source: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html
verified_on: 2026-05-29
url: https://vanemmerik.ai/aws-ai/2026-05-29.html
---

# AWS Bedrock & AgentCore · Tip of the Day · 2026-05-29

## AgentCore Identity — workload identities, the token vault, and on-behalf-of without writing the auth code

Yesterday we covered AgentCore Memory — what an agent remembers
*about* you. Today we cover who the agent *is* and how it proves
that to anything else. AgentCore Identity is the identity and
credential-management plane for AI agents: a centralized directory
of **workload identities**, an encrypted **token vault**, and a set
of **outbound credential providers** that turn the messy world of
OAuth2 into one decorator call.

    agentcore add credential --type oauth \
      --name github-provider \
      --discovery-url https://github.com/.well-known/openid-configuration \
      --client-id $GH_CLIENT_ID \
      --client-secret $GH_CLIENT_SECRET \
      --scopes repo,user

≈ 9 min read · Bedrock AgentCore · Identity

---

## 01 · Two directions, one identity service

From the docs: *"Amazon Bedrock AgentCore Identity is an identity
and credential management service designed specifically for AI
agents and automated workloads. It provides secure authentication,
authorization, and credential management capabilities that enable
agents and tools to access AWS resources and third-party services
on behalf of users while helping to maintain strict security
controls and audit trails."*

Identity sits at the seam between two flows:

- **Inbound auth** — who is calling the agent? Validated by a JWT
  authorizer at the AgentCore Runtime or Gateway boundary. The
  inbound token's `iss` and `sub` claims identify the end user.
- **Outbound auth** — when the agent calls AWS or a third-party
  SaaS, what credentials does it use? Vended on demand by an
  **outbound credential provider**, scoped to the agent's
  workload identity.

> **The shift.** You stop embedding refresh tokens in agent code
> and stop writing the same 200 lines of OAuth dance per
> integration. AgentCore Identity stores the secrets, brokers the
> flows, and hands your code a fresh access token via a Python
> decorator. The agent never sees the client secret, never holds
> the refresh token, and can't extract its own workload token to
> resell.

---

## 02 · Workload identities and the agent directory

Agent identities are implemented as a specialized **workload
identity** — a logical application identity that's independent of
where it runs (Runtime, self-hosted, hybrid). They live in the
**agent identity directory**, which the docs liken to a Cognito
User Pool: a single unit of governance for the workloads in your
account and Region.

Every workload identity gets an ARN with a hierarchical path you
can target with IAM:

    arn:aws:bedrock-agentcore:us-east-1:111122223333:workload-identity/directory/default/workload-identity/my-demo-agent

The vocabulary you actually need:

| Term | What it is |
| --- | --- |
| **Agent** | An AI-powered application or automated workload that performs tasks on behalf of users with pre-authorized consent. |
| **Agent identity** | The unique identifier and metadata for an agent — a workload identity with agent-specific attributes. |
| **Workload identity** | The underlying implementation. Agent identities are a specialized workload identity. |
| **Token vault** | Encrypted store for OAuth2 tokens, client credentials, API keys, certificates, SAML assertions — keyed to a workload identity. |
| **Resource credential provider** | The component that talks to a specific external IdP (Google, GitHub, custom OAuth2, API-key-only services) and brokers tokens. |
| **Workload access token** | An AWS-signed opaque token that carries both the workload's identity and the end-user identity, used to authorize first-party AgentCore API calls. |
| **Agent access token** | An AWS-signed token created via the token exchange flow, carrying both identities for downstream authorization decisions. |

Workload identities can be created by you, but on Runtime and
Gateway they are usually **service-managed** — and that distinction
matters in section 06.

---

## 03 · Inbound: JWT authorizers and the workload access token

When Runtime or Gateway receives a request with inbound OAuth, the
service does five things for you automatically:

1. Validates the inbound IdP's OAuth token (issuer, signature).
2. Extracts the `iss` and `sub` claims (the end user).
3. Fetches the workload identity for the agent.
4. Calls
   [`GetWorkloadAccessTokenForJWT`](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_GetWorkloadAccessTokenForJWT.html)
   with both identities.
5. Injects the resulting **workload access token** into the agent
   invocation payload as a header.

Your code receives a token that already carries *who the user is*
and *which agent you are*. Two important traits from the docs:

- **First-party only.** The workload access token authorizes calls
  to AgentCore APIs (the credential vault, the resource providers).
  It is **not** an access token you can present to GitHub or
  Salesforce.
- **No self-extraction.** Runtime-managed and Gateway-managed
  workload identities **cannot retrieve their own workload access
  token** — the API refuses with *"WorkloadIdentity is linked to a
  service and cannot retrieve an access token by the caller."*
  That's the security boundary; agents can't mint their own
  tokens to bypass scope.

When you need to fetch a workload token from a self-hosted agent,
the SDK has two patterns:

    from bedrock_agentcore.services.identity import IdentityClient
    client = IdentityClient("us-east-1")

    # Preferred: JWT-bound
    tok = client.get_workload_access_token(
        workload_name="my-demo-agent",
        user_token="<inbound JWT>",
    )

    # Fallback: no JWT, identify the user by a string you control
    tok = client.get_workload_access_token(
        workload_name="my-demo-agent",
        user_id="cognito+user-123",  # partition by IdP to avoid collisions
    )

The `provider_id+user_id` pattern (e.g. `cognito+user123` vs
`auth0+user123`) is the docs' explicit recommendation when multiple
IdPs share a user namespace.

---

## 04 · Outbound: credential providers and the token vault

For anything external — GitHub, Slack, your own OAuth2 server, an
API-key-only weather service — you register an **outbound
credential provider** once. AgentCore Identity then handles the
authorization flow, stores the tokens in the **token vault** with
KMS encryption (customer- or service-managed), and vends them on
demand to whichever workload identity is authorized to call.

Three provider types from the
[Configure credential provider](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/resource-providers.html)
page:

- **OAuth 2.0** — built-ins for Google, GitHub, Slack, Salesforce,
  Atlassian/Jira, plus a `CustomOauth2` vendor for anything else.
  Discovery URL + client ID/secret + scopes.
- **API key** — a single stored key with secure retrieval. One
  command: `agentcore add credential --name svc --api-key …`.
- **Payment** — `CoinbaseCDP` and `StripePrivy`, where the secrets
  live in Secrets Manager and only the ARNs are surfaced to your
  code. Useful when an agent transacts on behalf of a user.

The CLI keeps the configuration in `agentcore/agentcore.json` and
puts sensitive values in `agentcore/.env.local`. The SDK call looks
like this:

    from bedrock_agentcore.services.identity import IdentityClient
    identity = IdentityClient("us-east-1")
    identity.create_oauth2_credential_provider({
        "name": "github-provider",
        "credentialProviderVendor": "GithubOauth2",
        "oauth2ProviderConfigInput": {
            "githubOauth2ProviderConfig": {
                "clientId":     "your-github-client-id",
                "clientSecret": "your-github-client-secret",
            }
        },
    })

Two OAuth flows are supported end-to-end:

- **2LO — client credentials grant.** Machine-to-machine. No user
  involved. Agent authenticates as itself against the resource
  server.
- **3LO — authorization code grant.** User consent required. The
  SDK will hand you back an authorization URL when consent is
  missing; you redirect the user, they approve, the token lands
  in the vault for next time.

---

## 05 · On-behalf-of: RFC 8693 vs RFC 7523, picked from a dropdown

The high-leverage feature in Identity is the **on-behalf-of (OBO)
token exchange**. Given an inbound user JWT, the credential
provider trades it for a *downstream* access token for a different
audience — without re-prompting the user for consent. The result
carries both the agent's identity and the original user's identity,
so the downstream resource server can enforce zero-trust at every
hop.

AgentCore Identity speaks **both** standards:

- **RFC 8693 OAuth 2.0 Token Exchange** (`grant_type` =
  `TOKEN_EXCHANGE`). Maps to
  `urn:ietf:params:oauth:grant-type:token-exchange`. The inbound
  JWT is the `subject_token`. You also configure an
  `actor_token_content` — `M2M` (do a client-credentials grant
  first and pass that token as `actor_token`),
  `AWS_IAM_ID_TOKEN_JWT` (call `sts:GetWebIdentityToken` and pass
  the resulting JWT), or `NONE`.
- **RFC 7523 §2.1 JWT Profile** (`grant_type` =
  `JWT_AUTHORIZATION_GRANT`). Maps to
  `urn:ietf:params:oauth:grant-type:jwt-bearer`. The inbound JWT
  is the `assertion`. No actor token required. This is what
  Microsoft Entra's on-behalf-of flow expects, and the
  `MicrosoftOauth2` built-in adds the
  `requested_token_use=on_behalf_of` parameter for you.

You pick the mode once, on the credential provider:

    aws bedrock-agentcore-control create-oauth2-credential-provider \
      --cli-input-json '{
        "name": "sample-obo-custom",
        "credentialProviderVendor": "CustomOauth2",
        "oauth2ProviderConfigInput": {
          "customOauth2ProviderConfig": {
            "oauthDiscovery": {
              "discoveryUrl": "https://my.idp.com/.well-known/openid-configuration"
            },
            "clientId":     "your-client-id",
            "clientSecret": "your-client-secret",
            "clientAuthenticationMethod": "CLIENT_SECRET_BASIC",
            "onBehalfOfTokenExchangeConfig": {
              "grantType": "TOKEN_EXCHANGE",
              "tokenExchangeGrantTypeConfig": {
                "actorTokenContent": "M2M",
                "actorTokenScopes":  ["scope1", "scope2"]
              }
            }
          }
        }
      }'

At runtime, the agent calls
[`GetResourceOauth2Token`](https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_GetResourceOauth2Token.html)
with `oauth2Flow=ON_BEHALF_OF_TOKEN_EXCHANGE`, passing the workload
access token (from `GetWorkloadAccessTokenForJWT`) as the binding
of *which* inbound user this exchange is for. AgentCore Identity
constructs the right exchange request, parses the response, and
hands the agent a downstream access token. **The agent never holds
the inbound user JWT or the client secret.**

> **The shift.** "We need OBO" used to mean reading three RFCs and
> writing a brittle token-exchange client. Now it's a config field
> on the credential provider plus a single SDK call.

One gotcha worth flagging: `actor_token_content =
AWS_IAM_ID_TOKEN_JWT` requires your account to be enabled for
outbound web identity federation
(`iam:EnableOutboundWebIdentityFederation`). Most accounts are not
by default.

---

## 06 · SDK ergonomics: two decorators

The agent code most people write boils down to two declarative
annotations from the AgentCore SDK:

- `@requires_access_token(provider_name="github-provider", scopes=["repo","user"])`
  — runs the OAuth flow if needed, refreshes if expired, and
  passes the access token into the wrapped function. If user
  consent is required, the decorator surfaces the authorization
  URL and propagates a structured error your front end can render
  as a "click to authorize" link.
- `@requires_api_key(provider_name="weather-svc")` — fetches the
  stored API key from the vault.

This is the layer most agent developers actually touch. Token
expiration, refresh, the entire OAuth state machine — handled.

If you need finer control, three condition keys let you scope a
workload identity down to specific credential providers:

| Pattern | What it isolates |
| --- | --- |
| Allow on `bedrock-agentcore:*` for one workload-identity ARN | This one agent can call any provider it has IAM rights to. |
| `Deny` on `GetResourceOauth2Token` unless `bedrock-agentcore:resourceCredentialProvider` matches a name | Block calls to a sensitive provider from anywhere but a designated workload. |
| Tag-based ABAC on both the workload identity and the provider | Per-tenant isolation without per-tenant IAM roles. |

The
[Scope down access to credential providers](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/scope-credential-provider-access.html)
page has the worked examples. The takeaway: the workload identity
is your IAM principal-level handle on credential vending.

---

## 07 · Limits worth knowing

From the
[official quotas page](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/bedrock-agentcore-limits.html#identity-service-limits):

- **1,000 workload identities** per account per Region. **Not
  adjustable.** Plan your identity hygiene — one identity per
  agent version, not per agent invocation.
- **50 OAuth2 credential providers** per account per Region. **Not
  adjustable.** That includes both ingress and egress providers,
  so consolidate by vendor where you can.
- **50 API key credential providers** per account per Region.
  **Not adjustable.**
- **Service-managed workloads cannot self-extract tokens.** This
  is a security feature, not a quota — but if you see
  *"WorkloadIdentity is linked to a service…"*, this is why.
- **OBO with `AWS_IAM_ID_TOKEN_JWT` requires outbound web identity
  federation** to be enabled at the account level — separate from
  Identity itself.
- **Token vault encryption.** Either a customer-managed AWS KMS
  key or the service-managed key. There is no "no encryption"
  option — pick before you start.

Two non-quota gotchas: agent IDs across multiple IdPs collide
unless you partition with `provider_id+user_id`, and the workload
access token is **opaque to your agent** — you can only present it
to other AgentCore APIs, not introspect it for user data.

---

## 08 · Try it in five minutes

Assuming an existing `agentcore` project from earlier in the week:

    # 1. Register an outbound OAuth2 credential provider for GitHub
    agentcore add credential --type oauth \
      --name github-provider \
      --discovery-url https://github.com/.well-known/openid-configuration \
      --client-id $GH_CLIENT_ID \
      --client-secret $GH_CLIENT_SECRET \
      --scopes repo,user
    agentcore deploy

    # 2. Wire the agent code to use it — two decorators do the OAuth dance
    python - <<'PY'
    from bedrock_agentcore.identity.auth import (
        requires_access_token, requires_api_key,
    )

    @requires_access_token(provider_name="github-provider",
                           scopes=["repo", "user"])
    async def list_my_repos(access_token: str = ""):
        import httpx
        async with httpx.AsyncClient() as c:
            r = await c.get(
                "https://api.github.com/user/repos",
                headers={"Authorization": f"Bearer {access_token}"},
            )
            return [r["full_name"] for r in r.json()]
    PY

    # 3. Invoke locally — the first run prints an authorization URL;
    #    open it once, approve, and the token lands in the vault.
    agentcore dev
    agentcore invoke '{"prompt":"List my repos"}'

The very first call surfaces a consent URL because there is no
stored 3LO token yet. After consent, the vault holds the refresh
token, and every subsequent invocation runs without prompting —
even from a different machine, because the credentials live in
AgentCore Identity, not on disk.

Tomorrow we'll look at **AgentCore Gateway** — the managed surface
that turns any REST API, Lambda, or OpenAPI spec into MCP tools
your agent can call, with this same Identity layer underneath
handling outbound auth.

---

**Verified against the official AWS docs on 2026-05-29.**
Sources:
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-overview.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-terminology.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/key-features-and-benefits.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-outbound-credential-provider.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/resource-providers.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/get-workload-access-token.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/on-behalf-of-token-exchange.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/scope-credential-provider-access.html>,
<https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/bedrock-agentcore-limits.html>.

If the docs change, this lesson is a snapshot of that day — check
the sources for current behaviour.

---

> **This page — research, writing, verification, and deployment — was built by
> Claude Cowork.** No human touched the prose, the layout, or the upload
> pipeline. The lesson was generated this morning, cross-checked against the
> official AWS docs by an independent verification pass, and published
> to Cloudflare R2 on a schedule.
>
> A daily experiment by Monty van Emmerik · <https://vanemmerik.ai/>

— AWS Bedrock & AgentCore · Tip of the Day · No. 004 · vanemmerik.ai
