<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: M Isaac</title>
    <description>The latest articles on DEV Community by M Isaac (@misaac).</description>
    <link>https://dev.to/misaac</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F703118%2F7eca785b-23ae-44ce-babb-3377b8d3c340.png</url>
      <title>DEV Community: M Isaac</title>
      <link>https://dev.to/misaac</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/misaac"/>
    <language>en</language>
    <item>
      <title>Secure GitHub or any MCP Server with Okta via AgentCore Gateway</title>
      <dc:creator>M Isaac</dc:creator>
      <pubDate>Mon, 01 Jun 2026 04:32:45 +0000</pubDate>
      <link>https://dev.to/aws-builders/secure-github-or-any-mcp-server-with-okta-via-agentcore-gateway-4ci6</link>
      <guid>https://dev.to/aws-builders/secure-github-or-any-mcp-server-with-okta-via-agentcore-gateway-4ci6</guid>
      <description>&lt;p&gt;I've spent much of this year thinking about how to help teams adopt AI. MCPs have become a big part of that journey because all of a sudden, it seems like every company, even those who previously refused to expose an API, now has an MCP. &lt;/p&gt;

&lt;p&gt;While this is great for end users, you need a strategy for protecting the various MCPs in use. One way to achieve this is to deploy a &lt;a href="https://builder.aws.com/content/3CSDSWZNYyNtDxLbaV5YUgMnXb0/long-live-mcp-the-dev-summit-recap#:~:text=Gateways%20and%20registries%20became%20the%20architectural%20consensus" rel="noopener noreferrer"&gt;centralized gateway&lt;/a&gt; where you can apply governance controls.  As a bonus, your developers get to add just one MCP server config, and all tools from target MCPs, GitHub, Jira, Linear, Notion, you name it, will be accessible via the unified MCP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/machine-learning/transform-your-mcp-architecture-unite-mcp-servers-through-agentcore-gateway/" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Gateway&lt;/a&gt; can help you do this. It sits in front of your MCP servers and presents MCP clients with a single endpoint where security teams can enforce organizational policy. Here's what that might look like:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/pCuqpFUS1sw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If that looks like your end goal, lets start with some theory first!&lt;/p&gt;

&lt;h2&gt;
  
  
  Client ID Metadata Documents - CIMD
&lt;/h2&gt;

&lt;p&gt;Before &lt;a href="https://client.dev/" rel="noopener noreferrer"&gt;CIMD&lt;/a&gt;, the original auth story for MCP revolved around Dynamic Client Registration. The idea was that you could let MCP clients like Claude Code or VS Code automatically register themselves with the authorization server that protects an MCP server. This was always a non-starter for most enterprises because it flew in the face of security to just let random clients register themselves with your authorization server. &lt;/p&gt;

&lt;p&gt;Enter CIMD. CIMD's core innovation is to let OAuth clients identify themselves using a URL. A client like VS Code or Claude Code hosts a JSON metadata document at a well-known URL and that URL &lt;strong&gt;doubles as&lt;/strong&gt; the client ID. Here's VS Code's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visual Studio Code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logo_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://code.visualstudio.com/assets/branding/code-stable.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"grant_types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"authorization_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"urn:ietf:params:oauth:grant-type:device_code"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response_types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"code"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint_auth_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"application_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"native"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://vscode.dev/oauth/client-metadata.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://vscode.dev/product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"redirect_uris"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:33418/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://vscode.dev/redirect"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a id="mcp-server-setup"&gt;&lt;/a&gt;In your IDE or coding agent, you'd set up the MCP server as below. Note that setting &lt;code&gt;MCP-Protocol-Version&lt;/code&gt; to &lt;code&gt;2025-11-25&lt;/code&gt; is required, as that's the MCP spec version that introduced CIMD.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example-mcp-server.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oauth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"clientId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://vscode.dev/oauth/client-metadata.json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"MCP-Protocol-Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-25"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When your IDE or coding agent attempts to authenticate with the MCP, the MCP's  authorization server fetches the client's metadata document using the url-shaped client id, and validates that the &lt;code&gt;client_id&lt;/code&gt; in the metadata document matches the presented URL. There are a bunch of other &lt;a href="https://client.dev/servers" rel="noopener noreferrer"&gt;recommended validation steps&lt;/a&gt;, but once completed, the authorization server can then use the values in the metadata document in its authorization decisions. &lt;/p&gt;

&lt;p&gt;Unfortunately, as of May 2026, Okta authorization servers do not support CIMD. &lt;/p&gt;

&lt;h2&gt;
  
  
  The OAuth proxy
&lt;/h2&gt;

&lt;p&gt;To workaround this, we'll need a proxy. Instead of registering the CIMD clients in Okta, we create an Okta &lt;a href="https://developer.okta.com/docs/guides/configure-native-sso/main/" rel="noopener noreferrer"&gt;native&lt;/a&gt; client. Our proxy maps any allowlisted CIMD MCP clients onto this client and performs the Okta authorization flow. For this to work with the gateway, we'll register the Okta native app client ID — not the CIMD URL — in AgentCore Gateway's &lt;code&gt;allowedClients&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;Our proxy does more than just substitute the Okta client. It performs the recommended CIMD validation steps hinted at earlier. For instance, it verifies that the requested &lt;code&gt;redirect_uri&lt;/code&gt; is well-formed and matches an entry in the metadata document's &lt;code&gt;redirect_uris&lt;/code&gt; list. &lt;/p&gt;

&lt;h3&gt;
  
  
  Session binding
&lt;/h3&gt;

&lt;p&gt;When a user calls a GitHub tool, the call flows through our proxy to AgentCore Gateway. If a valid token already exists for this user, the gateway uses it immediately. If not, &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html" rel="noopener noreferrer"&gt;AgentCore Identity&lt;/a&gt; returns an authorization URL and a session URI, and AgentCore Gateway surfaces them as a &lt;code&gt;-32042&lt;/code&gt; URL &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation" rel="noopener noreferrer"&gt;elicitation&lt;/a&gt;. The proxy intercepts it, extracting the session URI and storing it in DynamoDB alongside the initiating user’s Okta subject. Then it forwards the elicitation to the MCP client. It does all this to support &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/oauth2-authorization-url-session-binding.html" rel="noopener noreferrer"&gt;session binding&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Session binding ensures that the user who initiated the tool call that needs authorization is the same user who granted consent. Without it, if the initiating user forwards the authorization URL and someone else completes the GitHub consent flow, their GitHub access token would get stored in the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/key-features-and-benefits.html#secure-credential-storage" rel="noopener noreferrer"&gt;token vault&lt;/a&gt; under the initiating user’s identity.&lt;/p&gt;

&lt;p&gt;To prevent this, AWS provides us with a regular OAuth callback URL which we register with GitHub so GitHub can deliver the authorization code to AgentCore Identity. After AgentCore Identity receives that code, it doesn't immediately use it to obtain an access token. Instead, it redirects the user’s browser to a callback URL, &lt;code&gt;/sessionbinding/callback&lt;/code&gt;, served by our proxy.&lt;/p&gt;

&lt;p&gt;Here the proxy looks up the stored session binding and immediately redirects to Okta with &lt;code&gt;prompt=none&lt;/code&gt;. Okta uses the existing browser session to issue an authorization code without showing a login prompt, then redirects to &lt;code&gt;/sessionbinding/okta-callback&lt;/code&gt; on our proxy.&lt;/p&gt;

&lt;p&gt;At &lt;code&gt;/sessionbinding/okta-callback&lt;/code&gt;, the proxy exchanges that code for an ID token, extracts the &lt;code&gt;sub&lt;/code&gt; claim, and compares it against the stored initiating user’s subject. Only if they match does it call &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/APIReference/API_CompleteResourceTokenAuth.html" rel="noopener noreferrer"&gt;&lt;code&gt;CompleteResourceTokenAuth&lt;/code&gt;&lt;/a&gt; which signals to AgentCore that its safe to get an access token for the user, i.e &lt;strong&gt;&lt;em&gt;bind the session&lt;/em&gt;&lt;/strong&gt; . Once the binding succeeds, AgentCore Identity caches the GitHub access token for that user in the token vault, and subsequent tool calls complete normally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps5b3pwhbo2fengz2d2f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps5b3pwhbo2fengz2d2f.png" alt="Sequence Diagram for CIMD Oauth proxy" width="800" height="961"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The proxy contract
&lt;/h3&gt;

&lt;p&gt;Since the proxy handles &lt;a href="https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#use-pkce-to-make-your-apps-more-secure" rel="noopener noreferrer"&gt;PKCE&lt;/a&gt;, &lt;a href="https://www.okta.com/identity-101/csrf-attack/" rel="noopener noreferrer"&gt;CSRF defense&lt;/a&gt;, &lt;a href="https://developer.okta.com/docs/concepts/token-lifecycles/" rel="noopener noreferrer"&gt;token exchange&lt;/a&gt;, and &lt;a href="https://developer.okta.com/docs/guides/validate-id-tokens/main/" rel="noopener noreferrer"&gt;identity verification&lt;/a&gt;, it is critical that you understand what each line of code does and why. So instead of providing a full copy-paste proxy implementation, the sequence diagram above coupled with the table below outlines what each endpoint must do. I've included links to documentation and reference implementations for specific parts to help you build your own.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/.well-known/oauth-protected-resource&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9728" rel="noopener noreferrer"&gt;Tells&lt;/a&gt; MCP clients that the proxy &lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/v1.27.2/src/mcp/server/auth/routes.py#L209-L253" rel="noopener noreferrer"&gt;is the authorization server&lt;/a&gt; for our gateway.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/.well-known/oauth-authorization-server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc8414" rel="noopener noreferrer"&gt;Publishes&lt;/a&gt; the proxy's authorization server capabilities, &lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/v1.27.2/src/mcp/server/auth/routes.py#L69-L149" rel="noopener noreferrer"&gt;the authorize and token endpoints&lt;/a&gt;, so MCP clients know how to authenticate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/authorize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/PrefectHQ/fastmcp/blob/1a06130fcfaece1d494bf444c1561e752d94c61a/fastmcp_slim/fastmcp/server/auth/cimd.py#L1" rel="noopener noreferrer"&gt;Validates&lt;/a&gt; the presented &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;redirect_uri&lt;/code&gt; against the metadata document, then &lt;a href="https://github.com/awslabs/agentcore-samples/blob/main/06-workshops/02-AgentCore-gateway/04-integration/03-ide-gateway-tool/lambda/mcp_proxy_lambda.py#L88-L120" rel="noopener noreferrer"&gt;redirects to Okta substituting&lt;/a&gt; the Okta client ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/callback&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/awslabs/agentcore-samples/blob/main/06-workshops/02-AgentCore-gateway/04-integration/03-ide-gateway-tool/lambda/mcp_proxy_lambda.py#L123-L160" rel="noopener noreferrer"&gt;Receives&lt;/a&gt; Okta's authorization code, verifies the state HMAC and re-validates the CIMD &lt;code&gt;redirect_uri&lt;/code&gt;, then forwards the code to the MCP client's original redirect URI.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/awslabs/agentcore-samples/blob/main/06-workshops/02-AgentCore-gateway/04-integration/03-ide-gateway-tool/lambda/mcp_proxy_lambda.py#L163-L194" rel="noopener noreferrer"&gt;Proxies&lt;/a&gt; token requests to Okta after validating CIMD client identity, substituting the Okta client ID, and rewriting &lt;code&gt;redirect_uri&lt;/code&gt; to match what &lt;code&gt;/authorize&lt;/code&gt; used.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/sessionbinding/callback&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/awslabs/agentcore-samples/blob/main/06-workshops/02-AgentCore-gateway/04-integration/03-ide-gateway-tool/lambda/callback_lambda.py" rel="noopener noreferrer"&gt;Receives&lt;/a&gt; AgentCore Identity's 3LO return redirect, looks up the session binding, then redirects to Okta with &lt;code&gt;prompt=none&lt;/code&gt; to silently identify the current browser user.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/sessionbinding/okta-callback&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://developer.okta.com/docs/guides/implement-grant-type/authcodepkce/main/#exchange-the-code-for-tokens" rel="noopener noreferrer"&gt;Exchanges&lt;/a&gt; the code for an ID token, compares the current user against the stored initiating user, and calls &lt;a href="https://github.com/awslabs/agentcore-samples/blob/main/06-workshops/02-AgentCore-gateway/04-integration/03-ide-gateway-tool/lambda/callback_lambda.py#L56" rel="noopener noreferrer"&gt;&lt;code&gt;CompleteResourceTokenAuth&lt;/code&gt;&lt;/a&gt; if they match.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/{full_path:path}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/awslabs/agentcore-samples/blob/main/06-workshops/02-AgentCore-gateway/04-integration/03-ide-gateway-tool/lambda/mcp_proxy_lambda.py#L211-L284" rel="noopener noreferrer"&gt;Proxies&lt;/a&gt; MCP traffic to AgentCore Gateway. On a &lt;code&gt;-32042&lt;/code&gt; response, extracts the &lt;code&gt;session_uri&lt;/code&gt; from the elicitation URL and stores the initiating user's identity in DynamoDB so &lt;code&gt;/sessionbinding/okta-callback&lt;/code&gt; can complete the binding.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/register&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rejects &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;Dynamic Client Registration&lt;/a&gt; requests with a 400 since only CIMD clients are trusted.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you've coded your proxy and have it deployed, hooked up to DynamoDB, and ready to receive requests, we can proceed with setting up Okta. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Okta
&lt;/h2&gt;

&lt;p&gt;Every Okta org comes with a built-in org &lt;a href="https://developer.okta.com/docs/concepts/auth-servers/" rel="noopener noreferrer"&gt;authorization server&lt;/a&gt;. &lt;strong&gt;Do not&lt;/strong&gt; use this for your AgentCore Gateway as you cannot customize its audience, claims, policies, or scopes. Instead, set up a custom authorization server. &lt;/p&gt;

&lt;p&gt;If you don't have an Okta org to play with, or are nervous about experimenting in your prod org, you can set one up using their &lt;a href="https://developer.okta.com/docs/reference/org-defaults/" rel="noopener noreferrer"&gt;Integrator Free Plan&lt;/a&gt; orgs option. It is limited to 10 users but that should be more than enough for you and a few beta-testers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up an Authorization Server
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Security&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add Authorization Server&lt;/strong&gt;. Enter a name, desired audience, and a description&lt;/li&gt;
&lt;li&gt;Open the authorization server you just created, click the &lt;strong&gt;Claims&lt;/strong&gt; tab, and choose &lt;strong&gt;Add Claim&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name the new claim &lt;code&gt;client_id&lt;/code&gt; and set its value to &lt;code&gt;app.clientId&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Include in token type&lt;/strong&gt; to &lt;strong&gt;Access Token&lt;/strong&gt; and choose &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Follow the same steps to add a &lt;code&gt;scopes&lt;/code&gt; claim and set its value to &lt;code&gt;app.scopes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Scopes&lt;/strong&gt; tab and add any custom scopes you need&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Access Policies&lt;/strong&gt; tab and create an &lt;a href="https://developer.okta.com/docs/guides/configure-access-policy/main/" rel="noopener noreferrer"&gt;access policy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;All clients&lt;/strong&gt; in the &lt;strong&gt;Assign to&lt;/strong&gt; field. You can always modify it to specific clients later&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Rule&lt;/strong&gt; on the policy you just created. Without a rule, Okta will reject all authorization requests against this server. &lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Authorization Code&lt;/strong&gt; as a grant type, leave user and scope conditions as &lt;strong&gt;Any&lt;/strong&gt;, and &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If everything went well, you should see a JSON object that describes your authorization server's capabilities at &lt;a href="https://your-tenant.okta.com/oauth2/your-authorization-server-name/.well-known/openid-configuration" rel="noopener noreferrer"&gt;https://your-tenant.okta.com/oauth2/your-authorization-server-name/.well-known/openid-configuration&lt;/a&gt;. We'll use this URL later when setting up the gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up an Okta client
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;From the Okta developer console left navigation pane, click &lt;strong&gt;Applications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Create App Integration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;OIDC - OpenID Connect&lt;/strong&gt; and then &lt;strong&gt;Native Application&lt;/strong&gt; as your application type&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Sign-in redirect URIs&lt;/strong&gt;, click &lt;strong&gt;Add URI&lt;/strong&gt; twice and add:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://&amp;lt;your-proxy-domain&amp;gt;/callback&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://&amp;lt;your-proxy-domain&amp;gt;/sessionbinding/okta-callback&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Assignments&lt;/strong&gt; select &lt;strong&gt;Allow everyone in your organization to access&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;Enable immediate access with Federation Broker Mode&lt;/strong&gt; checked. You can always return and modify these later&lt;/li&gt;
&lt;li&gt;Hit &lt;strong&gt;Save&lt;/strong&gt; and copy the generated client ID and use it as the proxy's static okta client id&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up AgentCore Gateway
&lt;/h2&gt;

&lt;p&gt;Before we can fully setup AgentCore Gateway, we need a &lt;a href="https://docs.github.com/en/apps/oauth-apps/using-oauth-apps" rel="noopener noreferrer"&gt;GitHub OAuth&lt;/a&gt; app which will provide us with a client ID and secret for use in setting up AgentCore Gateway's &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-outbound-auth.html" rel="noopener noreferrer"&gt;Outbound auth&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up GitHub
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://github.com/settings/apps" rel="noopener noreferrer"&gt;https://github.com/settings/apps&lt;/a&gt; and select &lt;strong&gt;New GitHub App&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Fill in details, note that the GitHub App name must be unique&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Authorization callback URL&lt;/strong&gt;, enter a temporary value like &lt;code&gt;https://bedrock-agentcore.us-east-1.amazonaws.com/oauth/callback&lt;/code&gt;. We will replace it with the generated AgentCore callback URL after creating the outbound identity.&lt;/li&gt;
&lt;li&gt;Uncheck all the checkboxes&lt;/li&gt;
&lt;li&gt;Leave user permissions as default but feel free to return and add more as your use-case requires&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Any account&lt;/strong&gt; under &lt;strong&gt;Where can this GitHub App be installed?&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create GitHub App&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You should see a "Registration successful. You must generate a private key in order to install your GitHub App." message. Follow the link to do so&lt;/li&gt;
&lt;li&gt;Go to the Client Secrets section and click &lt;strong&gt;Generate a new client secret&lt;/strong&gt;. Save the value as you won't see it again&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Set up AgentCore GitHub outbound identity
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign in to your AWS account and go to the Amazon Bedrock AgentCore page&lt;/li&gt;
&lt;li&gt;From the console left navigation pane, click &lt;strong&gt;Identity&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Add Outbound Auth&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add OAuth client&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Change the name to something you'll remember or make a note of the autogenerated one&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Included provider&lt;/strong&gt; and then select &lt;strong&gt;GitHub&lt;/strong&gt; from the drop-down&lt;/li&gt;
&lt;li&gt;Enter the client ID of the GitHub App we created above
&lt;/li&gt;
&lt;li&gt;Similarly, enter the client secret you saved in step 9 above&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Copy the outbound identity's callback URL and return to the GitHub OAuth app&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add Callback URL&lt;/strong&gt; and add the copied value. Hit &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create the AgentCore Gateway
&lt;/h3&gt;

&lt;p&gt;Certain features of AgentCore Gateway can only be enabled during creation. These include the MCP protocol version, encryption settings, debug mode, amongst others. Perhaps the most important is  &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html#:~:text=Semantic%20Tool%20Selection" rel="noopener noreferrer"&gt;semantic search&lt;/a&gt;, which routes tool calls to the right target when similar tool names exist across multiple MCP servers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Amazon Bedrock AgentCore page&lt;/li&gt;
&lt;li&gt;From the console left navigation pane, click &lt;strong&gt;Gateways&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Create Gateway&lt;/strong&gt; and name your gateway&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Additional configurations - optional&lt;/strong&gt; drop down&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Enable Semantic Search&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Enable response streaming&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;2025-11-25&lt;/code&gt; under supported versions and click &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;exception level&lt;/code&gt; is enabled by default but not recommended for production, so be sure to disable it if you're creating your production gateway.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Set up the Gateway IdP configs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Select &lt;strong&gt;Use existing identity provider configurations&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enter the &lt;code&gt;.well-known/openid-configuration&lt;/code&gt; URL from the authorization server section above in the &lt;strong&gt;Discovery URL&lt;/strong&gt; field&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;JWT Authorization Configuration&lt;/strong&gt; enter the client ID from the Okta client section above&lt;/li&gt;
&lt;li&gt;Leave the rest of the settings as default and hit &lt;strong&gt;Next&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Add the GitHub MCP Server as an AgentCore Gateway Target
&lt;/h3&gt;

&lt;p&gt;When using the UI to create the gateway, AWS requires you to set up at least one MCP target. In our case, this will be the GitHub MCP. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add 'github' to the autogenerated target name for easier identification&lt;/li&gt;
&lt;li&gt;Under MCP endpoint, enter &lt;a href="https://api.githubcopilot.com/mcp/" rel="noopener noreferrer"&gt;https://api.githubcopilot.com/mcp/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;OAuth client&lt;/strong&gt; under &lt;strong&gt;Outbound Auth configurations&lt;/strong&gt; and choose the outbound identity created in the outbound identity section above&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Additional configurations - optional&lt;/strong&gt; drop down and select &lt;strong&gt;Authorization code grant (3LO)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Return URL&lt;/strong&gt;, enter the proxy session binding URL, &lt;code&gt;https://&amp;lt;your-proxy-domain&amp;gt;/sessionbinding/callback&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;scopes&lt;/strong&gt;, add 3 scopes for &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;repo&lt;/code&gt;, and &lt;code&gt;workflow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Next&lt;/strong&gt;, then &lt;strong&gt;Create Gateway&lt;/strong&gt;. After the gateway is created, you'll be taken to its details page&lt;/li&gt;
&lt;li&gt;You should also see a yellow banner that prompts you to authorize the GitHub MCP target. Click &lt;strong&gt;Authorize&lt;/strong&gt; to do so. &lt;/li&gt;
&lt;li&gt;Wait for the authorization flow to complete. It can take anywhere from seconds to a few minutes&lt;/li&gt;
&lt;li&gt;On completion, when you scroll down to the target list, the GitHub MCP target status should show  &lt;strong&gt;Ready&lt;/strong&gt; and the &lt;strong&gt;Authorization status&lt;/strong&gt; pane should say &lt;strong&gt;No authorization required&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Authorizing here is an admin-level step that verifies your GitHub OAuth app is correctly configured. Each end user will still complete their own GitHub authorization the first time they invoke a tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect your MCP client
&lt;/h2&gt;

&lt;p&gt;To test the Gateway, you'll need to use a client that supports CIMD. That &lt;a href="https://github.com/modelcontextprotocol/inspector/issues/1150" rel="noopener noreferrer"&gt;rules out&lt;/a&gt; the MCP Inspector. Fortunately, both VS Code and Claude Code do, hosting their CIMDs &lt;a href="https://vscode.dev/oauth/client-metadata.json" rel="noopener noreferrer"&gt;here&lt;/a&gt; and &lt;a href="https://claude.ai/oauth/claude-code-client-metadata" rel="noopener noreferrer"&gt;here&lt;/a&gt; respectively. Let's use VS Code.&lt;/p&gt;

&lt;p&gt;You might want to use a different GitHub account from the one with which you completed the admin authorization. The reason is, as we alluded to earlier, if we make a tool call and the Gateway already has a saved token for the gateway/user pair, we might not see the full initial setup, including the elicitation. So use a different GitHub profile and accompanying Okta account to complete the steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an &lt;code&gt;mcp.json&lt;/code&gt; at &lt;code&gt;.vscode/mcp.json&lt;/code&gt; in an active VS Code workspace &lt;/li&gt;
&lt;li&gt;Set it up as in the example shown earlier, replacing the url with your proxy's url&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Start&lt;/strong&gt; and complete the OAuth flow&lt;/li&gt;
&lt;li&gt;Once VS Code completes initialization and caches the tools advertised by AgentCore Gateway, make a tool call. A good candidate would be &lt;code&gt;get_my_user_profile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You should see the elicitation response pop up. Follow the prompts to grant consent&lt;/li&gt;
&lt;li&gt;Once consent is completed, make the tool call again&lt;/li&gt;
&lt;li&gt;Profit&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;You now have a single MCP gateway endpoint protected by Okta and your users can use it to call downstream MCP servers like GitHub's. From here, adding other MCP server targets would be a natural next step. As you do this, consider whether your organization might benefit from coupling AgentCore Gateway with &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/registry.html" rel="noopener noreferrer"&gt;AgentCore Agent Registry&lt;/a&gt; to provide your end users with a one stop discovery shop for all things AI - MCPs, skills, and even Agents. &lt;/p&gt;

&lt;p&gt;Or you might want to stay on the security train and invest in some &lt;a href="https://builder.aws.com/content/3AEiRsxX7l9ZLq0FeC0TZwR82LR/zero-trust-ai-tool-access-how-claude-code-authenticates-to-agentcore-mcp-servers-with-okta#:~:text=Recommended%20policy%20rules" rel="noopener noreferrer"&gt;hardening&lt;/a&gt; with Okta Authentication Policies or &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/policy.html#:~:text=With%20Policy%20in%20AgentCore%2C%20developers,engine%20before%20allowing%20tool%20access." rel="noopener noreferrer"&gt;AgentCore Policies&lt;/a&gt;. Whatever you do, remember the field moves fast so make sure you're keeping up with new releases. Who knows, maybe sometime soon, we won't need a proxy anymore and AgentCore Gateway will ship with a full-on CIMD compatible authorization server.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
