{
    "componentChunkName": "component---src-pages-blog-markdown-remark-fields-slug-js",
    "path": "/blog/auth-for-ai-agents",
    "result": {"data":{"markdownRemark":{"html":"<p>In February 2026, Meta alignment director <a href=\"https://www.businessinsider.com/meta-ai-alignment-director-openclaw-email-deletion-2026-2\" target=\"_blank\" rel=\"nofollow\">Summer Yue</a> connected an OpenClaw agent to her email, and gave it one instruction: suggest what to archive or delete, but take no action without approval. For weeks, the test runs were clean.</p>\n<p>Then she pointed it at her real inbox. During that run, a routine step known as <code class=\"language-text\">context-window compaction</code> that summarizes older context to free up tokens, kicked in and quietly summarized the safety instruction away. Without that constraint in place, the agent started deleting emails, and by the time Yue noticed, it had already removed over 200. She typed “STOP OPENCLAW.” But the agent simply acknowledged the message and kept deleting anyway. So she ran to her Mac Mini and physically killed the process.</p>\n<p>Nothing was compromised. No token stolen, no prompt injection, no credential leak. Authentication succeeded. API-level authorization succeeded. The identity layer had no mechanism to intervene, because there was no third check. That gap, and the architecture that closes it, is what this post covers.</p>\n<h2 id=\"why-classic-oauth-and-session-authentication-break-for-ai-agents\" style=\"position:relative;\"><a href=\"#why-classic-oauth-and-session-authentication-break-for-ai-agents\" aria-label=\"why classic oauth and session authentication break for ai agents permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Why Classic OAuth and Session Authentication Break for AI Agents</h2>\n<p><a href=\"https://supertokens.com/blog/openid-connect-vs-oauth2\" target=\"_blank\" rel=\"nofollow\">OAuth</a> was designed around a present human. A user clicks approve, a token is issued, an application spends it while the user waits. AI agent authentication breaks three of those assumptions.</p>\n<ul>\n<li><strong>Agents act after the session ends.</strong> A user kicks off a task and closes the laptop. Session tokens representing “this human is here right now” are spent by a process running autonomously for the next forty minutes.</li>\n<li><strong>Agents chain tool calls without pausing.</strong> A single request fans out into a dozen downstream calls. A compromise at step three propagates through every subsequent step with no human checkpoint.</li>\n<li><strong>Agents lose their instructions.</strong> This is what the Yue incident demonstrates. A conventional application does not forget its access control logic mid-request. An agent whose constraint lives in the prompt can lose it to routine compaction, not an attack, just the system working as designed.</li>\n</ul>\n<p>The architectural consequence: any constraint that matters must live outside the agent’s context window. Scopes in a signed token and policy evaluated at a tool server survive compaction, prompt injection, and model rationalization, because the agent does not get a vote.</p>\n<h2 id=\"the-four-actors-in-an-agentic-authentication-system\" style=\"position:relative;\"><a href=\"#the-four-actors-in-an-agentic-authentication-system\" aria-label=\"the four actors in an agentic authentication system permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The Four Actors in an Agentic Authentication System</h2>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/b93e291bd8e8715804c1b33ffb4f3e3a/9c701/the-four-actors.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABj0lEQVQoz2WRW2sTYRCG9///F+/FC0WISTHB1mRrEiuWtN3G3STf+TyHr+xKkeLDMAPzMu/FvA1P1MoAcDpdhFBKGSHU+SyMsc55pYyUyhgrpZZSG+P4lWZsRLXWGNOX2bJtd6vV+ubmdj5ftbf7H9u7zWZ7dfVtvli17W42W15ft/+OATlkjoUzcK2VeazJbYIrY2WolSZpnDUC+FIKUZMKZeT3a2g7TIViIu9p+dU/P496DmgG2C1UUJQCZsDfSr/b/3QACbHJQLHw513ZHykkOIswDHm99odDkqIYCfJY7lbKDGB1HrTfni+LruusTQBNQbYRns65lyUkDBH7Pj516XiMxpYUUA2xPzh1SimiDLHT5kHKozYZsSnAQgepnVDWhVwKC2G19kK6EEsKoIQb62xLBBui0EY7f1E6FWiImKbXAbFPlAulTIiMwMEiZILClcYn2ZyBiJjfREU05qw9ftr4hyH/6lIobEX5/lGcHtPwGCvWdd9/uL//41wEqG9yfgUn379x/Q8x09vNC/AVOdGOmfrRAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"the four actors\"\n        title=\"the four actors\"\n        src=\"/static/b93e291bd8e8715804c1b33ffb4f3e3a/f058b/the-four-actors.png\"\n        srcset=\"/static/b93e291bd8e8715804c1b33ffb4f3e3a/c26ae/the-four-actors.png 158w,\n/static/b93e291bd8e8715804c1b33ffb4f3e3a/6bdcf/the-four-actors.png 315w,\n/static/b93e291bd8e8715804c1b33ffb4f3e3a/f058b/the-four-actors.png 630w,\n/static/b93e291bd8e8715804c1b33ffb4f3e3a/40601/the-four-actors.png 945w,\n/static/b93e291bd8e8715804c1b33ffb4f3e3a/78612/the-four-actors.png 1260w,\n/static/b93e291bd8e8715804c1b33ffb4f3e3a/9c701/the-four-actors.png 1774w\"\n        sizes=\"(max-width: 630px) 100vw, 630px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p>Every agentic system has four distinct principals, and every boundary between them is a place where identity must be re-established rather than assumed.</p>\n<table>\n<thead>\n<tr>\n<th>Actor</th>\n<th>Identity</th>\n<th>Responsibility</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>End user</td>\n<td>Human, authenticated via OIDC</td>\n<td>Initiates the task, grants consent</td>\n</tr>\n<tr>\n<td>Application backend</td>\n<td>Service identity</td>\n<td>Authenticates the user, spawns the agent, mints agent tokens</td>\n</tr>\n<tr>\n<td>Agent runtime</td>\n<td>Agent identity, derived per task</td>\n<td>Plans and executes steps, requests capability tokens</td>\n</tr>\n<tr>\n<td>Tool or resource server</td>\n<td>Service identity</td>\n<td>Executes actions, enforces scope and policy</td>\n</tr>\n</tbody>\n</table>\n<p>The agent runtime is most often modeled wrong. Handing the agent the user’s own token makes every action indistinguishable from the user’s in logs. Giving it a long-lived service account detaches it from user consent. The correct model gives the agent its own agentic identity, derived from the user’s session at task start, scoped to that task, and expiring with it. Every token should answer: which user authorized this, which agent is acting, and which task is it acting for.</p>\n<h2 id=\"scoped-tokens-and-the-narrow-never-widen-rule\" style=\"position:relative;\"><a href=\"#scoped-tokens-and-the-narrow-never-widen-rule\" aria-label=\"scoped tokens and the narrow never widen rule permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Scoped Tokens and the “Narrow, Never Widen” Rule</h2>\n<p>Each hop in the token chain is an RFC 8693 token exchange. A user token carrying <code class=\"language-text\">repo:read</code> and <code class=\"language-text\">repo:write</code> exchanges for a narrower agent token carrying <code class=\"language-text\">repo:read</code>. That agent token exchanges for an even narrower per-tool capability token carrying a single action. At no exchange can a token acquire a permission its parent lacked.</p>\n<p>The rule needs to be code, not documentation:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"75682582086658020000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`def narrow(parent_scopes, requested_scopes):\n   &quot;&quot;&quot;Return only requested scopes the parent actually holds. An attempt to\n   widen is a signal worth failing on, not silently dropping.&quot;&quot;&quot;\n   parent = set(parent_scopes)\n   requested = set(requested_scopes)\n   escalation = requested - parent\n   if escalation:\n       raise PermissionError(f&quot;scope escalation attempt: {sorted(escalation)}&quot;)\n   return sorted(requested)`, `75682582086658020000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">def</span> <span class=\"token function\">narrow</span><span class=\"token punctuation\">(</span>parent_scopes<span class=\"token punctuation\">,</span> requested_scopes<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n   <span class=\"token triple-quoted-string string\">\"\"\"Return only requested scopes the parent actually holds. An attempt to\n   widen is a signal worth failing on, not silently dropping.\"\"\"</span>\n   parent <span class=\"token operator\">=</span> <span class=\"token builtin\">set</span><span class=\"token punctuation\">(</span>parent_scopes<span class=\"token punctuation\">)</span>\n   requested <span class=\"token operator\">=</span> <span class=\"token builtin\">set</span><span class=\"token punctuation\">(</span>requested_scopes<span class=\"token punctuation\">)</span>\n   escalation <span class=\"token operator\">=</span> requested <span class=\"token operator\">-</span> parent\n   <span class=\"token keyword\">if</span> escalation<span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">raise</span> PermissionError<span class=\"token punctuation\">(</span><span class=\"token string-interpolation\"><span class=\"token string\">f\"scope escalation attempt: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token builtin\">sorted</span><span class=\"token punctuation\">(</span>escalation<span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span>\n   <span class=\"token keyword\">return</span> <span class=\"token builtin\">sorted</span><span class=\"token punctuation\">(</span>requested<span class=\"token punctuation\">)</span></code></pre></div>\n<p>Raising rather than silently intersecting surfaces misconfiguration at the point it happens, since a well-formed agent should never request a scope it was not derived with.</p>\n<p>A capability token at the narrowest tier:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"78360438911535610000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`{\n &quot;sub&quot;: &quot;agent:triage-01&quot;,\n &quot;act&quot;: { &quot;sub&quot;: &quot;user:u123&quot; },\n &quot;tenant&quot;: &quot;acme&quot;,\n &quot;aud&quot;: &quot;https://tools.example.com/github&quot;,\n &quot;scopes&quot;: [&quot;github.issues.label&quot;],\n &quot;task_id&quot;: &quot;task:t789&quot;,\n &quot;cnf&quot;: { &quot;jkt&quot;: &quot;0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I&quot; },\n &quot;exp&quot;: 1774000120\n}`, `78360438911535610000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"json\"><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n <span class=\"token property\">\"sub\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"agent:triage-01\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"act\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token property\">\"sub\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"user:u123\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"tenant\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"acme\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"aud\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"https://tools.example.com/github\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"scopes\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"github.issues.label\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"task_id\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"task:t789\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"cnf\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token property\">\"jkt\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n <span class=\"token property\">\"exp\"</span><span class=\"token operator\">:</span> <span class=\"token number\">1774000120</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p><code class=\"language-text\">aud</code> restricts the token to one tool server. <code class=\"language-text\">act</code> records the delegating user (the RFC 8693 delegation claim). <code class=\"language-text\">cnf.jkt</code> binds it to a key. Two-minute expiry: capability tokens should survive one tool call, not a session.</p>\n<h2 id=\"mcp-specific-authorization-requirements\" style=\"position:relative;\"><a href=\"#mcp-specific-authorization-requirements\" aria-label=\"mcp specific authorization requirements permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>MCP-Specific Authorization Requirements</h2>\n<p>If you are building a remote MCP server, MCP authentication now requires OAuth 2.1, and several requirements are routinely missed.</p>\n<ul>\n<li><strong>OAuth 2.1 is mandatory for remote servers.</strong> PKCE on all clients per RFC 9700. Stdio servers are the exception: credentials arrive through environment variables from the host process.</li>\n<li><strong>Your MCP server is a resource server, not an authorization server.</strong> It validates tokens; your identity provider issues them. Conflating the two is the most common architectural error.</li>\n<li><strong>Resource Indicators are required.</strong> MCP clients must implement <a href=\"https://datatracker.ietf.org/doc/html/rfc8707\" target=\"_blank\" rel=\"nofollow\">RFC 8707</a> and include the resource parameter identifying the target MCP server’s canonical URI. Servers must validate the token was issued for them. Without this, a malicious MCP server can replay a token against a different server the user also uses. Several major identity providers are not compliant out of the box because they implemented a non-standard audience parameter before RFC 8707 was finalized.</li>\n<li><strong>Discovery is part of the protocol.</strong> Servers expose protected resource metadata at <code class=\"language-text\">/.well-known/oauth-protected-resource</code> (RFC 9728). Authorization server metadata follows RFC 8414. Dynamic Client Registration (RFC 7591) is now formally deprecated in the MCP spec in favor of Client ID Metadata Documents (CIMD). DCR remains available only for backward compatibility, with a twelve-month minimum deprecation window.</li>\n<li><strong>Never pass a client token through to a downstream API.</strong> Token passthrough defeats audience binding. If your MCP server calls a downstream service, it performs an RFC 8693 token exchange for a new token scoped to that service.</li>\n</ul>\n<h3 id=\"validating-the-audience\" style=\"position:relative;\"><a href=\"#validating-the-audience\" aria-label=\"validating the audience permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>Validating the Audience</strong></h3>\n<p>This runs before DPoP proof verification. Reject wrong-audience tokens without spending a signature check:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"85291765047771640000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`CANONICAL_URI = &quot;https://tools.example.com/mcp&quot;\n\ndef validate_audience(token_claims: dict, canonical_uri: str = CANONICAL_URI) -> None:\n    &quot;&quot;&quot;RFC 8707: the token must name this server, and only this server.&quot;&quot;&quot;\n    aud = token_claims.get(&quot;aud&quot;)\n    if aud is None:\n        # Some IdPs still emit the non-standard \\`audience\\` claim.\n        aud = token_claims.get(&quot;audience&quot;)\n    if aud is None:\n        raise PermissionError(&quot;token carries no audience&quot;)\n\n    audiences = [aud] if isinstance(aud, str) else list(aud)\n    if canonical_uri not in audiences:\n        raise PermissionError(f&quot;token not issued for this server: {audiences}&quot;)`, `85291765047771640000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\">CANONICAL_URI <span class=\"token operator\">=</span> <span class=\"token string\">\"https://tools.example.com/mcp\"</span>\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">validate_audience</span><span class=\"token punctuation\">(</span>token_claims<span class=\"token punctuation\">:</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">,</span> canonical_uri<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span> <span class=\"token operator\">=</span> CANONICAL_URI<span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token boolean\">None</span><span class=\"token punctuation\">:</span>\n    <span class=\"token triple-quoted-string string\">\"\"\"RFC 8707: the token must name this server, and only this server.\"\"\"</span>\n    aud <span class=\"token operator\">=</span> token_claims<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"aud\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> aud <span class=\"token keyword\">is</span> <span class=\"token boolean\">None</span><span class=\"token punctuation\">:</span>\n        <span class=\"token comment\"># Some IdPs still emit the non-standard `audience` claim.</span>\n        aud <span class=\"token operator\">=</span> token_claims<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"audience\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> aud <span class=\"token keyword\">is</span> <span class=\"token boolean\">None</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">raise</span> PermissionError<span class=\"token punctuation\">(</span><span class=\"token string\">\"token carries no audience\"</span><span class=\"token punctuation\">)</span>\n\n    audiences <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>aud<span class=\"token punctuation\">]</span> <span class=\"token keyword\">if</span> <span class=\"token builtin\">isinstance</span><span class=\"token punctuation\">(</span>aud<span class=\"token punctuation\">,</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">else</span> <span class=\"token builtin\">list</span><span class=\"token punctuation\">(</span>aud<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> canonical_uri <span class=\"token keyword\">not</span> <span class=\"token keyword\">in</span> audiences<span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">raise</span> PermissionError<span class=\"token punctuation\">(</span><span class=\"token string-interpolation\"><span class=\"token string\">f\"token not issued for this server: </span><span class=\"token interpolation\"><span class=\"token punctuation\">{</span>audiences<span class=\"token punctuation\">}</span></span><span class=\"token string\">\"</span></span><span class=\"token punctuation\">)</span></code></pre></div>\n<h2 id=\"sender-constrained-tokens-dpop-vs-mtls\" style=\"position:relative;\"><a href=\"#sender-constrained-tokens-dpop-vs-mtls\" aria-label=\"sender constrained tokens dpop vs mtls permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Sender-Constrained Tokens: DPoP vs. mTLS</h2>\n<p>Bearer tokens are passwords: whoever holds one can spend it. Sender-constrained tokens bind a token to a key. mutual TLS (mTLS) does this at the transport layer and requires certificate infrastructure at every hop. Demonstrating Proof-of-Possession (DPoP), defined in <a href=\"https://datatracker.ietf.org/doc/html/rfc9449\" target=\"_blank\" rel=\"nofollow\">RFC 9449</a>, does it by signing a short JWT per request over ordinary HTTPS, usually the right choice for OAuth for AI agents where tool servers are third-party.</p>\n<p>The agent generates a keypair; the token carries a <code class=\"language-text\">cnf.jkt</code> thumbprint. Each request includes a fresh DPoP proof:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"19401039341778883000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import base64, hashlib, json, time, uuid\nimport jwt\nfrom cryptography.hazmat.primitives.asymmetric import ec\n\n\ndef b64url(data: bytes) -> str:\n   return base64.urlsafe_b64encode(data).decode().rstrip(&quot;=&quot;)\n\n\nprivate_key = ec.generate_private_key(ec.SECP256R1())\nnums = private_key.public_key().public_numbers()\njwk = {\n   &quot;kty&quot;: &quot;EC&quot;,\n   &quot;crv&quot;: &quot;P-256&quot;,\n   &quot;x&quot;: b64url(nums.x.to_bytes(32, &quot;big&quot;)),\n   &quot;y&quot;: b64url(nums.y.to_bytes(32, &quot;big&quot;)),\n}\n\n\ndef create_dpop_proof(method: str, url: str, access_token: str) -> str:\n   # ath binds this proof to one specific access token\n   ath = b64url(hashlib.sha256(access_token.encode()).digest())\n   return jwt.encode(\n       {\n           &quot;jti&quot;: str(uuid.uuid4()),\n           &quot;htm&quot;: method,\n           &quot;htu&quot;: url,\n           &quot;iat&quot;: int(time.time()),\n           &quot;ath&quot;: ath,\n       },\n       private_key,\n       algorithm=&quot;ES256&quot;,\n       headers={&quot;typ&quot;: &quot;dpop+jwt&quot;, &quot;alg&quot;: &quot;ES256&quot;, &quot;jwk&quot;: jwk},\n   )`, `19401039341778883000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">import</span> base64<span class=\"token punctuation\">,</span> hashlib<span class=\"token punctuation\">,</span> json<span class=\"token punctuation\">,</span> time<span class=\"token punctuation\">,</span> uuid\n<span class=\"token keyword\">import</span> jwt\n<span class=\"token keyword\">from</span> cryptography<span class=\"token punctuation\">.</span>hazmat<span class=\"token punctuation\">.</span>primitives<span class=\"token punctuation\">.</span>asymmetric <span class=\"token keyword\">import</span> ec\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">b64url</span><span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">:</span> <span class=\"token builtin\">bytes</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">:</span>\n   <span class=\"token keyword\">return</span> base64<span class=\"token punctuation\">.</span>urlsafe_b64encode<span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>decode<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>rstrip<span class=\"token punctuation\">(</span><span class=\"token string\">\"=\"</span><span class=\"token punctuation\">)</span>\n\n\nprivate_key <span class=\"token operator\">=</span> ec<span class=\"token punctuation\">.</span>generate_private_key<span class=\"token punctuation\">(</span>ec<span class=\"token punctuation\">.</span>SECP256R1<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\nnums <span class=\"token operator\">=</span> private_key<span class=\"token punctuation\">.</span>public_key<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>public_numbers<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\njwk <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n   <span class=\"token string\">\"kty\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"EC\"</span><span class=\"token punctuation\">,</span>\n   <span class=\"token string\">\"crv\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"P-256\"</span><span class=\"token punctuation\">,</span>\n   <span class=\"token string\">\"x\"</span><span class=\"token punctuation\">:</span> b64url<span class=\"token punctuation\">(</span>nums<span class=\"token punctuation\">.</span>x<span class=\"token punctuation\">.</span>to_bytes<span class=\"token punctuation\">(</span><span class=\"token number\">32</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"big\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n   <span class=\"token string\">\"y\"</span><span class=\"token punctuation\">:</span> b64url<span class=\"token punctuation\">(</span>nums<span class=\"token punctuation\">.</span>y<span class=\"token punctuation\">.</span>to_bytes<span class=\"token punctuation\">(</span><span class=\"token number\">32</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"big\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">create_dpop_proof</span><span class=\"token punctuation\">(</span>method<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">,</span> url<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">,</span> access_token<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">:</span>\n   <span class=\"token comment\"># ath binds this proof to one specific access token</span>\n   ath <span class=\"token operator\">=</span> b64url<span class=\"token punctuation\">(</span>hashlib<span class=\"token punctuation\">.</span>sha256<span class=\"token punctuation\">(</span>access_token<span class=\"token punctuation\">.</span>encode<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>digest<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n   <span class=\"token keyword\">return</span> jwt<span class=\"token punctuation\">.</span>encode<span class=\"token punctuation\">(</span>\n       <span class=\"token punctuation\">{</span>\n           <span class=\"token string\">\"jti\"</span><span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">(</span>uuid<span class=\"token punctuation\">.</span>uuid4<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n           <span class=\"token string\">\"htm\"</span><span class=\"token punctuation\">:</span> method<span class=\"token punctuation\">,</span>\n           <span class=\"token string\">\"htu\"</span><span class=\"token punctuation\">:</span> url<span class=\"token punctuation\">,</span>\n           <span class=\"token string\">\"iat\"</span><span class=\"token punctuation\">:</span> <span class=\"token builtin\">int</span><span class=\"token punctuation\">(</span>time<span class=\"token punctuation\">.</span>time<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n           <span class=\"token string\">\"ath\"</span><span class=\"token punctuation\">:</span> ath<span class=\"token punctuation\">,</span>\n       <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n       private_key<span class=\"token punctuation\">,</span>\n       algorithm<span class=\"token operator\">=</span><span class=\"token string\">\"ES256\"</span><span class=\"token punctuation\">,</span>\n       headers<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token string\">\"typ\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"dpop+jwt\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"alg\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"ES256\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"jwk\"</span><span class=\"token punctuation\">:</span> jwk<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n   <span class=\"token punctuation\">)</span></code></pre></div>\n<p>Verification at the tool server has to check five things, and skipping any one of them removes most of the value:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"8776008967357928000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`def jwk_thumbprint(jwk_dict: dict) -> str:\n   # RFC 7638: required members only, lexicographic order, no whitespace\n   canonical = json.dumps(\n       {&quot;crv&quot;: jwk_dict[&quot;crv&quot;], &quot;kty&quot;: jwk_dict[&quot;kty&quot;],\n        &quot;x&quot;: jwk_dict[&quot;x&quot;], &quot;y&quot;: jwk_dict[&quot;y&quot;]},\n       separators=(&quot;,&quot;, &quot;:&quot;), sort_keys=True,\n   )\n   return b64url(hashlib.sha256(canonical.encode()).digest())\n\n\n_seen_jti = set()  # use Redis with a TTL in production\n\n\ndef verify_dpop_proof(proof, method, url, access_token, token_cnf_jkt):\n   header = jwt.get_unverified_header(proof)\n   if header.get(&quot;typ&quot;) != &quot;dpop+jwt&quot;:\n       raise ValueError(&quot;bad typ&quot;)\n\n\n   proof_jwk = header[&quot;jwk&quot;]\n   pub = jwt.algorithms.ECAlgorithm.from_jwk(json.dumps(proof_jwk))\n   claims = jwt.decode(proof, pub, algorithms=[&quot;ES256&quot;])\n\n\n   # 1. the proof covers this exact request\n   assert claims[&quot;htm&quot;] == method, &quot;method mismatch&quot;\n   assert claims[&quot;htu&quot;] == url, &quot;url mismatch&quot;\n   # 2. the proof is fresh\n   assert abs(time.time() - claims[&quot;iat&quot;]) < 60, &quot;proof too old&quot;\n   # 3. the proof is tied to this access token\n   expected_ath = b64url(hashlib.sha256(access_token.encode()).digest())\n   assert claims[&quot;ath&quot;] == expected_ath, &quot;access token hash mismatch&quot;\n   # 4. the token was issued to this key\n   assert jwk_thumbprint(proof_jwk) == token_cnf_jkt, &quot;token not bound to this key&quot;\n   # 5. the proof has not been replayed\n   if claims[&quot;jti&quot;] in _seen_jti:\n       raise ValueError(&quot;replay detected&quot;)\n   _seen_jti.add(claims[&quot;jti&quot;])\n\n\n   return claims`, `8776008967357928000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">def</span> <span class=\"token function\">jwk_thumbprint</span><span class=\"token punctuation\">(</span>jwk_dict<span class=\"token punctuation\">:</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">:</span>\n   <span class=\"token comment\"># RFC 7638: required members only, lexicographic order, no whitespace</span>\n   canonical <span class=\"token operator\">=</span> json<span class=\"token punctuation\">.</span>dumps<span class=\"token punctuation\">(</span>\n       <span class=\"token punctuation\">{</span><span class=\"token string\">\"crv\"</span><span class=\"token punctuation\">:</span> jwk_dict<span class=\"token punctuation\">[</span><span class=\"token string\">\"crv\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"kty\"</span><span class=\"token punctuation\">:</span> jwk_dict<span class=\"token punctuation\">[</span><span class=\"token string\">\"kty\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string\">\"x\"</span><span class=\"token punctuation\">:</span> jwk_dict<span class=\"token punctuation\">[</span><span class=\"token string\">\"x\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"y\"</span><span class=\"token punctuation\">:</span> jwk_dict<span class=\"token punctuation\">[</span><span class=\"token string\">\"y\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n       separators<span class=\"token operator\">=</span><span class=\"token punctuation\">(</span><span class=\"token string\">\",\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\":\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> sort_keys<span class=\"token operator\">=</span><span class=\"token boolean\">True</span><span class=\"token punctuation\">,</span>\n   <span class=\"token punctuation\">)</span>\n   <span class=\"token keyword\">return</span> b64url<span class=\"token punctuation\">(</span>hashlib<span class=\"token punctuation\">.</span>sha256<span class=\"token punctuation\">(</span>canonical<span class=\"token punctuation\">.</span>encode<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>digest<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n\n_seen_jti <span class=\"token operator\">=</span> <span class=\"token builtin\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>  <span class=\"token comment\"># use Redis with a TTL in production</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">verify_dpop_proof</span><span class=\"token punctuation\">(</span>proof<span class=\"token punctuation\">,</span> method<span class=\"token punctuation\">,</span> url<span class=\"token punctuation\">,</span> access_token<span class=\"token punctuation\">,</span> token_cnf_jkt<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n   header <span class=\"token operator\">=</span> jwt<span class=\"token punctuation\">.</span>get_unverified_header<span class=\"token punctuation\">(</span>proof<span class=\"token punctuation\">)</span>\n   <span class=\"token keyword\">if</span> header<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"typ\"</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">!=</span> <span class=\"token string\">\"dpop+jwt\"</span><span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">raise</span> ValueError<span class=\"token punctuation\">(</span><span class=\"token string\">\"bad typ\"</span><span class=\"token punctuation\">)</span>\n\n\n   proof_jwk <span class=\"token operator\">=</span> header<span class=\"token punctuation\">[</span><span class=\"token string\">\"jwk\"</span><span class=\"token punctuation\">]</span>\n   pub <span class=\"token operator\">=</span> jwt<span class=\"token punctuation\">.</span>algorithms<span class=\"token punctuation\">.</span>ECAlgorithm<span class=\"token punctuation\">.</span>from_jwk<span class=\"token punctuation\">(</span>json<span class=\"token punctuation\">.</span>dumps<span class=\"token punctuation\">(</span>proof_jwk<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n   claims <span class=\"token operator\">=</span> jwt<span class=\"token punctuation\">.</span>decode<span class=\"token punctuation\">(</span>proof<span class=\"token punctuation\">,</span> pub<span class=\"token punctuation\">,</span> algorithms<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"ES256\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n\n\n   <span class=\"token comment\"># 1. the proof covers this exact request</span>\n   <span class=\"token keyword\">assert</span> claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"htm\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">==</span> method<span class=\"token punctuation\">,</span> <span class=\"token string\">\"method mismatch\"</span>\n   <span class=\"token keyword\">assert</span> claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"htu\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">==</span> url<span class=\"token punctuation\">,</span> <span class=\"token string\">\"url mismatch\"</span>\n   <span class=\"token comment\"># 2. the proof is fresh</span>\n   <span class=\"token keyword\">assert</span> <span class=\"token builtin\">abs</span><span class=\"token punctuation\">(</span>time<span class=\"token punctuation\">.</span>time<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span> claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"iat\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;</span> <span class=\"token number\">60</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"proof too old\"</span>\n   <span class=\"token comment\"># 3. the proof is tied to this access token</span>\n   expected_ath <span class=\"token operator\">=</span> b64url<span class=\"token punctuation\">(</span>hashlib<span class=\"token punctuation\">.</span>sha256<span class=\"token punctuation\">(</span>access_token<span class=\"token punctuation\">.</span>encode<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>digest<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n   <span class=\"token keyword\">assert</span> claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"ath\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">==</span> expected_ath<span class=\"token punctuation\">,</span> <span class=\"token string\">\"access token hash mismatch\"</span>\n   <span class=\"token comment\"># 4. the token was issued to this key</span>\n   <span class=\"token keyword\">assert</span> jwk_thumbprint<span class=\"token punctuation\">(</span>proof_jwk<span class=\"token punctuation\">)</span> <span class=\"token operator\">==</span> token_cnf_jkt<span class=\"token punctuation\">,</span> <span class=\"token string\">\"token not bound to this key\"</span>\n   <span class=\"token comment\"># 5. the proof has not been replayed</span>\n   <span class=\"token keyword\">if</span> claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"jti\"</span><span class=\"token punctuation\">]</span> <span class=\"token keyword\">in</span> _seen_jti<span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">raise</span> ValueError<span class=\"token punctuation\">(</span><span class=\"token string\">\"replay detected\"</span><span class=\"token punctuation\">)</span>\n   _seen_jti<span class=\"token punctuation\">.</span>add<span class=\"token punctuation\">(</span>claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"jti\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n\n\n   <span class=\"token keyword\">return</span> claims</code></pre></div>\n<h2 id=\"the-missing-third-layer-per-action-authorization\" style=\"position:relative;\"><a href=\"#the-missing-third-layer-per-action-authorization\" aria-label=\"the missing third layer per action authorization permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The Missing Third Layer: Per-Action Authorization</h2>\n<p>Most AI agent authorization architectures stop after two layers. Authentication establishes who is calling. API-level authorization confirms the token carries the right scope. Both passed in the Yue incident, and 200 emails still vanished.</p>\n<p>A scope is a statement about a category of action. The dangerous thing is a specific action with specific arguments. <code class=\"language-text\">email.delete</code> says the agent may delete email. It says nothing about whether this agent should delete these 200 messages in a single burst after the user asked it to suggest rather than act.</p>\n<p>That question belongs to a policy decision point. The pattern: a policy enforcement point (PEP) at the tool server calls a policy decision point (PDP) before execution. The OpenID <a href=\"https://openid.net/wg/authzen/\" target=\"_blank\" rel=\"nofollow\">AuthZEN</a> working group is standardizing this around <code class=\"language-text\">Subject/Action/Resource/Context</code>, and its COAZ profile maps it specifically to MCP tool authorization, requiring that context carry the agent’s agentic identity so policy can distinguish an agent acting for a user from the user acting directly.</p>\n<p>Two design rules worth adopting from AuthZEN regardless of PDP implementation: a deny is a successful evaluation returning a negative decision, not an HTTP error; a malformed request is an error, not a silent deny.</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"40502467772326730000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`def has_scope(token_claims: dict, required_scope: str) -> bool:\n   return required_scope in token_claims.get(&quot;scopes&quot;, [])\n\n\ndef policy_decision(subject: dict, action: str, resource: str, context: dict) -> dict:\n   &quot;&quot;&quot;In production this is an HTTP call to an AuthZEN PDP.\n   The Subject/Action/Resource/Context shape is the same either way.&quot;&quot;&quot;\n   if action == &quot;github.issues.delete&quot;:\n       return {&quot;decision&quot;: False, &quot;reason&quot;: &quot;action_not_permitted_for_agents&quot;}\n   if resource.split(&quot;:&quot;)[1].split(&quot;/&quot;)[0] != subject[&quot;tenant&quot;]:\n       return {&quot;decision&quot;: False, &quot;reason&quot;: &quot;cross_tenant_denied&quot;}\n   if context.get(&quot;spend_usd&quot;, 0) > 5.00:\n       return {&quot;decision&quot;: False, &quot;reason&quot;: &quot;spend_cap_exceeded&quot;}\n   return {&quot;decision&quot;: True, &quot;reason&quot;: &quot;policy:triage-agent-v2&quot;}\n\n\ndef authorize_tool_call(token_claims, action, resource, context):\n   # Layer 1: does the token permit this category of action?\n   if not has_scope(token_claims, action):\n       return {&quot;allowed&quot;: False, &quot;reason&quot;: &quot;scope_missing&quot;}\n   # Layer 2: does policy permit this specific call, right now?\n   subject = {&quot;agent_id&quot;: token_claims[&quot;sub&quot;], &quot;tenant&quot;: token_claims[&quot;tenant&quot;]}\n   decision = policy_decision(subject, action, resource, context)\n   return {&quot;allowed&quot;: decision[&quot;decision&quot;], &quot;reason&quot;: decision[&quot;reason&quot;]}`, `40502467772326730000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">def</span> <span class=\"token function\">has_scope</span><span class=\"token punctuation\">(</span>token_claims<span class=\"token punctuation\">:</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">,</span> required_scope<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">bool</span><span class=\"token punctuation\">:</span>\n   <span class=\"token keyword\">return</span> required_scope <span class=\"token keyword\">in</span> token_claims<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"scopes\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">policy_decision</span><span class=\"token punctuation\">(</span>subject<span class=\"token punctuation\">:</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">,</span> action<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">,</span> resource<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">:</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">:</span>\n   <span class=\"token triple-quoted-string string\">\"\"\"In production this is an HTTP call to an AuthZEN PDP.\n   The Subject/Action/Resource/Context shape is the same either way.\"\"\"</span>\n   <span class=\"token keyword\">if</span> action <span class=\"token operator\">==</span> <span class=\"token string\">\"github.issues.delete\"</span><span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"decision\"</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">False</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"action_not_permitted_for_agents\"</span><span class=\"token punctuation\">}</span>\n   <span class=\"token keyword\">if</span> resource<span class=\"token punctuation\">.</span>split<span class=\"token punctuation\">(</span><span class=\"token string\">\":\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>split<span class=\"token punctuation\">(</span><span class=\"token string\">\"/\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">!=</span> subject<span class=\"token punctuation\">[</span><span class=\"token string\">\"tenant\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"decision\"</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">False</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"cross_tenant_denied\"</span><span class=\"token punctuation\">}</span>\n   <span class=\"token keyword\">if</span> context<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"spend_usd\"</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">></span> <span class=\"token number\">5.00</span><span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"decision\"</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">False</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"spend_cap_exceeded\"</span><span class=\"token punctuation\">}</span>\n   <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"decision\"</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">True</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"policy:triage-agent-v2\"</span><span class=\"token punctuation\">}</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">authorize_tool_call</span><span class=\"token punctuation\">(</span>token_claims<span class=\"token punctuation\">,</span> action<span class=\"token punctuation\">,</span> resource<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n   <span class=\"token comment\"># Layer 1: does the token permit this category of action?</span>\n   <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> has_scope<span class=\"token punctuation\">(</span>token_claims<span class=\"token punctuation\">,</span> action<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n       <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"allowed\"</span><span class=\"token punctuation\">:</span> <span class=\"token boolean\">False</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"scope_missing\"</span><span class=\"token punctuation\">}</span>\n   <span class=\"token comment\"># Layer 2: does policy permit this specific call, right now?</span>\n   subject <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"agent_id\"</span><span class=\"token punctuation\">:</span> token_claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"sub\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"tenant\"</span><span class=\"token punctuation\">:</span> token_claims<span class=\"token punctuation\">[</span><span class=\"token string\">\"tenant\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span>\n   decision <span class=\"token operator\">=</span> policy_decision<span class=\"token punctuation\">(</span>subject<span class=\"token punctuation\">,</span> action<span class=\"token punctuation\">,</span> resource<span class=\"token punctuation\">,</span> context<span class=\"token punctuation\">)</span>\n   <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"allowed\"</span><span class=\"token punctuation\">:</span> decision<span class=\"token punctuation\">[</span><span class=\"token string\">\"decision\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">:</span> decision<span class=\"token punctuation\">[</span><span class=\"token string\">\"reason\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span></code></pre></div>\n<p>Running that against a correctly-scoped token makes the point:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">allowed label call -> allowed=True   reason=policy:triage-agent-v2\nscope not granted -> allowed=False  reason=scope_missing\ncross tenant-> allowed=False  reason=cross_tenant_denied\nspend cap -> allowed=False  reason=spend_cap_exceeded</code></pre></div>\n<p>Rows three and four are the entire argument. Valid token. Correct scope. Call denied anyway, because scope is not policy. Any agent architecture without that second check is one compaction away from the Yue incident.</p>\n<p>One rule governs placement: policy is evaluated at the tool server, never inside the agent’s prompt. Policy in a prompt is advisory text subject to exactly the failures described above.</p>\n<h2 id=\"real-time-revocation-with-caep\" style=\"position:relative;\"><a href=\"#real-time-revocation-with-caep\" aria-label=\"real time revocation with caep permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Real-Time Revocation With CAEP</h2>\n<p>A capability token valid for 120 seconds is still valid for 120 seconds after the user’s account is disabled. Continuous Access Evaluation Profile (CAEP) closes this gap by pushing security events (<code class=\"language-text\">session-revoked</code>, <code class=\"language-text\">credential-change</code>, <code class=\"language-text\">device-compliance-change</code>) from your identity provider to tool servers via the OpenID Shared Signals Framework, which profiles Security Event Tokens (RFC 8417) and pushes them over HTTP (RFC 8935). The spec explicitly covers “human or robotic users,” making agent runtimes first-class. Treat CAEP as what makes a kill switch real: revocation on the next token refresh is not a kill switch when the agent already has the token it needs.</p>\n<p>A receiver is two pieces: an endpoint that accepts and verifies incoming SETs, and a revocation set the tool server checks before executing any action.</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"80437025250977660000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import jwt\nfrom jwt import PyJWKClient\nimport requests\n\nCAEP_STREAM_ENDPOINT = &quot;https://idp.example.com/.well-known/sse-configuration&quot;\nREVOKED_SESSIONS = set()  # Redis in production\n\ndef register_caep_stream(receiver_url: str, events: list[str]) -> dict:\n    &quot;&quot;&quot;Register this tool server as a CAEP receiver.&quot;&quot;&quot;\n    return requests.post(CAEP_STREAM_ENDPOINT, json={\n        &quot;delivery&quot;: {&quot;method&quot;: &quot;urn:ietf:rfc:8935&quot;, &quot;endpoint_url&quot;: receiver_url},\n        &quot;events_requested&quot;: events,\n    }).json()\n\nTRANSMITTER_JWKS_URI = &quot;https://idp.example.com/.well-known/jwks.json&quot;\n\n\ndef handle_caep_event(raw_set: str) -> None:\n    &quot;&quot;&quot;Verify and process inbound Security Event Tokens.&quot;&quot;&quot;\n    jwks_client = PyJWKClient(TRANSMITTER_JWKS_URI)\n    key = jwks_client.get_signing_key_from_jwt(raw_set).key\n    event = jwt.decode(raw_set, key, algorithms=[&quot;RS256&quot;])\n    event_type = event.get(&quot;events&quot;, {})\n    CAEP_SESSION_REVOKED = &quot;https://schemas.openid.net/secevent/caep/event-type/session-revoked&quot;\n    if CAEP_SESSION_REVOKED in event_type:\n        session_id = event_type[CAEP_SESSION_REVOKED].get(&quot;subject&quot;, {}).get(&quot;session_id&quot;)\n        if session_id:\n            REVOKED_SESSIONS.add(session_id)\n\ndef session_is_revoked(token_claims: dict) -> bool:\n    return token_claims.get(&quot;sid&quot;) in REVOKED_SESSIONS\n\n# Register for revocation events at startup\nregister_caep_stream(\n    receiver_url=&quot;https://tools.example.com/caep/events&quot;,\n    events=[&quot;session-revoked&quot;, &quot;credential-change&quot;],\n)`, `80437025250977660000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">import</span> jwt\n<span class=\"token keyword\">from</span> jwt <span class=\"token keyword\">import</span> PyJWKClient\n<span class=\"token keyword\">import</span> requests\n\nCAEP_STREAM_ENDPOINT <span class=\"token operator\">=</span> <span class=\"token string\">\"https://idp.example.com/.well-known/sse-configuration\"</span>\nREVOKED_SESSIONS <span class=\"token operator\">=</span> <span class=\"token builtin\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>  <span class=\"token comment\"># Redis in production</span>\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">register_caep_stream</span><span class=\"token punctuation\">(</span>receiver_url<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">,</span> events<span class=\"token punctuation\">:</span> <span class=\"token builtin\">list</span><span class=\"token punctuation\">[</span><span class=\"token builtin\">str</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">:</span>\n    <span class=\"token triple-quoted-string string\">\"\"\"Register this tool server as a CAEP receiver.\"\"\"</span>\n    <span class=\"token keyword\">return</span> requests<span class=\"token punctuation\">.</span>post<span class=\"token punctuation\">(</span>CAEP_STREAM_ENDPOINT<span class=\"token punctuation\">,</span> json<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>\n        <span class=\"token string\">\"delivery\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"method\"</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"urn:ietf:rfc:8935\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"endpoint_url\"</span><span class=\"token punctuation\">:</span> receiver_url<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string\">\"events_requested\"</span><span class=\"token punctuation\">:</span> events<span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>json<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\nTRANSMITTER_JWKS_URI <span class=\"token operator\">=</span> <span class=\"token string\">\"https://idp.example.com/.well-known/jwks.json\"</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">handle_caep_event</span><span class=\"token punctuation\">(</span>raw_set<span class=\"token punctuation\">:</span> <span class=\"token builtin\">str</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token boolean\">None</span><span class=\"token punctuation\">:</span>\n    <span class=\"token triple-quoted-string string\">\"\"\"Verify and process inbound Security Event Tokens.\"\"\"</span>\n    jwks_client <span class=\"token operator\">=</span> PyJWKClient<span class=\"token punctuation\">(</span>TRANSMITTER_JWKS_URI<span class=\"token punctuation\">)</span>\n    key <span class=\"token operator\">=</span> jwks_client<span class=\"token punctuation\">.</span>get_signing_key_from_jwt<span class=\"token punctuation\">(</span>raw_set<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>key\n    event <span class=\"token operator\">=</span> jwt<span class=\"token punctuation\">.</span>decode<span class=\"token punctuation\">(</span>raw_set<span class=\"token punctuation\">,</span> key<span class=\"token punctuation\">,</span> algorithms<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"RS256\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n    event_type <span class=\"token operator\">=</span> event<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"events\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    CAEP_SESSION_REVOKED <span class=\"token operator\">=</span> <span class=\"token string\">\"https://schemas.openid.net/secevent/caep/event-type/session-revoked\"</span>\n    <span class=\"token keyword\">if</span> CAEP_SESSION_REVOKED <span class=\"token keyword\">in</span> event_type<span class=\"token punctuation\">:</span>\n        session_id <span class=\"token operator\">=</span> event_type<span class=\"token punctuation\">[</span>CAEP_SESSION_REVOKED<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"subject\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"session_id\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> session_id<span class=\"token punctuation\">:</span>\n            REVOKED_SESSIONS<span class=\"token punctuation\">.</span>add<span class=\"token punctuation\">(</span>session_id<span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">session_is_revoked</span><span class=\"token punctuation\">(</span>token_claims<span class=\"token punctuation\">:</span> <span class=\"token builtin\">dict</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span><span class=\"token operator\">></span> <span class=\"token builtin\">bool</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">return</span> token_claims<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"sid\"</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">in</span> REVOKED_SESSIONS\n\n<span class=\"token comment\"># Register for revocation events at startup</span>\nregister_caep_stream<span class=\"token punctuation\">(</span>\n    receiver_url<span class=\"token operator\">=</span><span class=\"token string\">\"https://tools.example.com/caep/events\"</span><span class=\"token punctuation\">,</span>\n    events<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"session-revoked\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"credential-change\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span></code></pre></div>\n<h2 id=\"human-in-the-loop-approval-gates\" style=\"position:relative;\"><a href=\"#human-in-the-loop-approval-gates\" aria-label=\"human in the loop approval gates permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Human-in-the-Loop Approval Gates</h2>\n<p>Not every action needs a human, and gating everything trains operators to approve reflexively. Route by risk instead:</p>\n<table>\n<thead>\n<tr>\n<th>Ruleset</th>\n<th>Example condition</th>\n<th>Outcome</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Auto-allow</td>\n<td>Read-only operations under rate limit</td>\n<td>Execute immediately</td>\n</tr>\n<tr>\n<td>Soft-hold</td>\n<td>Spend above threshold, first use of a new tool</td>\n<td>Queue for async approval</td>\n</tr>\n<tr>\n<td>Must-approve</td>\n<td>Deletes, cross-tenant access, irreversible operations</td>\n<td>Block until explicit approval</td>\n</tr>\n</tbody>\n</table>\n<p>Three details separate a working gate from theatre. The gate is enforced at the tool server, not requested of the agent. “Ask before deleting” in a prompt is the instruction compaction removed in the Yue incident. Approval must be out of band. Yue’s STOP commands entered the same failing loop. And every decision is recorded in a store the agent cannot write to.</p>\n<h2 id=\"implementing-this-with-supertokens\" style=\"position:relative;\"><a href=\"#implementing-this-with-supertokens\" aria-label=\"implementing this with supertokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Implementing This With SuperTokens</h2>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f9f7166952e6cb007575558aaa765077/669cd/Supertokens.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 49.36708860759494%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAACCUlEQVQoz13RaU8TURiG4ZpIpmeb9czSzrSle0I3aCulBERQwAgJhi0K/hF+/W1aEMUPd95vV56cU6qnAcNOwWzYZ7KZsWgmHHUzDtop82bGoFGlWVTI0pQkSYjj+E1pmhKGIU9PT9zd3VEKPE0Ru0xqESfdmJtByq9Jxs9RxkUvYbduqcchnuejXR+tXZTSKKXeZK3FGEMpWoGRYVT1+dyJuNmKeRwl/BgkfOvGzAtLYWO0axHGokyEVC5CSMrl8johBI7jrCtZX1OzhnHuc/ICPowS7rcSvvYqHGz1GG02abYH5M0BKqii3Bipn1HxD7q6Jesr6tYwyn0OWyGnnYizdsh+JrlaTrk8PmYx3WV5eMr4wxF5c8g7x0Mo72WleAtGniKPNP3MY5L7TKsuH7sZt5/2uD//zMP1NZcXV3y/eeTs4paT8ysG2wu0b3Gcv9gr6BtFGmjqsaEVG/qJWX/Ql/mI5aDLcnvEYr7LdLbHzmyf8c6STn+MMiHOf+s2Nt5T0koSuprEV2SBJA8VRSCpBZJ2NabfKOhtNug1W2zW6mRJhjEBQhqcl/dbpbUmTauUpBAYJVkt9Y0kMALrSTJPEMkyRZZQS2Ma1YyKDQmFQ+h7SKnWv7rCVtf3AwbDGaU/k5UUKFFeZ9QzWgkkjVgz6eQMWxUaiUcWugSuRoryK7hKKYm1Eb8B7o8EsKuX1jcAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"SuperTokens\"\n        title=\"SuperTokens\"\n        src=\"/static/f9f7166952e6cb007575558aaa765077/f058b/Supertokens.png\"\n        srcset=\"/static/f9f7166952e6cb007575558aaa765077/c26ae/Supertokens.png 158w,\n/static/f9f7166952e6cb007575558aaa765077/6bdcf/Supertokens.png 315w,\n/static/f9f7166952e6cb007575558aaa765077/f058b/Supertokens.png 630w,\n/static/f9f7166952e6cb007575558aaa765077/40601/Supertokens.png 945w,\n/static/f9f7166952e6cb007575558aaa765077/78612/Supertokens.png 1260w,\n/static/f9f7166952e6cb007575558aaa765077/669cd/Supertokens.png 3024w\"\n        sizes=\"(max-width: 630px) 100vw, 630px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p><a href=\"https://supertokens.com/\" target=\"_blank\" rel=\"nofollow\">SuperTokens</a> covers the <a href=\"https://supertokens.com/docs/post-authentication/session-management/introduction\" target=\"_blank\" rel=\"nofollow\">session</a> and token layer of this architecture, and ships an <a href=\"https://supertokens.com/docs/authentication/ai-authentication\" target=\"_blank\" rel=\"nofollow\">MCP plugin</a> that handles the OAuth flow the specification requires. Two notes before the code: the <a href=\"https://supertokens.com/blog/supertokens-mcp-toolkit\" target=\"_blank\" rel=\"nofollow\">MCP plugin</a> is currently in beta and may change, and it depends on the OAuth2 recipe, which is a paid feature currently available for the Node SDK.</p>\n<h3 id=\"issuing-an-agent-session-with-narrowed-claims\" style=\"position:relative;\"><a href=\"#issuing-an-agent-session-with-narrowed-claims\" aria-label=\"issuing an agent session with narrowed claims permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>Issuing an Agent Session With Narrowed Claims</strong></h3>\n<p>The agent session derives from the user’s session and carries the narrowed scope set, the acting user, the tenant, and the task it is bound to:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"66889917918917450000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import { verifySession } from &quot;supertokens-node/recipe/session/framework/express&quot;;\nimport Session from &quot;supertokens-node/recipe/session&quot;;\nimport express from &quot;express&quot;;\n\n\nconst app = express();\n\n\napp.post(&quot;/agent/start&quot;, verifySession(), async (req, res) => {\n const userSession = req.session!;\n const userPayload = userSession.getAccessTokenPayload();\n\n\n // Never widen: the agent can only receive scopes the user already holds.\n const userScopes: string[] = userPayload.scopes ?? [];\n const requested: string[] = req.body.scopes ?? [];\n const escalation = requested.filter((s) => !userScopes.includes(s));\n if (escalation.length > 0) {\n   return res.status(403).json({ error: &quot;scope_escalation&quot;, escalation });\n }\n\n\n const agentSession = await Session.createNewSession(\n   req, res,\n   userSession.getTenantId(),\n   userSession.getRecipeUserId(),\n   {\n     agent_id: \\`agent:\\${req.body.agentName}\\`,\n     act: { sub: userSession.getUserId() },   // RFC 8693 delegation claim\n     scopes: requested,\n     task_id: req.body.taskId,\n     token_type: &quot;agent&quot;,\n   },\n );\n\n\n res.json({ agentSessionHandle: agentSession.getHandle() });\n});`, `66889917918917450000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> verifySession <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"supertokens-node/recipe/session/framework/express\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> Session <span class=\"token keyword\">from</span> <span class=\"token string\">\"supertokens-node/recipe/session\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> express <span class=\"token keyword\">from</span> <span class=\"token string\">\"express\"</span><span class=\"token punctuation\">;</span>\n\n\n<span class=\"token keyword\">const</span> app <span class=\"token operator\">=</span> <span class=\"token function\">express</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\napp<span class=\"token punctuation\">.</span><span class=\"token function\">post</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/agent/start\"</span><span class=\"token punctuation\">,</span> <span class=\"token function\">verifySession</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n <span class=\"token keyword\">const</span> userSession <span class=\"token operator\">=</span> req<span class=\"token punctuation\">.</span>session<span class=\"token operator\">!</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">const</span> userPayload <span class=\"token operator\">=</span> userSession<span class=\"token punctuation\">.</span><span class=\"token function\">getAccessTokenPayload</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\n <span class=\"token comment\">// Never widen: the agent can only receive scopes the user already holds.</span>\n <span class=\"token keyword\">const</span> userScopes<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> userPayload<span class=\"token punctuation\">.</span>scopes <span class=\"token operator\">??</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">const</span> requested<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> req<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>scopes <span class=\"token operator\">??</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">const</span> escalation <span class=\"token operator\">=</span> requested<span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token operator\">!</span>userScopes<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>escalation<span class=\"token punctuation\">.</span>length <span class=\"token operator\">></span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n   <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">status</span><span class=\"token punctuation\">(</span><span class=\"token number\">403</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> error<span class=\"token operator\">:</span> <span class=\"token string\">\"scope_escalation\"</span><span class=\"token punctuation\">,</span> escalation <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n <span class=\"token punctuation\">}</span>\n\n\n <span class=\"token keyword\">const</span> agentSession <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Session<span class=\"token punctuation\">.</span><span class=\"token function\">createNewSession</span><span class=\"token punctuation\">(</span>\n   req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">,</span>\n   userSession<span class=\"token punctuation\">.</span><span class=\"token function\">getTenantId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n   userSession<span class=\"token punctuation\">.</span><span class=\"token function\">getRecipeUserId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n   <span class=\"token punctuation\">{</span>\n     agent_id<span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">agent:</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>req<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>agentName<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n     act<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> sub<span class=\"token operator\">:</span> userSession<span class=\"token punctuation\">.</span><span class=\"token function\">getUserId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>   <span class=\"token comment\">// RFC 8693 delegation claim</span>\n     scopes<span class=\"token operator\">:</span> requested<span class=\"token punctuation\">,</span>\n     task_id<span class=\"token operator\">:</span> req<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>taskId<span class=\"token punctuation\">,</span>\n     token_type<span class=\"token operator\">:</span> <span class=\"token string\">\"agent\"</span><span class=\"token punctuation\">,</span>\n   <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\n res<span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> agentSessionHandle<span class=\"token operator\">:</span> agentSession<span class=\"token punctuation\">.</span><span class=\"token function\">getHandle</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"token-exchange-endpoint-for-capability-tokens\" style=\"position:relative;\"><a href=\"#token-exchange-endpoint-for-capability-tokens\" aria-label=\"token exchange endpoint for capability tokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>Token-Exchange Endpoint for Capability Tokens</strong></h3>\n<p>SuperTokens does not expose an RFC 8693 grant type natively, so the exchange is a custom endpoint built on top of session verification. It verifies the agent session, narrows again, and signs a short-lived token bound to one audience and one DPoP key:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"63003227469419930000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import jwt from &quot;jsonwebtoken&quot;;\n\n\napp.post(&quot;/agent/exchange&quot;, verifySession(), async (req, res) => {\n const payload = req.session!.getAccessTokenPayload();\n if (payload.token_type !== &quot;agent&quot;) {\n   return res.status(403).json({ error: &quot;not_an_agent_session&quot; });\n }\n\n\n const agentScopes: string[] = payload.scopes ?? [];\n const requested: string[] = req.body.scopes ?? [];\n const escalation = requested.filter((s) => !agentScopes.includes(s));\n if (escalation.length > 0) {\n   return res.status(403).json({ error: &quot;scope_escalation&quot;, escalation });\n }\n\n\n const capabilityToken = jwt.sign(\n   {\n     sub: payload.agent_id,\n     act: payload.act,\n     tenant: req.session!.getTenantId(),\n     aud: req.body.audience,          // exactly one tool server\n     scopes: requested,\n     task_id: payload.task_id,\n     cnf: { jkt: req.body.dpopKeyThumbprint },\n   },\n   process.env.CAPABILITY_SIGNING_KEY!,\n   { algorithm: &quot;RS256&quot;, expiresIn: &quot;120s&quot; },\n );\n\n\n res.json({\n   access_token: capabilityToken,\n   token_type: &quot;DPoP&quot;,\n   expires_in: 120,\n   issued_token_type: &quot;urn:ietf:params:oauth:token-type:access_token&quot;,\n });\n});`, `63003227469419930000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> jwt <span class=\"token keyword\">from</span> <span class=\"token string\">\"jsonwebtoken\"</span><span class=\"token punctuation\">;</span>\n\n\napp<span class=\"token punctuation\">.</span><span class=\"token function\">post</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/agent/exchange\"</span><span class=\"token punctuation\">,</span> <span class=\"token function\">verifySession</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n <span class=\"token keyword\">const</span> payload <span class=\"token operator\">=</span> req<span class=\"token punctuation\">.</span>session<span class=\"token operator\">!</span><span class=\"token punctuation\">.</span><span class=\"token function\">getAccessTokenPayload</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>payload<span class=\"token punctuation\">.</span>token_type <span class=\"token operator\">!==</span> <span class=\"token string\">\"agent\"</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n   <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">status</span><span class=\"token punctuation\">(</span><span class=\"token number\">403</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> error<span class=\"token operator\">:</span> <span class=\"token string\">\"not_an_agent_session\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n <span class=\"token punctuation\">}</span>\n\n\n <span class=\"token keyword\">const</span> agentScopes<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> payload<span class=\"token punctuation\">.</span>scopes <span class=\"token operator\">??</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">const</span> requested<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> req<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>scopes <span class=\"token operator\">??</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">const</span> escalation <span class=\"token operator\">=</span> requested<span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token operator\">!</span>agentScopes<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span>s<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>escalation<span class=\"token punctuation\">.</span>length <span class=\"token operator\">></span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n   <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">status</span><span class=\"token punctuation\">(</span><span class=\"token number\">403</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> error<span class=\"token operator\">:</span> <span class=\"token string\">\"scope_escalation\"</span><span class=\"token punctuation\">,</span> escalation <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n <span class=\"token punctuation\">}</span>\n\n\n <span class=\"token keyword\">const</span> capabilityToken <span class=\"token operator\">=</span> jwt<span class=\"token punctuation\">.</span><span class=\"token function\">sign</span><span class=\"token punctuation\">(</span>\n   <span class=\"token punctuation\">{</span>\n     sub<span class=\"token operator\">:</span> payload<span class=\"token punctuation\">.</span>agent_id<span class=\"token punctuation\">,</span>\n     act<span class=\"token operator\">:</span> payload<span class=\"token punctuation\">.</span>act<span class=\"token punctuation\">,</span>\n     tenant<span class=\"token operator\">:</span> req<span class=\"token punctuation\">.</span>session<span class=\"token operator\">!</span><span class=\"token punctuation\">.</span><span class=\"token function\">getTenantId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n     aud<span class=\"token operator\">:</span> req<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>audience<span class=\"token punctuation\">,</span>          <span class=\"token comment\">// exactly one tool server</span>\n     scopes<span class=\"token operator\">:</span> requested<span class=\"token punctuation\">,</span>\n     task_id<span class=\"token operator\">:</span> payload<span class=\"token punctuation\">.</span>task_id<span class=\"token punctuation\">,</span>\n     cnf<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> jkt<span class=\"token operator\">:</span> req<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>dpopKeyThumbprint <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n   <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n   process<span class=\"token punctuation\">.</span>env<span class=\"token punctuation\">.</span><span class=\"token constant\">CAPABILITY_SIGNING_KEY</span><span class=\"token operator\">!</span><span class=\"token punctuation\">,</span>\n   <span class=\"token punctuation\">{</span> algorithm<span class=\"token operator\">:</span> <span class=\"token string\">\"RS256\"</span><span class=\"token punctuation\">,</span> expiresIn<span class=\"token operator\">:</span> <span class=\"token string\">\"120s\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\n res<span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n   access_token<span class=\"token operator\">:</span> capabilityToken<span class=\"token punctuation\">,</span>\n   token_type<span class=\"token operator\">:</span> <span class=\"token string\">\"DPoP\"</span><span class=\"token punctuation\">,</span>\n   expires_in<span class=\"token operator\">:</span> <span class=\"token number\">120</span><span class=\"token punctuation\">,</span>\n   issued_token_type<span class=\"token operator\">:</span> <span class=\"token string\">\"urn:ietf:params:oauth:token-type:access_token\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"an-authenticated-mcp-server\" style=\"position:relative;\"><a href=\"#an-authenticated-mcp-server\" aria-label=\"an authenticated mcp server permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>An Authenticated MCP Server</strong></h3>\n<p>The MCP plugin wraps <code class=\"language-text\">@modelcontextprotocol/sdk</code> and applies SuperTokens authentication to each request, making the verified token payload available inside every tool handler via <code class=\"language-text\">extra.authInfo</code>:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"69716962801704985000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`import OAuth2Provider from &quot;supertokens-node/recipe/oauth2provider&quot;;\nimport { UserRoleClaim } from &quot;supertokens-node/recipe/userroles&quot;;\nimport SuperTokensMcpPlugin, {\n SuperTokensMcpServer,\n} from &quot;supertokens-mcp-plugin&quot;;\nimport supertokens from &quot;supertokens-node&quot;;\n\n\nconst server = new SuperTokensMcpServer({\n name: &quot;triage-mcp&quot;,\n version: &quot;1.0.0&quot;,\n path: &quot;/mcp&quot;,\n validateTokenPayload: async (accessTokenPayload, _userContext) => {\n   // Reject anything that is not an agent-derived token. Check the plugin's\n   // exported types for the exact non-OK status shape in your version.\n   if (accessTokenPayload.token_type !== &quot;agent&quot;) {\n     throw new Error(&quot;agent token required&quot;);\n   }\n   return { status: &quot;OK&quot; };\n },\n claimValidators: [UserRoleClaim.validators.includes(&quot;agent-operator&quot;)],\n});\n\n\nserver.registerTool(\n &quot;label-issue&quot;,\n { inputSchema: {}, description: &quot;Apply a label to a GitHub issue&quot; },\n async (args, extra) => {\n   const claims = extra.authInfo;\n   validate_audience(claims);\n   // DPoP proof verification requires raw request headers. \n   // Wire this at the HTTP middleware layer before requests reach the MCP handler.\n   if (session_is_revoked(claims)) throw new Error(&quot;session revoked&quot;);\n   const decision = authorize_tool_call(claims, &quot;github.issues.label&quot;, &quot;repo:acme-org/payments-service&quot;, {});\n   if (!decision.allowed) throw new Error(decision.reason);\n   return { content: [{ type: &quot;text&quot;, text: &quot;labelled&quot; }] };\n },\n);\n\n\nsupertokens.init({\n supertokens: { connectionURI: &quot;<CONNECTION_URI>&quot;, apiKey: &quot;<API_KEY>&quot; },\n appInfo: {\n   appName: &quot;<APP_NAME>&quot;,\n   apiDomain: &quot;<API_DOMAIN>&quot;,\n   websiteDomain: &quot;<WEBSITE_DOMAIN>&quot;,\n },\n recipeList: [\n   OAuth2Provider.init(),   // required for the MCP authorization flow\n ],\n experimental: {\n   plugins: [SuperTokensMcpPlugin.init({ mcpServers: [server] })],\n },\n});`, `69716962801704985000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"typescript\"><pre class=\"language-typescript\"><code class=\"language-typescript\"><span class=\"token keyword\">import</span> OAuth2Provider <span class=\"token keyword\">from</span> <span class=\"token string\">\"supertokens-node/recipe/oauth2provider\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> UserRoleClaim <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"supertokens-node/recipe/userroles\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> SuperTokensMcpPlugin<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n SuperTokensMcpServer<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"supertokens-mcp-plugin\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> supertokens <span class=\"token keyword\">from</span> <span class=\"token string\">\"supertokens-node\"</span><span class=\"token punctuation\">;</span>\n\n\n<span class=\"token keyword\">const</span> server <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">SuperTokensMcpServer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n name<span class=\"token operator\">:</span> <span class=\"token string\">\"triage-mcp\"</span><span class=\"token punctuation\">,</span>\n version<span class=\"token operator\">:</span> <span class=\"token string\">\"1.0.0\"</span><span class=\"token punctuation\">,</span>\n path<span class=\"token operator\">:</span> <span class=\"token string\">\"/mcp\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token function-variable function\">validateTokenPayload</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>accessTokenPayload<span class=\"token punctuation\">,</span> _userContext<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n   <span class=\"token comment\">// Reject anything that is not an agent-derived token. Check the plugin's</span>\n   <span class=\"token comment\">// exported types for the exact non-OK status shape in your version.</span>\n   <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>accessTokenPayload<span class=\"token punctuation\">.</span>token_type <span class=\"token operator\">!==</span> <span class=\"token string\">\"agent\"</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n     <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"agent token required\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n   <span class=\"token punctuation\">}</span>\n   <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span> status<span class=\"token operator\">:</span> <span class=\"token string\">\"OK\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n claimValidators<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>UserRoleClaim<span class=\"token punctuation\">.</span>validators<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"agent-operator\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\nserver<span class=\"token punctuation\">.</span><span class=\"token function\">registerTool</span><span class=\"token punctuation\">(</span>\n <span class=\"token string\">\"label-issue\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">{</span> inputSchema<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> description<span class=\"token operator\">:</span> <span class=\"token string\">\"Apply a label to a GitHub issue\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>args<span class=\"token punctuation\">,</span> extra<span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n   <span class=\"token keyword\">const</span> claims <span class=\"token operator\">=</span> extra<span class=\"token punctuation\">.</span>authInfo<span class=\"token punctuation\">;</span>\n   <span class=\"token function\">validate_audience</span><span class=\"token punctuation\">(</span>claims<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n   <span class=\"token comment\">// DPoP proof verification requires raw request headers. </span>\n   <span class=\"token comment\">// Wire this at the HTTP middleware layer before requests reach the MCP handler.</span>\n   <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">session_is_revoked</span><span class=\"token punctuation\">(</span>claims<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"session revoked\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n   <span class=\"token keyword\">const</span> decision <span class=\"token operator\">=</span> <span class=\"token function\">authorize_tool_call</span><span class=\"token punctuation\">(</span>claims<span class=\"token punctuation\">,</span> <span class=\"token string\">\"github.issues.label\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"repo:acme-org/payments-service\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n   <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>decision<span class=\"token punctuation\">.</span>allowed<span class=\"token punctuation\">)</span> <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span>decision<span class=\"token punctuation\">.</span>reason<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n   <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span> content<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">{</span> type<span class=\"token operator\">:</span> <span class=\"token string\">\"text\"</span><span class=\"token punctuation\">,</span> text<span class=\"token operator\">:</span> <span class=\"token string\">\"labelled\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\nsupertokens<span class=\"token punctuation\">.</span><span class=\"token function\">init</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n supertokens<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> connectionURI<span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;CONNECTION_URI>\"</span><span class=\"token punctuation\">,</span> apiKey<span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;API_KEY>\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n appInfo<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n   appName<span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;APP_NAME>\"</span><span class=\"token punctuation\">,</span>\n   apiDomain<span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;API_DOMAIN>\"</span><span class=\"token punctuation\">,</span>\n   websiteDomain<span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;WEBSITE_DOMAIN>\"</span><span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n recipeList<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n   OAuth2Provider<span class=\"token punctuation\">.</span><span class=\"token function\">init</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>   <span class=\"token comment\">// required for the MCP authorization flow</span>\n <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n experimental<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n   plugins<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>SuperTokensMcpPlugin<span class=\"token punctuation\">.</span><span class=\"token function\">init</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> mcpServers<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>server<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>DPoP verification and the PDP call are not SuperTokens features. They belong in your tool server, using the Python implementations shown earlier or their Node equivalents, running before the tool body executes.</p>\n<h2 id=\"faq\" style=\"position:relative;\"><a href=\"#faq\" aria-label=\"faq permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>FAQ</h2>\n<h3 id=\"what-is-the-difference-between-ai-agent-authentication-and-ai-agent-authorization\" style=\"position:relative;\"><a href=\"#what-is-the-difference-between-ai-agent-authentication-and-ai-agent-authorization\" aria-label=\"what is the difference between ai agent authentication and ai agent authorization permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>What is the difference between AI agent authentication and AI agent authorization?</strong></h3>\n<p>Authentication establishes which agent is calling and which user it acts for. Authorization is two separate questions: whether the token carries the right scope, and whether policy permits this specific call with these arguments right now. Most agent incidents involve a correctly authenticated agent whose specific action nobody checked.</p>\n<h3 id=\"do-i-need-oauth-21-for-my-mcp-server\" style=\"position:relative;\"><a href=\"#do-i-need-oauth-21-for-my-mcp-server\" aria-label=\"do i need oauth 21 for my mcp server permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>Do I need OAuth 2.1 for my MCP server?</strong></h3>\n<p>For a remote MCP server, yes. The spec requires OAuth 2.1, mandates RFC 8707 Resource Indicators for audience binding, and requires protected resource metadata at <code class=\"language-text\">/.well-known/oauth-protected-resource</code>. Local stdio servers are the exception; credentials arrive from the host process.</p>\n<h3 id=\"what-is-dpop-and-why-does-it-matter-for-ai-agents-specifically\" style=\"position:relative;\"><a href=\"#what-is-dpop-and-why-does-it-matter-for-ai-agents-specifically\" aria-label=\"what is dpop and why does it matter for ai agents specifically permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>What is DPoP and why does it matter for AI agents specifically?</strong></h3>\n<p>DPoP binds a token to a key the caller proves possession of on every request, so a stolen token alone is useless. It matters more for agents than browsers because agent tokens travel to many third-party tool servers over long-running tasks, multiplying interception risk. DPoP works without the certificate infrastructure mTLS requires.</p>\n<h3 id=\"can-an-ai-agents-token-have-more-access-than-the-user-who-invoked-it\" style=\"position:relative;\"><a href=\"#can-an-ai-agents-token-have-more-access-than-the-user-who-invoked-it\" aria-label=\"can an ai agents token have more access than the user who invoked it permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>Can an AI agent’s token have more access than the user who invoked it?</strong></h3>\n<p>It must not. Scopes narrow at every RFC 8693 token exchange and never widen, enforced in code at each token-issuing endpoint. An agent requesting a scope its parent session does not hold is a signal worth alerting on.</p>\n<h3 id=\"what-happens-if-an-agents-token-is-stolen-mid-session\" style=\"position:relative;\"><a href=\"#what-happens-if-an-agents-token-is-stolen-mid-session\" aria-label=\"what happens if an agents token is stolen mid session permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a><strong>What happens if an agent’s token is stolen mid-session?</strong></h3>\n<p>Three controls compound. Short expiry bounds the window. DPoP makes the token unusable without the bound key. CAEP pushes a revocation event to tool servers so in-flight tokens are dropped immediately. Audience restriction limits replay to one tool server.</p>\n<h2 id=\"summary\" style=\"position:relative;\"><a href=\"#summary\" aria-label=\"summary permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Summary</h2>\n<p>AI agent authentication is three layers, and most implementations build two. Authentication answers who is calling. Scoped, sender-constrained, audience-restricted tokens answer what category of action the caller may take. Per-action policy evaluated at the tool server answers whether this specific call should proceed.</p>\n<p>The Yue incident cleared the first two layers and needed the third. Every constraint that mattered lived in a context window that compaction summarized away, and stop commands travelled through the same failing loop. Constraints belong in signed tokens and in policy evaluated outside the agent. Control channels belong outside the channel being controlled.</p>","frontmatter":{"date":"August 02, 2026","title":"AI Agent Authentication: Scoped Tokens, MCP, and Per-Action Authorization","cover":"auth-for-ai-agents.png","author":"Mostafa Ibrahim","description":"AI agent authentication explained: scoped tokens, MCP OAuth 2.1 requirements, and per-action authorization gates."},"fields":{"slug":"/auth-for-ai-agents/"}},"site":{"siteMetadata":{"title":"SuperTokens Blog"}}},"pageContext":{"id":"179a44ca-0f4c-58e3-865e-2a56629ae7f2","fields__slug":"/auth-for-ai-agents/","__params":{"fields__slug":"auth-for-ai-agents"}}},
    "staticQueryHashes": []}