Zero-Trust AI Tool Access: How Claude Code Authenticates to AgentCore MCP Servers with Okta
Claude Code can connect to enterprise tools through MCP servers hosted on AWS AgentCore, secured with OAuth 2.0 and Okta. This post details the complete authentication flow — server discovery, PKCE-based login, JWT validation — for both AgentCore Runtime and Gateway. Includes step-by-step Okta and Claude Code configuration, a protocol-annotated flow diagram, localhost redirect security analysis, authentication policy hardening, and troubleshooting.
The views and opinions expressed in this article are my own and do not represent the official positions of Amazon Web Services (AWS).
Claude Code's power grows significantly when it can reach beyond its built-in capabilities. Through the Model Context Protocol (MCP), Claude Code can connect to external tools and services — databases, APIs, internal systems — and use them as naturally as its own features. AWS AgentCore provides a managed way to host and secure these MCP servers, and when you combine it with an identity provider like Okta, you get enterprise-grade authentication without building any auth infrastructure yourself.
This post walks through how the OAuth flow works when Claude Code connects to an AgentCore-hosted MCP server, what you need to configure on both the Okta and AWS sides, and how the entire handshake plays out under the hood. Everything here has been validated with live testing against both AgentCore Runtime and AgentCore Gateway.
Why OAuth for MCP Servers?
When Claude Code connects to an MCP server, it gains access to whatever tools that server exposes — reading from databases, calling internal APIs, triggering workflows. In an enterprise setting, you need to know who is making those calls and whether they're authorized to do so.
OAuth solves this cleanly. Instead of embedding API keys or service credentials into Claude Code's configuration, the user authenticates through their organization's identity provider (Okta, in this example) using a standard browser-based flow. Claude Code receives a JWT access token scoped to that user's permissions, and every subsequent MCP call carries that token. AgentCore validates it on every request.
The result is a zero-trust model where:
- Users authenticate with their existing corporate identity — no new credentials to manage
- Every MCP call is traceable to a specific user — full audit trail
- Access is governed by your IDP's policies — MFA, session limits, conditional access all apply
- No secrets are stored in config files — tokens live in the system keychain
This is the same pattern used across enterprise SaaS integrations, applied to AI tool access.
Two Paths: AgentCore Runtime vs. AgentCore Gateway
AWS AgentCore offers two ways to host MCP servers, and both support OAuth authentication. The choice between them depends on how you want to host your tools.
AgentCore Runtime hosts your own containerized MCP server. You bring a Docker container that implements the MCP protocol, and AgentCore runs it for you with built-in scaling, networking, and authentication. The URL format uses a path-based structure with your runtime's ARN encoded in the path. For a step-by-step walkthrough of building and deploying an MCP server on Runtime — including container setup, authentication configuration, and testing — see Deploy MCP servers in AgentCore Runtime .
AgentCore Gateway provides a managed MCP endpoint that routes to AWS-native targets like Lambda functions, OpenAPI specs, external MCP servers or other services (check out post by Dhawal Patel ). For end-to-end gateway setup including tool creation, target configuration, and inbound OAuth authorization, see Set up an AgentCore Gateway . If you're adding an existing MCP server as a gateway target, the MCP server targets guide covers the specific configuration for that target type.
| AgentCore Runtime | AgentCore Gateway | |
|---|---|---|
| URL format | https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded-arn}/invocations | https://{gateway-id}.gateway.bedrock-agentcore.{region}.amazonaws.com/mcp |
| What it hosts | Your containerized MCP server | AWS-managed targets (Lambda, OpenAPI, etc.) |
| Auth options | OAuth JWT, SigV4 | OAuth JWT, IAM, None |
| MCP endpoint | /invocations?qualifier=DEFAULT | /mcp |
| Setup guide | Deploy MCP servers in Runtime | Set up a Gateway |
Both paths support external identity providers for OAuth authentication. AgentCore Identity supports Okta, Auth0, Google, GitHub, Salesforce, and other OAuth 2.0-compliant providers — see the supported identity providers list for the full set, and the Okta-specific setup guide for the IDP configuration used in this post.
The OAuth flow is identical from Step 3 onwards — only the initial URL format and discovery endpoints differ. Everything in this post applies to both paths.
For additional context on these hosting models, see the AWS blog posts: Introducing Amazon Bedrock AgentCore Gateway and Unite MCP servers through AgentCore Gateway .
What You Need Before Starting
The setup involves three systems working together. Before connecting Claude Code, make sure each one is ready.
On the Okta side
You need a Native Application configured for the Authorization Code flow with PKCE. Key settings:
- Application type: Native (enables RFC 8252 localhost redirects)
- Grant types: Authorization Code + Refresh Token
- Client authentication: None (public client — no client secret)
- PKCE: Required (S256)
- Redirect URIs:
http://localhost:8080/callback
The port number (8080 in this example) is your choice, but it must match exactly between Okta's redirect URI registration and the
--callback-port you pass to Claude Code. Okta enforces exact URI matching.Your Okta authorization server also needs the right scopes — at minimum
openid, plus any custom scopes your AgentCore configuration requires.On the AgentCore side
Your AgentCore Runtime or Gateway must have a JWT authorizer configured with your Okta authorization server's OIDC discovery URL. This tells AgentCore where to find the public keys for JWT validation and which issuer and audiences to trust.
You can verify AgentCore is configured correctly by sending an unauthenticated request and checking for the expected 401 response:
1
2
3
4
curl -v -X POST \
"https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/<encoded_arn>/invocations?qualifier=DEFAULT" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{}}}'You should see
HTTP/2 401 with a www-authenticate header containing a resource_metadata URL. If you get a different response, the JWT authorizer configuration needs attention before proceeding.On the Claude Code side
Claude Code needs to be installed and available on the command line. Claude Code works with Anthropic's API directly, but organizations that want to keep AI traffic within their AWS environment can also run it through Amazon Bedrock. If you haven't set this up yet, the key steps are:
- Configure AWS credentials using any method supported by the AWS SDK — SSO profile, environment variables, or
aws configure. - Set the required environment variables to connect Claude Code to Bedrock:
1
2
3
export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-east-1 # required — must match your model's region
export AWS_PROFILE=your-profile # if using SSO or named profiles- Pin model versions to prevent breakage when new model versions are released:
1
2
3
export ANTHROPIC_DEFAULT_SONNET_MODEL='us.anthropic.claude-sonnet-4-6'
export ANTHROPIC_DEFAULT_OPUS_MODEL='us.anthropic.claude-opus-4-6-v1'
export ANTHROPIC_DEFAULT_HAIKU_MODEL='us.anthropic.claude-haiku-4-5-20251001-v1:0'For the full setup guide — including IAM permissions, credential refresh, Guardrails configuration, and troubleshooting — see the Claude Code on Amazon Bedrock documentation .
Once Claude Code is running with Bedrock, the MCP server will be added using the
claude mcp add command with OAuth parameters.Adding the MCP Server to Claude Code
With all three systems ready, register the AgentCore MCP server in Claude Code:
For AgentCore Runtime:
1
2
3
4
5
claude mcp add --transport http \
--client-id <your-okta-client-id> \
--callback-port 8080 \
agentcore-mcp \
"https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/<encoded_arn>/invocations?qualifier=DEFAULT"For AgentCore Gateway:
1
2
3
4
5
claude mcp add --transport http \
--client-id <your-okta-client-id> \
--callback-port 8080 \
agentcore-mcp \
"https://<gateway-id>.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp"The
--client-id flag tells Claude Code to use a pre-registered OAuth client rather than attempting Dynamic Client Registration (DCR). Verify the configuration was stored correctly:
1
claude mcp get agentcore-mcpThis should show the URL, client ID, and callback port. The configuration is written to
~/.claude.json — no secrets are stored there, only the client ID and connection details.For more on Claude Code's MCP configuration options, see the Claude Code MCP documentation .
How the OAuth Flow Works
When you first use the MCP server, an OAuth 2.0 Authorization Code flow with PKCE plays out between the MCP client (Claude Code), AgentCore, and Okta. The MCP specification defines the protocol requirements, and Claude Code's MCP documentation confirms its support for OAuth 2.0 with MCP servers. Here's how the flow works step by step.
1. Discovery — AgentCore signals how to authenticate
On the first MCP request, the client sends a message with no authentication. AgentCore responds with
HTTP 401 and a WWW-Authenticate header containing a Protected Resource Metadata (PRM) URL — following RFC 9728 .The client fetches this PRM endpoint (which is intentionally unauthenticated — it's a discovery mechanism) and learns two things: which authorization server to use (your Okta tenant) and the resource identifier for token binding.
This is documented in the AgentCore MCP protocol contract :
"OAuth-configured agents follow RFC 6749 (OAuth 2.0) authentication standards. When authentication is missing, the service returns a 401 Unauthorized response with a WWW-Authenticate header (per RFC 7235), enabling clients to discover the authorization server endpoints."
2. Metadata — the client learns Okta's endpoints
Using the issuer URL from the PRM response, the client requests Okta's authorization server metadata. The MCP specification requires clients to implement this metadata discovery. The response tells the client where to send the user for login (
authorization_endpoint) and where to exchange the code for tokens (token_endpoint).3. PKCE and browser authentication
The client generates a cryptographic PKCE pair — a random
code_verifier and its SHA-256 hash as the code_challenge — and opens your default browser to Okta's authorization endpoint with the challenge, client ID, redirect URI, and requested scopes. For a deeper look at how PKCE works under the hood — including the code verifier/challenge exchange and why it replaced the implicit flow — see Okta's Implement the OAuth 2.0 Authorization Code with PKCE Flow .You see Okta's login page (or go straight to consent if you have an active SSO session). After authenticating, Okta redirects your browser to
http://localhost:8080/callback with an authorization code.A temporary HTTP server on the callback port catches this redirect, validates the state parameter, and extracts the authorization code.
4. Token exchange and storage
The client exchanges the authorization code and the original
code_verifier with Okta's token endpoint. Because this is a public client (no client secret), PKCE is the sole proof of the client's identity.Okta validates that
SHA256(code_verifier) == code_challenge and returns an access token (JWT) and optionally a refresh token. As noted in Claude Code's documentation , authentication tokens are stored securely (in the system keychain on macOS) — not in configuration files.5. Authenticated MCP calls — AgentCore validates every request
From this point on, every MCP request includes the JWT as a Bearer token:
1
2
3
4
5
6
POST /runtimes/<encoded_arn>/invocations?qualifier=DEFAULT HTTP/2
Host: bedrock-agentcore.us-east-1.amazonaws.com
Authorization: Bearer eyJraWQiOiJ...
Content-Type: application/json
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"your_tool","arguments":{...}}}
AgentCore validates the JWT on every request — checking the signature against Okta's JWKS, verifying the issuer, audience, expiration, and optionally the
client_id claim against its configured allowedClients . Only after validation passes does AgentCore forward the request to your MCP server.The Complete Flow at a Glance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Claude Code Browser AgentCore Okta
| | | |
| | | |
| DISCOVERY (HTTPS) | | |
|--- POST /mcp (no auth) -------------------->| |
|<-- 401 + WWW-Authenticate ------------------| |
| (PRM discovery URL) | |
| | |
|--- GET PRM endpoint ----------------------->| |
|<-- {authorization_servers, resource} -------| |
| |
|--- GET /.well-known/oauth-authorization-server ----------------------->|
|<-- {authorization_endpoint, token_endpoint} ---------------------------|
| |
| LOCAL PREPARATION |
| [Skips DCR — client-id provided] |
| [Generates PKCE code_verifier + code_challenge] |
| [Starts temp HTTP server on 127.0.0.1:8080] |
| |
| BROWSER AUTHENTICATION (HTTPS — over the network) |
|--- Opens browser ---->| |
| |--- GET /authorize?client_id=... [HTTPS] ----->|
| | [User authenticates in browser] |
| | |
| REDIRECT (two hops) |
| |<-- 302 Location: http://localhost:8080/ |
| | callback?code=ABC&state=XYZ [HTTPS] ------|
| | (code is in HTTPS response — encrypted) |
| | |
|<-- GET /callback?code=ABC&state=XYZ [HTTP loopback — never leaves |
| (browser → 127.0.0.1:8080) the device] |
| |
| [Validates state, extracts code, shuts down temp server] |
| |
| TOKEN EXCHANGE (HTTPS — over the network) |
|--- POST /token {code, code_verifier, client_id} [HTTPS] ------------->|
|<-- {access_token, refresh_token} [HTTPS] -----------------------------|
| |
| [Stores tokens in system keychain] |
| |
| AUTHENTICATED MCP CALLS (HTTPS) |
|--- POST /mcp | |
| Authorization: Bearer <jwt> --->| |
| |-- Validate JWT (JWKS) ----------->|
| |<-- Valid -------------------------|
| | |
| |-- Forward to MCP server |
|<-- MCP Response -------------------| |Protocol summary: Every arrow that crosses the network uses HTTPS. The only HTTP leg is the browser-to-localhost redirect (hop 2 of the callback), which stays on the loopback interface and never leaves the device. PKCE ensures the authorization code is useless without the
code_verifier that only Claude Code holds in memory.Hardening the Authentication Policy
You may notice that the OAuth flow redirects the authorization code to
http://localhost — HTTP, not HTTPS. This is worth understanding in detail, because it's secure by design.How the redirect actually works — two hops, not one
When Okta completes authentication, it sends a
302 redirect response with Location: http://localhost:8080/callback?code=ABC&state=XYZ. That 302 response travels from Okta's server to your browser over HTTPS — the authorization code is inside the Location header of an encrypted response. 1
2
3
4
Hop 1 (encrypted):
Okta server ──── HTTPS ────> Your browser
Receives 302 response
Location: http://localhost:8080/callback?code=ABCYour browser then follows that redirect by making a new, separate GET request to
http://localhost:8080/callback?code=ABC. This is the HTTP (non-TLS) leg — but the destination is 127.0.0.1 on your own machine. The request goes from your browser process to Claude Code's temporary HTTP server, both running on the same device. It never touches a network interface.1
2
3
Hop 2 (loopback only):
Your browser ──── HTTP (127.0.0.1) ────> Claude Code's temp server on port 8080
(same machine — never leaves the device)| Hop | From | To | Protocol | Authorization code exposed to network? |
|---|---|---|---|---|
| 1 | Okta server | Your browser | HTTPS (encrypted) | No |
| 2 | Your browser | localhost:8080 | HTTP (loopback) | No — stays on the device |
The authorization code is never sent unencrypted over a network. Hop 1 is TLS-protected. Hop 2 is loopback-only.
Why HTTP on loopback is standards-compliant
RFC 8252 (OAuth 2.0 for Native Apps) Section 8.3 explicitly permits this:
"Loopback interface redirect URIs use the 'http' scheme (without TLS). This is acceptable for loopback interface redirect URIs as the HTTP request never leaves the device."
The MCP specification follows the same model: redirect URIs must be either localhost URLs or HTTPS URLs.
What about same-device interception?
RFC 8252 Section 8.1 acknowledges that a malicious process on the same machine could potentially observe loopback traffic or race to bind the same port:
"Loopback IP-based redirect URIs may be susceptible to interception by other apps accessing the same loopback interface on some operating systems."
This is where PKCE provides the critical mitigation. Even if a local attacker captures the authorization code from the loopback redirect, they cannot exchange it for a token — the token exchange requires the
code_verifier, which only Claude Code knows (it was generated in-process and sent to Okta's token endpoint over TLS). RFC 8252 Section 8.1:"The PKCE protocol was created specifically to mitigate this attack. An app that intercepted the authorization code would not be in possession of this secret, rendering the code useless."
Enterprise defense-in-depth
The redirect mechanism is secure by design — HTTPS for the network hop, loopback isolation for the local hop, and PKCE as a backstop against same-device interception. That said, in an enterprise environment you should still layer additional controls through a dedicated Okta authentication policy for the Claude Code application.
Create a dedicated authentication policy in Okta
In addition to using Okta's global default policy, you can create a separate authentication policy scoped specifically to your Claude Code Native app. This gives you independent control without affecting other applications in your tenant.
In Okta Admin: Security > Authentication Policies > Add a Policy and assign it to your Claude Code application.
Recommended policy rules
Within that policy, consider defining rules around:
- Network zones — restrict authentication to corporate IP ranges or VPN exit points
- User and group assignments — scope access to specific groups (e.g.,
claude-code-users) rather than the entire organization - Device trust — require managed/compliant devices via Okta Device Trust or MDM (Jamf, Intune)
- Behavior detection — step up or deny authentication based on risk signals like new devices, unusual locations, or impossible travel
- Multi-factor authentication — require MFA for every sign-in, or at minimum on new devices, using Okta Verify, FIDO2/WebAuthn, or other supported factors
- Session limits — set maximum session lifetimes that work alongside the access token lifetime to create a layered expiry model
For the full set of policy configuration options, see the Okta app sign-on policy documentation .
Common Setup Issues and Fixes
"redirect_uri_mismatch" error in the browser
The redirect URI Claude Code is using doesn't exactly match what's registered in Okta. Register
http://localhost:8080/callback in your Okta app. The port must match your --callback-port exactly.AgentCore returns 401 even after authentication
The JWT is valid but failing AgentCore's validation. Decode your token at jwt.io and check:
issmust match your Okta authorization server's issuer URLaudmust match AgentCore's configuredallowedAudiencesclient_idclaim must be present if AgentCore hasallowedClientsconfigured
A common issue: Okta doesn't include a
client_id claim in access tokens by default. If AgentCore requires it, add a custom claim in Okta: Security > API > Authorization Server > Claims > Add Claim with name client_id and value app.clientId.Token acquired but no tools appear
Authentication succeeded but the MCP connection is failing. Test the endpoint directly with a valid token:
1
2
3
4
curl -v -X POST "<agentcore-url>" \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'If this returns tools, the MCP server is healthy and the issue is in Claude Code's connection handling. If it returns 401, revisit the JWT claims. If it returns 500, the MCP server container itself needs debugging.
Quick Reference
One-time setup
1
2
3
4
5
6
7
8
9
# Add the MCP server with OAuth credentials
claude mcp add --transport http \
--client-id <okta-client-id> \
--callback-port 8080 \
agentcore-mcp \
"<agentcore-runtime-or-gateway-url>"
# Verify
claude mcp get agentcore-mcpAuthenticate (first time and after token expiry)
1
2
3
4
5
6
/mcp
Select agentcore-mcp (shows "needs authentication")
1. Authenticate
[Browser opens, log into Okta]
Browser shows "Authentication Successful"
Claude Code shows agentcore-mcp as ConnectedOkta app checklist for setup used in this post
- [ ] Application type: Single Page App (SPA)
- [ ] Grant types: Authorization Code + Refresh Token
- [ ] Client authentication: None (public client)
- [ ] PKCE: Required (S256)
- [ ] Redirect URIs:
http://localhost:8080/callback - [ ] Audience matches AgentCore's
allowedAudiences - [ ] Required scopes present in authorization server
References
- Claude Code MCP Documentation — adding MCP servers, OAuth configuration,
--client-idand--callback-portflags - AgentCore MCP Protocol Contract — 401 response format, WWW-Authenticate header, PRM discovery
- AgentCore JWT Authorizer — JWT validation configuration, discovery URL, allowed audiences and clients
- AgentCore Deploy MCP Servers — end-to-end MCP deployment including Auth0 example and
resourceparameter confirmation - AgentCore Gateway Setup — end-to-end gateway creation with tool configuration and inbound/outbound authorization
- AgentCore Gateway MCP Server Targets — configuring MCP servers as gateway targets with OAuth2 authorization
- AgentCore Gateway Inbound Auth — Gateway JWT authorizer configuration
- AgentCore Supported Identity Providers — full list of supported OAuth 2.0 identity providers (Okta, Auth0, Google, GitHub, and more)
- AgentCore Identity — Okta Setup — Okta-specific identity provider configuration
- Introducing Amazon Bedrock AgentCore Gateway (AWS Blog) — architecture overview, security model, and best practices
- Unite MCP Servers through AgentCore Gateway (AWS Blog) — consolidating MCP servers through a unified gateway
- MCP Authorization Specification (2025-03-26) — PKCE requirement, RFC 8414 discovery, Bearer header mandate
- RFC 9728 — OAuth 2.0 Protected Resource Metadata — PRM endpoint specification
- RFC 8707 — Resource Indicators for OAuth 2.0 —
resourceparameter for audience binding - RFC 7636 — PKCE — Proof Key for Code Exchange specification
- Okta Authorization Servers — Okta authorization server concepts
- Okta Native App with PKCE — Okta implementation guide for Authorization Code + PKCE
- Implement the OAuth 2.0 Authorization Code with PKCE Flow (Okta Blog) — deep dive into PKCE mechanics, code verifier/challenge exchange, and why it replaced the implicit flow
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article