
Agentic AI Identity anti-patterns and how Amazon Bedrock AgentCore solves them
AI agents authenticate across five actors (user, app, agent, gateway, tools) — and most teams only configure the static half. This post covers six production identity anti-patterns, how Amazon Bedrock AgentCore solves each with dynamic runtime enforcement, and what your observability stack should (and shouldn't) be showing you.
AI agents sit between you and your enterprise systems. Every agent must authenticate each invocation: as whom, who allowed it, under what condition, and for how long?
You likely started with the first step: set up an IAM role or register an OAuth client. That covers the static part. Production agent systems also need dynamic identity: runtime policy evaluation, just-in-time token exchange, session-bound credentials, and interceptor-based access control. This post covers the six identity anti-patterns we see in production, how Amazon Bedrock AgentCore addresses each, why static configuration alone is insufficient, and what your observability stack should (and should not) show you.
What is agent identity?
In a traditional application, identity is binary: you authenticate, and the application acts on your behalf. In an agentic system, identity is a chain — multiple actors, each with their own credential, bound together by trust relationships.
Five actors
Actor | Role | Identity Type |
|---|---|---|
User | The human who initiated the request | OIDC / OAuth identity (JWT from IdP) |
Application | The frontend or orchestration layer calling the agent | API key, IAM role , or OAuth client |
Agent | The autonomous AI workload performing actions | Workload identity (own client_id + client_secret ) |
Gateway | The infrastructure middleman routing and enforcing policy | Service identity ( AgentCore Gateway + interceptor chain) |
Tools | The downstream APIs and resources the agent touches | Resource-specific credentials (scoped access tokens) |
Identity bindings
What binds these actors together is not a single credential — it's a chain of delegated trust, expressed through different OAuth 2.0 flows depending on the relationship:

Machine-to-Machine (M2M) — Agent authenticates as itself (
client_credentials grant ). No user in the loop. Use for: service-to-service APIs, background jobs, scheduled pipelines.Three-Legged OAuth (3LO) — User explicitly consents to the agent accessing their resources (
authorization_code grant + PKCE ). The agent never sees the user's password. Use for: first-time access to email, calendar, files.On-Behalf-Of (OBO) — Agent exchanges an inbound user token for a downstream resource token (
token_exchange , RFC 8693 ). The resulting token carries composite identity: sub=user , act=agent . Use for: accessing user-scoped resources without re-prompting consent.Why this matters
Four questions determine whether your agent identity is functional:
- As whom? — The agent must have its own identity, distinct from the user
- Who allowed it? — Every action traces back to a consent event or policy decision
- Under what condition? — Credentials are scoped, time-bound, and session-linked
- For how long? — Tokens expire, sessions end, and revocation propagates instantly
If any of these can't be answered for a given request, you have an identity gap. The six anti-patterns below show where those gaps appear in production, and where in the chain (user → app → agent → gateway → tool) the failure occurs.
Classify first: the L0–L3 framework
Not every agent needs every control. The required identity posture depends on the agent's agency (what it can access) and autonomy (whether a human is in the loop):
| Low autonomy (human-in-the-loop) | High autonomy (self-directed) |
|---|---|---|
Low agency | L0 — Public info only. Inbound auth sufficient. | L2 — Pre-authorized, bounded by standing policy. Needs M2M + Token Vault + rotation. |
High agency | L1 — User-consented. Needs 3LO + scoped tokens. | L3 — Broad access, delegates to sub-agents. Needs everything: OBO, Cedar policy, FGAC interceptors, delegation chain controls. |
Higher agency demands stronger credential controls. Higher autonomy demands stronger policy enforcement. The anti-patterns below become progressively more dangerous as you move toward L3.
The six agent identity anti-patterns

Anti-Pattern #1: Impersonation Instead of Delegation
The agent replays the user's access token directly against downstream APIs. No separate agent identity exists.
Scenario: An AI assistant obtains a user's Entra ID token and calls Microsoft Graph with it. Audit logs show the user sent 400 emails in 2 minutes — security flags it as account compromise. It was the agent. There's no way to distinguish or revoke agent access independently.

The Fix: OBO Token Exchange with AgentCore Identity

AgentCore Identity performs RFC 8693 token exchange. The resulting token carries composite identity (
sub=user , act=agent ). Downstream services distinguish agent-initiated actions from direct user actions. You revoke the agent without touching the user's session.Key API calls:
-
GetWorkloadAccessTokenForJWT— binds user identity to agent workload
-
GetResourceOauth2Token(oauth2Flow=ON_BEHALF_OF_TOKEN_EXCHANGE)— exchanges for downstream token
Anti-Pattern #2: Shared Credentials & Static Tokens
Multiple agents share one token, or tokens never expire. Secrets stored in code repos or env vars.
Scenario: 12 agents share one
svc-bot Salesforce integration user. The token leaks from one agent's debug logs — all agents and all customer records are exposed. No attribution of which agent acted. Rotation breaks all 12 simultaneously.The fix: Credential Providers + workload identity
Before | After (AgentCore Identity) |
|---|---|
12 agents share one client_id + client_secret | Credentials live in a Credential Provider — agents never see them |
Token created in 2022, never rotated | Agent receives a short-lived JWT (minutes). Underlying secret rotation is invisible to agents |
Stored in .env or hardcoded | Agent authenticates to Credential Provider via its own workload identity |
Compromise = total exposure (secret + all data) | * Compromise = total exposure (secret + all data) |
The agent authenticates to the Credential Provider using its workload identity. The Credential Provider holds the actual
client_id and client_secret — the agent never sees them, they're never logged, and they can't be leaked from agent code or debug output.What the agent does see is the resulting JWT. That's the only thing that can leak. Blast radius: the JWT's TTL (minutes), scoped to whatever the Credential Provider issued it for.
A single Credential Provider can be shared across multiple agents — each agent proves its identity via its workload credentials, and the Provider decides what to issue. The underlying secret is managed, rotated, and audited in one place.
* To eliminate the compromise, AgentCore Gateway can broker the communication with downstream resources and handle credential exchange entirely with AgentCore Identity, so the agent sees only tool results and no auth tokens at all.
Anti-Pattern #3: Over-Scoped OAuth Grants
The agent requests
Mail.ReadWrite , Calendars.ReadWrite , Files.ReadWrite.All when it only needs Mail.Read . "Admin consent so it works."Scenario: A compromised email-summarization agent's token exposes the user's full mailbox, calendar, and entire OneDrive — not just read access to email.
The Fix: Scoped resourceOauth2Scopes Per Tool
Python
@requires_access_token(auth_flow="USER_FEDERATION",scopes=["Mail.Read"] # minimum viable scope)def summarize_emails(user_id: str):...
AgentCore enforces scope declarations at the tool level. Each tool specifies exactly what it needs. Incremental consent prompts only when a tool with additional scopes is invoked — not upfront.
Anti-Pattern #4: No Credential Isolation & No Revocation
All users' tokens in one flat table keyed by
user_id . Access tokens have 24-hour TTL. No mechanism to revoke before expiry.Scenario: A user leaves. IT disables their Okta account. The agent holds a valid 24-hour token and continues operating at full access — because there's no revocation mechanism and no short TTL to limit the window.
The fix: Token Vault isolation + short TTL + forced re-authentication
Before | After (AgentCore Identity - Token Vault) |
|---|---|
One flat table for all users | Composite key: {agent_id}#{user_id}#{resource} |
Shared encryption key | Per-tenant KMS key |
24-hour access tokens | 5–15 minute TTL, auto-refreshed |
No revocation mechanism | Short TTL + forceAuthentication=true to purge stored tokens |
Terminated user → 24h exposure | Terminated user → minutes of residual access |

For immediate invalidation: Call
GetResourceOauth2Token with forceAuthentication: true . This clears the stored refresh token from the vault and forces a full re-authentication flow. If the user's IdP account is disabled, re-authentication fails — access is permanently revoked.One-time-use refresh tokens add replay detection: a stolen refresh token can only be used once before it's rotated, limiting the window even further.
Anti-Pattern #5: Standing Access & No Session Binding
The agent authenticates via M2M (
client_credentials ) to access user-scoped data. It has permanent credentials — no user needs to be present.Scenario: A data analytics agent holds a standing M2M token with
HR.Read.All scope. It queries salary data at 3 AM with no user session. Audit shows "agent-xyz accessed HR API" but not on whose behalf.The fix: OBO + principal-constrained Cedar policy
Structural fix: Replace M2M with OBO for user-scoped data. The agent calls
GetWorkloadAccessTokenForJWT — no inbound user JWT = call fails = no downstream credentials exist.Policy fix: Constrain the permit to
AgentCore::OAuthUser — so even if someone grants the agent IAM access, Cedar blocks it:plaintext
permit(principal is AgentCore::OAuthUser,action == AgentCore::Action::"HRTool___query_records",resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/my-gateway")when {principal.getTag("role") == "hr-analyst" &&duration("8h") <= context.system.now.toTime() &&context.system.now.toTime() <= duration("18h")};
Three things happen:
-
principal is AgentCore::OAuthUser— M2M calls present asAgentCore::IamEntity, don't match this permit, fall to default DENY
-
principal.getTag("role")— only hr-analysts, not any authenticated user
-
context.system.now.toTime()— even with a valid token, denied outside 8–18h UTC
No user JWT = no OBO token = agent can't reach the Gateway with OAuth credentials. Standing access becomes structurally impossible.
For approval-gated workflows, temporal policies (Dogwood) can additionally require a prior approved action within the session before permitting sensitive operations.
Anti-Pattern #6: Uncontrolled Cross-Agent Delegation
Agent A delegates to Agent B with full permissions. No scope reduction exists at delegation boundaries.
Scenario: An orchestration agent delegates to a sub-agent which delegates again. The third-hop agent (meant for S3 reads) ends up with permissions to write production databases.
The Fix: AgentCore Policy + fine-grained access control via AgentCore Gateway Interceptors at every boundary

Cedar policies enforce explicit authorization at each delegation hop. Gateway interceptors filter the tool list per agent. Every hop is logged with the complete delegation chain.
Static vs. dynamic identity: setting up IAM is half the story

Most teams stop at the static layer. They configure IAM roles, register OAuth clients, define scopes, and set up IdP trust. This answers "who can access what." It does not answer "who should access what, right now, under these conditions."
Static layer (necessary, insufficient)
What it covers | What it misses |
|---|---|
IAM execution role for the agent | Whether the agent should use it now |
OAuth client registration | Per-request scope evaluation |
Scope definitions | Runtime scope reduction per delegation |
IdP trust (OIDC discovery) | Session-binding of credentials |
Dynamic layer (where AgentCore operates)
- Cedar Policy Evaluation — Evaluated on every Gateway request. Conditions include time-of-day (
context.system.now), tool input parameters (context.input.<field>), and user attributes from the JWT (principal.getTag()).forbidalways wins overpermit. Default = DENY. For rules that depend on what happened earlier in a session (e.g., requiring a prior approval), Temporal Policies (Dogwood) evaluate against session history — not the current request alone.
- Gateway Interceptors — Lambda functions at the request/response boundary. Token exchange (swap Okta JWT for GCP token), interceptors (filter tools by JWT scopes), PII masking (redact before reaching LLM). No caching — real-time evaluation.
- JIT Token Exchange ( OBO ) — The agent calls
GetResourceOauth2Tokento obtain a short-lived access token scoped to the declared permissions. Tokens are cached in the vault and auto-refreshed — the agent never holds the underlyingclient_id/client_secret. If the Gateway is used to reach the resources instead, the agent never sees a token at all.
- Session-Bound Credentials — Token Vault releases credentials only during active user sessions. No session = no tokens, regardless of what IAM or OAuth configuration allows.

How this works in practice
AgentCore Gateway working together with AgentCore Policy and AgentCore Identity provides exactly this runtime evaluation layer lifting the responsibility from the developer to write the code for authentication or authorization as part of the agent logic:

Observability of agent identity: what you should see, and what silence means
You don't get agent identity observability from a single dashboard. It's a shared responsibility between what AgentCore provides and what you build on top.
What ideal looks like
For every agent-initiated request, you should be able to answer:
which agent, acting for which user, called which tool, with what parameters, under what policy decision, and what was the outcome?
If you can't reconstruct that chain from your logs, you have blind spots.
What AgentCore provides
Signal | Source | What you see | Validates fix for |
|---|---|---|---|
| Cedar policy language decisions (PERMIT / DENY) | CloudWatch Logs | Every policy evaluation: principal, action, resource, decision, and the conditions that matched | Over-scoped grants, standing access |
AgentCore Identity API calls | CloudTrail | GetWorkloadAccessToken , GetResourceOauth2Token — which workload identity called, when, with what oauth2Flow | Impersonation, shared credentials |
Policy enforcement mode | CloudTrail | UpdateGateway events — alerts you if someone switches from ENFORCE to mode | All anti-patterns (enforcement disabled = none are enforced) |
Temporal policy session events | CloudWatch Logs | If you use Temporal Policies (Dogwood) , the policy engine logs session history and evaluation results | Approval-gated workflows |
What you build yourself
Signal | How to build it | Validates fix for |
|---|---|---|
Lifecycle-triggered token revocation | IdP webhook (Okta/Entra event) → Lambda → GetResourceOauth2Token ( forceAuthentication=true) | No isolation / revocation — active revocation on termination |
Gateway interceptor audit logs | Your interceptor Lambda writes to CloudWatch: what was filtered, masked, or transformed per request | Uncontrolled delegation — scope reduction at boundaries |
Downstream service attribution ( sub + act claims) | Configure downstream services (MS Graph, Salesforce) to log token claims. The act=agent claim is in the OBO token — the downstream service decides whether to surface it. | Impersonation — proving delegation, not impersonation |
Per-agent identity usage dashboards | Query CloudTrail for GetResourceOauth2Token grouped by workload identity. Alert if one identity serves multiple agents. | Shared credentials — limiting damage from a single compromise |
Scope drift detection | Compare declared scopes in @requires_access_token() across deployments. Alert on scope additions without review. | Over-scoped grants — incremental scope creep |
Red flags: what silence tells you
What's missing in your logs | What it means |
|---|---|
Zero Cedar DENY events with policies in ENFORCE mode | Scopes may be too broad — agents never hit a boundary. Or policies aren't attached to the Gateway. |
Only one workload identity across all agents in CloudTrail | Agents share credentials — no per-agent attribution and no way to limit damage from one compromise. |
No GetWorkloadAccessToken / OBO calls in CloudTrail | Agent accesses user-scoped data via M2M — no user in the loop. Standing access not fixed. |
No forceAuthentication=true calls after HR lifecycle events | No active revocation path. Terminated users retain access until token TTL expires passively. |
No interceptor logs for multi-agent delegation flows | Scope reduction at delegation boundaries isn't instrumented — you can't verify it's happening. |
UpdateGateway flipping mode to LOG_ONLY with no alert | Someone disabled enforcement — all policies evaluate but don't block. |
Defense in depth: the full stack

Every request traverses all layers. A failure at any single layer does not grant access — the next layer still evaluates independently. This is Zero Trust applied to agent identity.
Summary
Agent identity is a runtime discipline. The six anti-patterns in this post come from production incidents across enterprises deploying agentic AI.
- Delegate, never impersonate. Agents authenticate as themselves with verifiable user context.
- Enforce at runtime. Cedar policies and interceptors evaluate conditions per-request, not per-deployment.
- Observe the absence. If your dashboards show no DENY events, no delegation chains, and no session boundaries, you have standing access with no guardrails.
Amazon Bedrock AgentCore provides the infrastructure to enforce these principles without requiring you to write auth code. Identity is the platform's job — not the agent's.
To get started, explore the Amazon Bedrock AgentCore Developer Guide and try the AgentCore Identity workshop . If you have questions or feedback, leave a comment on this post or reach out through the AWS re:Post community .
References
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article