<?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: Sumanth P</title>
    <description>The latest articles on DEV Community by Sumanth P (@sumanthprasad).</description>
    <link>https://dev.to/sumanthprasad</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%2F3877422%2Fb60d42b1-dff6-4699-9826-c59a613b5eb2.png</url>
      <title>DEV Community: Sumanth P</title>
      <link>https://dev.to/sumanthprasad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sumanthprasad"/>
    <language>en</language>
    <item>
      <title>Building an Agent that respects User Permissions — With AWS Bedrock AgentCore and Entra ID</title>
      <dc:creator>Sumanth P</dc:creator>
      <pubDate>Fri, 01 May 2026 18:41:10 +0000</pubDate>
      <link>https://dev.to/sumanthprasad/building-an-agent-that-respects-user-permissions-with-aws-bedrock-agentcore-and-entra-id-10f9</link>
      <guid>https://dev.to/sumanthprasad/building-an-agent-that-respects-user-permissions-with-aws-bedrock-agentcore-and-entra-id-10f9</guid>
      <description>&lt;p&gt;&lt;em&gt;A practical guide to building an AI agent that queries ServiceNow as the actual user, not a service account, using AgentCore Identity's On-Behalf-Of token exchange.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Everyone's building AI agents that talk to enterprise systems. But here's the thing most demos skip over: security.&lt;/p&gt;

&lt;p&gt;Picture this. You build an agent that helps employees interact with ServiceNow. Jane asks: &lt;em&gt;"Show me 5 incidents assigned to me."&lt;/em&gt; Your agent dutifully queries ServiceNow using a service account, filters by Jane's name, and returns results. Looks great in the demo.&lt;/p&gt;

&lt;p&gt;Except that service account can see &lt;em&gt;everything&lt;/em&gt; — HR complaints, security investigations, executive escalations. If the LLM gets creative with a query, or someone crafts a clever prompt injection, your agent could surface data Jane was never supposed to see. And when the security team checks the audit trail? All they find is "service-account-bot" made the request. Not helpful.&lt;/p&gt;

&lt;p&gt;The fix is straightforward in concept: the agent should access ServiceNow &lt;em&gt;as Jane&lt;/em&gt;, with Jane's exact permissions. If she can't see HR incidents when she logs into ServiceNow directly, the agent shouldn't see them either.&lt;/p&gt;

&lt;p&gt;That's what the On-Behalf-Of (OBO) token exchange pattern does. And AWS recently added native support for it in Bedrock AgentCore Identity.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A Strands-based AI agent running on AgentCore Runtime that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticates users through &lt;strong&gt;Microsoft Entra ID&lt;/strong&gt; (the company's SSO)&lt;/li&gt;
&lt;li&gt;Uses AgentCore Identity's &lt;strong&gt;native OBO exchange&lt;/strong&gt; to swap the user's token for a ServiceNow-scoped token&lt;/li&gt;
&lt;li&gt;Calls the &lt;strong&gt;ServiceNow REST API&lt;/strong&gt; carrying the user's delegated identity&lt;/li&gt;
&lt;li&gt;Returns only what &lt;strong&gt;ServiceNow's ACLs allow that specific user to see&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No service accounts. No broad permissions. No custom token exchange code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&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%2Fwj99pz0ynn2fbkgcx48m.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%2Fwj99pz0ynn2fbkgcx48m.png" alt=" " width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The end-to-end flow: user authenticates via Entra ID, AgentCore validates the JWT, AgentCore Identity performs the OBO exchange, and the agent calls ServiceNow with the user's delegated token.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two tokens are in play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token A&lt;/strong&gt;: Issued by Entra ID when Jane logs in. Scoped to the Agent App. Proves Jane's identity but doesn't grant access to ServiceNow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token B&lt;/strong&gt;: Issued by Entra ID through the OBO exchange. Scoped to ServiceNow. Still carries Jane's identity. This is what ServiceNow sees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent never holds a service account credential. It never sees data Jane isn't allowed to see. And ServiceNow's audit log shows &lt;code&gt;jane.smith@company.com&lt;/code&gt; made the query — not a bot.&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%2F1vzbeyjts9o5pkq381d3.gif" 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%2F1vzbeyjts9o5pkq381d3.gif" alt=" " width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How AgentCore Identity OBO Works
&lt;/h2&gt;

&lt;p&gt;Before this feature, you had two options for outbound auth in AgentCore Identity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;M2M (client credentials)&lt;/strong&gt;: The agent authenticates as itself. No user identity. ServiceNow sees "the bot."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USER_FEDERATION (3-legged OAuth)&lt;/strong&gt;: The user gets redirected to a browser to consent. Preserves identity but requires interactive login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither worked for OBO, where you need to silently exchange an existing user token for a downstream-scoped token — no browser redirect, no consent prompt.&lt;/p&gt;

&lt;p&gt;AgentCore Identity now supports a third option: &lt;strong&gt;&lt;code&gt;ON_BEHALF_OF_TOKEN_EXCHANGE&lt;/code&gt;&lt;/strong&gt;. When you configure a credential provider with &lt;code&gt;JWT_AUTHORIZATION_GRANT&lt;/code&gt;, it sends the inbound JWT as an assertion with &lt;code&gt;grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&lt;/code&gt;. That's exactly what Entra ID's OBO endpoint expects.&lt;/p&gt;

&lt;p&gt;The service handles the heavy lifting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes the inbound JWT (Token A) from the agent&lt;/li&gt;
&lt;li&gt;Combines it with the client credentials stored in the credential provider&lt;/li&gt;
&lt;li&gt;Sends the exchange request to Entra ID's token endpoint&lt;/li&gt;
&lt;li&gt;Caches the resulting Token B in the Token Vault&lt;/li&gt;
&lt;li&gt;Returns Token B to the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't touch the inbound token or manage client secrets in your code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important caveat&lt;/strong&gt;: At the time of writing, the &lt;code&gt;ON_BEHALF_OF_TOKEN_EXCHANGE&lt;/code&gt; flow is supported at the API level (&lt;code&gt;GetResourceOauth2Token&lt;/code&gt; with &lt;code&gt;oauth2Flow=ON_BEHALF_OF_TOKEN_EXCHANGE&lt;/code&gt;), but the Python SDK's &lt;code&gt;@requires_access_token&lt;/code&gt; decorator still only accepts &lt;code&gt;"M2M"&lt;/code&gt; and &lt;code&gt;"USER_FEDERATION"&lt;/code&gt; as &lt;code&gt;auth_flow&lt;/code&gt; values. Until the SDK catches up, you'll need to call the &lt;code&gt;IdentityClient&lt;/code&gt; directly or use the AWS CLI/boto3 data plane API. The code examples below show both approaches.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Microsoft Entra ID tenant&lt;/strong&gt; you can administer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A ServiceNow instance&lt;/strong&gt; with admin access to configure OAuth inbound integrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS account&lt;/strong&gt; with Bedrock model access (Claude Sonnet or Haiku)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentCore CLI&lt;/strong&gt;: &lt;code&gt;pip install bedrock-agentcore-starter-toolkit&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Part 1: Entra ID and ServiceNow Configuration
&lt;/h3&gt;

&lt;p&gt;Before touching any AWS or agent code, you need to wire up the identity chain between Entra ID and ServiceNow. This involves two Entra ID app registrations and an inbound integration in ServiceNow. (Credit to &lt;a href="https://medium.com/@greg.biegel/on-behalf-of-flow-token-exchange-with-agentcore-gateway-e54b84149551" rel="noopener noreferrer"&gt;Greg Biegel's walkthrough&lt;/a&gt; for documenting the ServiceNow side of this setup.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1a: Create the ServiceNow API Connector App Registration (Entra ID)
&lt;/h4&gt;

&lt;p&gt;This app registration represents ServiceNow as a resource in Entra ID. Its client ID becomes the &lt;code&gt;audience&lt;/code&gt; claim in Token B.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://portal.azure.com" rel="noopener noreferrer"&gt;Azure Portal&lt;/a&gt; → &lt;strong&gt;Entra ID&lt;/strong&gt; → &lt;strong&gt;App registrations&lt;/strong&gt; → &lt;strong&gt;New registration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Name it something like &lt;code&gt;ServiceNow API Connector&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;After creation, note the &lt;strong&gt;Application (client) ID&lt;/strong&gt; — this is your &lt;code&gt;API_CONNECTOR_CLIENT_ID&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Expose an API&lt;/strong&gt; → &lt;strong&gt;Set&lt;/strong&gt; the Application ID URI (e.g., &lt;code&gt;api://{API_CONNECTOR_CLIENT_ID}&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a scope&lt;/strong&gt; → name it &lt;code&gt;user_impersonation&lt;/code&gt;, set "Who can consent" to "Admins and users", and enable it&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 1b: Create the Agent App Registration (Entra ID)
&lt;/h4&gt;

&lt;p&gt;This app registration represents your AI agent. Its client ID becomes the &lt;code&gt;audience&lt;/code&gt; claim in Token A.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;App registrations&lt;/strong&gt; → &lt;strong&gt;New registration&lt;/strong&gt; → name it &lt;code&gt;ServiceNow Agent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Note the &lt;strong&gt;Application (client) ID&lt;/strong&gt; — this is your &lt;code&gt;AGENT_APP_CLIENT_ID&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Certificates &amp;amp; secrets&lt;/strong&gt; → &lt;strong&gt;New client secret&lt;/strong&gt; → save the secret value&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;API permissions&lt;/strong&gt; → &lt;strong&gt;Add a permission&lt;/strong&gt; → &lt;strong&gt;My APIs&lt;/strong&gt; → select &lt;code&gt;ServiceNow API Connector&lt;/code&gt; → check &lt;code&gt;user_impersonation&lt;/code&gt; → &lt;strong&gt;Add permissions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Grant admin consent&lt;/strong&gt; for your tenant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This authorizes the Agent App to perform OBO exchanges for the ServiceNow API Connector scope.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1c: Configure ServiceNow to Accept Entra ID Tokens
&lt;/h4&gt;

&lt;p&gt;This is the part most tutorials skip. Without it, ServiceNow won't know what to do with Token B.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create an Inbound Integration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In ServiceNow admin: &lt;strong&gt;System OAuth&lt;/strong&gt; → &lt;strong&gt;Inbound Integrations&lt;/strong&gt; → &lt;strong&gt;New&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;Client ID&lt;/strong&gt; to your &lt;code&gt;API_CONNECTOR_CLIENT_ID&lt;/code&gt; from Step 1a&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create an OIDC Provider Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose to create a new OIDC provider&lt;/li&gt;
&lt;li&gt;Set the &lt;strong&gt;OIDC Metadata URL&lt;/strong&gt; to:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid-configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map the user claim&lt;/strong&gt;: &lt;code&gt;preferred_username&lt;/code&gt; → &lt;code&gt;Email&lt;/code&gt; field in ServiceNow&lt;/li&gt;
&lt;li&gt;Uncheck &lt;strong&gt;JTI verification&lt;/strong&gt; if your Entra ID tenant doesn't include it in tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create an Auth Scope&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name it (e.g., &lt;code&gt;APIConnector&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit the scope to the Table API&lt;/strong&gt; — least privilege&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Verify user provisioning&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entra ID users need matching &lt;code&gt;sys_user&lt;/code&gt; records in ServiceNow&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;email&lt;/code&gt; field in &lt;code&gt;sys_user&lt;/code&gt; must match the &lt;code&gt;preferred_username&lt;/code&gt; claim from Entra ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Part 2: AWS AgentCore Configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 2a: Create the OAuth2 Credential Provider
&lt;/h4&gt;

&lt;p&gt;This registers Entra ID as the OAuth provider and configures the OBO exchange. The &lt;code&gt;clientId&lt;/code&gt; and &lt;code&gt;clientSecret&lt;/code&gt; come from the &lt;strong&gt;Agent App Registration&lt;/strong&gt; (Step 1b):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws bedrock-agentcore-control create-oauth2-credential-provider &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cli-input-json&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "EntraIdServiceNow",
    "credentialProviderVendor": "CustomOauth2",
    "oauth2ProviderConfigInput": {
      "customOauth2ProviderConfig": {
        "oauthDiscovery": {
          "discoveryUrl": "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0/.well-known/openid-configuration"
        },
        "clientId": "YOUR_AGENT_APP_CLIENT_ID",
        "clientSecret": "YOUR_AGENT_APP_CLIENT_SECRET",
        "onBehalfOfTokenExchangeConfig": {
          "grantType": "JWT_AUTHORIZATION_GRANT"
        }
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single command stores the client secret securely, configures the &lt;code&gt;jwt-bearer&lt;/code&gt; grant type, and sets up the Token Vault for caching.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2b: Create a Workload Identity
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws bedrock-agentcore-control create-workload-identity &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"servicenow-agent-workload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2c: Create the Agent Project
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore create &lt;span class="nt"&gt;--non-interactive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project-name&lt;/span&gt; ServiceNowAgent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt; basic &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agent-framework&lt;/span&gt; Strands &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model-provider&lt;/span&gt; Bedrock

&lt;span class="nb"&gt;cd &lt;/span&gt;ServiceNowAgent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Part 3: Write and Deploy the Agent
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Step 3a: Write the Agent Code
&lt;/h4&gt;

&lt;p&gt;Replace the generated &lt;code&gt;src/main.py&lt;/code&gt;. Since the &lt;code&gt;@requires_access_token&lt;/code&gt; decorator doesn't yet support &lt;code&gt;ON_BEHALF_OF_TOKEN_EXCHANGE&lt;/code&gt; as an &lt;code&gt;auth_flow&lt;/code&gt; value, we call the &lt;code&gt;IdentityClient&lt;/code&gt; directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
ServiceNow Agent with AgentCore Identity OBO.

Inbound:  Custom JWT Authorizer (Entra ID)
Outbound: ON_BEHALF_OF_TOKEN_EXCHANGE via IdentityClient
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextvars&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bedrock_agentcore.runtime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BedrockAgentCoreContext&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bedrock_agentcore.services.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;IdentityClient&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BedrockAgentCoreApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;SNOW_INSTANCE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SNOW_INSTANCE_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;MODEL_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-sonnet-4-20250514-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AWS_REGION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS_REGION&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-east-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Request-scoped token storage
&lt;/span&gt;&lt;span class="n"&gt;snow_token_var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContextVar&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contextvars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ContextVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;snow_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;identity_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IdentityClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AWS_REGION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_snow_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Perform OBO exchange via AgentCore Identity.

    Gets the workload access token (which wraps the inbound JWT),
    then calls GetResourceOauth2Token with ON_BEHALF_OF_TOKEN_EXCHANGE.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;workload_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BedrockAgentCoreContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_workload_access_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;workload_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No workload access token. Is inbound JWT auth configured?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;token_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;identity_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;provider_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EntraIdServiceNow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api://YOUR_API_CONNECTOR_CLIENT_ID/user_impersonation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;agent_identity_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;workload_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;auth_flow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ON_BEHALF_OF_TOKEN_EXCHANGE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;snow_token_var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# -- ServiceNow REST client --
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;snow_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snow_token_var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No ServiceNow token available.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SNOW_INSTANCE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/now/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPStatusError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token expired. Please re-authenticate.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Access denied for this operation.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ServiceNow error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;


&lt;span class="c1"&gt;# -- Tools --
&lt;/span&gt;
&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_incidents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number,short_description,priority,state,assigned_to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search for incidents in ServiceNow.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;snow_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table/incident&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sysparm_query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sysparm_limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sysparm_fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_incident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get details of a specific incident by number.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;snow_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table/incident&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sysparm_query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sysparm_limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_incident&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Update an existing incident.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;snow_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table/incident/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sys_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# -- Agent entrypoint --
&lt;/span&gt;
&lt;span class="nd"&gt;@app.entrypoint&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_snow_token&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OBO exchange failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authentication error: could not obtain ServiceNow token.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL_ID&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query_incidents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_incident&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update_incident&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a ServiceNow assistant. You help users query and manage &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;their incidents. You only see records the user is authorized to &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access. Be concise.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When the SDK adds &lt;code&gt;ON_BEHALF_OF_TOKEN_EXCHANGE&lt;/code&gt; to the decorator&lt;/strong&gt;, the &lt;code&gt;fetch_snow_token&lt;/code&gt; function collapses to five lines with &lt;code&gt;@requires_access_token&lt;/code&gt;. The API-level support is already there — it's just the Python decorator that hasn't caught up yet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Step 3b: Deploy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentcore configure &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; src/main.py &lt;span class="nt"&gt;--non-interactive&lt;/span&gt;

agentcore deploy &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;SNOW_INSTANCE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://YOUR_INSTANCE.service-now.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;MODEL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us.anthropic.claude-sonnet-4-20250514-v1:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3c: Configure Inbound JWT Authorizer
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws bedrock-agentcore update-agent-runtime &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agent-runtime-id&lt;/span&gt; YOUR_RUNTIME_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--authorizer-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{
    "customJWTAuthorizer": {
      "discoveryUrl": "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0/.well-known/openid-configuration",
      "allowedClients": ["YOUR_AGENT_APP_CLIENT_ID"],
      "allowedAudiences": ["api://YOUR_AGENT_APP_CLIENT_ID"]
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3d: Update IAM Permissions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam put-role-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; YOUR_AGENT_EXECUTION_ROLE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-name&lt;/span&gt; AgentCoreIdentityOBO &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-document&lt;/span&gt; &lt;span class="s1"&gt;'{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": [
        "bedrock-agentcore:GetResourceOauth2Token",
        "bedrock-agentcore:GetWorkloadAccessTokenForJwt",
        "secretsmanager:GetSecretValue"
      ],
      "Resource": [
        "arn:aws:bedrock-agentcore:REGION:ACCOUNT:token-vault/default/oauth2credentialprovider/EntraIdServiceNow",
        "arn:aws:bedrock-agentcore:REGION:ACCOUNT:workload-identity-directory/default/workload-identity/*",
        "arn:aws:bedrock-agentcore:REGION:ACCOUNT:workload-identity-directory/default",
        "arn:aws:bedrock-agentcore:REGION:ACCOUNT:token-vault/default",
        "arn:aws:secretsmanager:REGION:ACCOUNT:secret:bedrock-agentcore-identity!default/oauth2/EntraIdServiceNow*"
      ]
    }]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3e: Test
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Without a token — rejected before agent code runs&lt;/span&gt;
agentcore invoke &lt;span class="s1"&gt;'{"prompt": "Show me my incidents"}'&lt;/span&gt;
&lt;span class="c"&gt;# Expected: AccessDeniedException&lt;/span&gt;

&lt;span class="c"&gt;# With a valid Entra ID token&lt;/span&gt;
agentcore invoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bearer-token&lt;/span&gt; &lt;span class="s2"&gt;"YOUR_ENTRA_ID_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user-id&lt;/span&gt; &lt;span class="s2"&gt;"jane.smith@company.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"prompt": "Show me 5 incidents assigned to me"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What Jane sees:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INC0012345&lt;/td&gt;
&lt;td&gt;VPN connection dropping&lt;/td&gt;
&lt;td&gt;P2&lt;/td&gt;
&lt;td&gt;In Progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INC0012389&lt;/td&gt;
&lt;td&gt;Outlook calendar sync failure&lt;/td&gt;
&lt;td&gt;P3&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INC0012401&lt;/td&gt;
&lt;td&gt;Laptop BSOD after Windows update&lt;/td&gt;
&lt;td&gt;P2&lt;/td&gt;
&lt;td&gt;In Progress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INC0012455&lt;/td&gt;
&lt;td&gt;Shared drive permission error&lt;/td&gt;
&lt;td&gt;P3&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INC0012478&lt;/td&gt;
&lt;td&gt;Zoom plugin not loading in Teams&lt;/td&gt;
&lt;td&gt;P4&lt;/td&gt;
&lt;td&gt;Assigned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What Jane does &lt;strong&gt;not&lt;/strong&gt; see (blocked by ServiceNow ACLs):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;INC0012350&lt;/code&gt; — Employee complaint (HR category)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;INC0012399&lt;/code&gt; — SOC alert (Security category)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These records exist but are invisible to Jane. The agent never receives them — ServiceNow filters before the response leaves the instance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tracing the Request
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hop 1: Jane → AgentCore Runtime&lt;/strong&gt;&lt;br&gt;
The Chat UI attaches Jane's Entra ID token (Token A) as a Bearer header. AgentCore's Custom JWT Authorizer validates it against Entra ID's JWKS endpoint. Invalid tokens are rejected before any agent code runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hop 2: Agent → AgentCore Identity → Entra ID&lt;/strong&gt;&lt;br&gt;
The agent calls &lt;code&gt;IdentityClient.get_token()&lt;/code&gt; with &lt;code&gt;auth_flow="ON_BEHALF_OF_TOKEN_EXCHANGE"&lt;/code&gt;. AgentCore Identity takes Token A, combines it with the stored client credentials, and sends a &lt;code&gt;jwt-bearer&lt;/code&gt; grant to Entra ID. Entra ID returns Token B — same user, different audience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hop 3: Agent → ServiceNow&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;query_incidents&lt;/code&gt; tool calls ServiceNow's REST API with &lt;code&gt;Authorization: Bearer &amp;lt;Token B&amp;gt;&lt;/code&gt;. ServiceNow decodes Token B, matches &lt;code&gt;jane.smith@company.com&lt;/code&gt; to a &lt;code&gt;sys_user&lt;/code&gt; record, and applies her ACLs. Only her authorized incidents come back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hop 4: Back to Jane&lt;/strong&gt;&lt;br&gt;
The LLM formats the results. Jane sees her 5 incidents. The restricted ones never left ServiceNow.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Replaces
&lt;/h2&gt;

&lt;p&gt;Before native OBO support, implementing this pattern meant writing roughly 200 lines of custom code: a Starlette middleware to intercept requests, a function to POST to Entra ID's token endpoint, an in-memory cache with TTL management, Secrets Manager integration for the client secret, and JWT parsing for cache keying.&lt;/p&gt;

&lt;p&gt;With native OBO, the credential provider configuration handles the protocol and secrets. The agent code just calls &lt;code&gt;get_token()&lt;/code&gt; with the right flow type. When the SDK decorator catches up, it'll be even simpler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Else This Works
&lt;/h2&gt;

&lt;p&gt;OBO isn't specific to ServiceNow. Any downstream API that accepts delegated user tokens works the same way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Graph&lt;/strong&gt; — access email, calendar, or OneDrive as the user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salesforce&lt;/strong&gt; — query CRM records with the user's permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal APIs&lt;/strong&gt; — anything behind your Entra ID tenant with JWT auth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-hop chains&lt;/strong&gt; — Agent A calls Agent B calls ServiceNow, all carrying the original user's identity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The requirement is that the downstream service accepts tokens from your IdP and enforces authorization based on the subject claim.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building AI agents that access enterprise data is the easy part. Making sure they respect the same access controls as the humans they serve — that's where it gets interesting.&lt;/p&gt;

&lt;p&gt;AgentCore Identity's OBO support takes most of the plumbing off your plate. You configure a credential provider, call the API in your agent, and the platform handles the token exchange and caching. Your agent code stays focused on what matters: helping people get things done.&lt;/p&gt;

&lt;p&gt;If you're building agents that touch user-specific data in enterprise systems, bake this pattern in from the start. Retrofitting security after the fact is never fun.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;References:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/on-behalf-of-token-exchange.html" rel="noopener noreferrer"&gt;On-behalf-of token exchange with AgentCore Identity&lt;/a&gt; — AWS documentation for the OBO feature&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@greg.biegel/on-behalf-of-flow-token-exchange-with-agentcore-gateway-e54b84149551" rel="noopener noreferrer"&gt;On-behalf-of flow token exchange with AgentCore Gateway&lt;/a&gt; — Greg Biegel's walkthrough covering the ServiceNow inbound integration setup&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/awslabs/amazon-bedrock-agentcore-samples/tree/main/01-tutorials/03-AgentCore-identity/08-IDP-examples/EntraID" rel="noopener noreferrer"&gt;AgentCore Identity samples — EntraID examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.github.io/bedrock-agentcore-starter-toolkit/api-reference/identity.md" rel="noopener noreferrer"&gt;AgentCore Identity SDK reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>aws</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
