<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akash Devdhar</title>
    <description>The latest articles on DEV Community by Akash Devdhar (@neospeed83).</description>
    <link>https://dev.to/neospeed83</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4059095%2Fdb1c8fab-81c1-4bb3-a8da-da71cd7abc12.jpg</url>
      <title>DEV Community: Akash Devdhar</title>
      <link>https://dev.to/neospeed83</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neospeed83"/>
    <language>en</language>
    <item>
      <title>Authentication vs Authorization: Why Most AI Apps Get It Wrong</title>
      <dc:creator>Akash Devdhar</dc:creator>
      <pubDate>Sat, 22 Aug 2026 15:14:02 +0000</pubDate>
      <link>https://dev.to/neospeed83/authentication-vs-authorization-why-most-ai-apps-get-it-wrong-3kmg</link>
      <guid>https://dev.to/neospeed83/authentication-vs-authorization-why-most-ai-apps-get-it-wrong-3kmg</guid>
      <description>&lt;p&gt;Here is the failure mode I am seeing over and over in AI app reviews. The app checks who you are exactly once, at login, and after that it treats the agent as if it has a blank check for the rest of the session. Read a file, sure. Send an email, sure. Delete a record, why not, you logged in twenty minutes ago only. That is not an authorization model, that is a login screen pretending to be one.&lt;/p&gt;

&lt;p&gt;Authentication and authorization are not the same question asked twice, they are two entirely different questions, and most AI apps are answering the first one and quietly skipping the second, forever, for the whole session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one question that gets asked, and the one that doesn't
&lt;/h2&gt;

&lt;p&gt;Authentication happens once, at the door: who is this. The app checks a password, a session cookie, an SSO token, whatever, and it is satisfied. Fine so far, this part most teams actually get right.&lt;/p&gt;

&lt;p&gt;Authorization is supposed to happen continuously, at every door inside the building: now that we know who this is, should this specific action, on this specific resource, right now, be allowed. This is the part that AI apps are skipping basically. Traditional web apps got away with checking authorization loosely because a human was the one clicking buttons, slowly, with natural friction built into every step. An AI agent has no such friction. It can chain fifteen tool calls in three seconds, and if your authorization model is "well, they logged in," every single one of those fifteen calls just inherited the same blank check.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this actually looks like when it breaks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.akashdevdhar.com%2Fdiagrams%2Fdiagram-1-one-time-gate.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.akashdevdhar.com%2Fdiagrams%2Fdiagram-1-one-time-gate.png" alt="The one-time gate model most AI apps are actually running" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Say your AI assistant is authenticated as a support engineer who can read customer tickets. Somewhere in its tool list, there is also a "run_sql_query" tool, because someone needed it for a debugging session six months ago and nobody removed it. The authentication layer sees a valid, logged-in support engineer. It has no concept of "this specific tool call, on this specific table, for this specific reason, right now." So the agent runs the query. Not because anyone decided the support engineer should have that access for that task, but because nobody built the layer that would have stopped it.&lt;/p&gt;

&lt;p&gt;This is exactly the gap that RBAC and, better, scoped OAuth tokens are supposed to close, and it is also exactly the gap that gets silently reintroduced when a team wires an agent up with one long-lived service account "to keep things simple."&lt;/p&gt;

&lt;h2&gt;
  
  
  What continuous authorization actually looks like
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.akashdevdhar.com%2Fdiagrams%2Fdiagram-2-continuous-authz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.akashdevdhar.com%2Fdiagrams%2Fdiagram-2-continuous-authz.png" alt="Continuous, per-action authorization for an agent" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difference is not exotic. Every tool call the agent makes should carry a scope, and something on the other end should actually check that scope before doing anything, every single time, not just once at session start. This is what OAuth 2.0 scopes were built for, and it is what the newer Rich Authorization Requests spec (RFC 9396) extends further, letting you express authorization at the level of "read tickets in this one project" rather than a flat "read tickets, all of them, everywhere."&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick code example
&lt;/h2&gt;

&lt;p&gt;Here is roughly what the one-time-gate version looks like, which is unfortunately close to what a lot of agent frameworks default to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Checked once, at session start, then trusted for everything after
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_authenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not logged in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the per-action version, where the token's scope is actually checked against what the specific tool call is trying to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;  &lt;span class="c1"&gt;# short lived, scoped, from the OAuth flow
&lt;/span&gt;    &lt;span class="n"&gt;required_scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOOL_SCOPE_MAP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# e.g. "tickets:read"
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;required_scope&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token lacks scope: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;required_scope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;resource_matches_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# e.g. token is scoped to project_id=42, but the call targets project_id=17
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource outside token&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s authorized scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_expired&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token expired, re-authorize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version has more code, I know, but every one of those checks is answering a question the first version never even asked. Which tool, on which resource, under which still-valid grant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standards worth reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RFC 6749&lt;/strong&gt;, The OAuth 2.0 Authorization Framework, for the base scope model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 9396&lt;/strong&gt;, OAuth 2.0 Rich Authorization Requests, for expressing authorization at a finer grain than a flat scope string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NIST SP 800-207&lt;/strong&gt;, Zero Trust Architecture, for the broader principle this post is really just a specific case of: never trust a request just because it came from an already-authenticated session, verify it again for the specific action being taken.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Logging someone in, or logging an agent in, answers exactly one question and that question is not "should this happen." If your AI app's entire authorization model is "well, the session is valid," you do not have authorization at all, you have authentication wearing a bigger hat. Check the action, not just the actor, and check it every time, not just at the door.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Akash Devdhar is a Senior Software Engineer specializing in enterprise identity, authentication, authorization, and AI infrastructure. He writes about building secure AI systems using OAuth, OIDC, RBAC, and modern identity architectures.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>authorization</category>
      <category>ai</category>
      <category>oauth</category>
    </item>
    <item>
      <title>OAuth, OIDC, SAML, JWT: A Practical Guide for Modern Engineers</title>
      <dc:creator>Akash Devdhar</dc:creator>
      <pubDate>Sat, 15 Aug 2026 18:19:56 +0000</pubDate>
      <link>https://dev.to/neospeed83/oauth-oidc-saml-jwt-a-practical-guide-for-modern-engineers-4ofl</link>
      <guid>https://dev.to/neospeed83/oauth-oidc-saml-jwt-a-practical-guide-for-modern-engineers-4ofl</guid>
      <description>&lt;p&gt;I am asking engineers in interviews to explain the difference between OAuth and OIDC, and half the time I am getting the same answer basically: "they are both for login." That answer is wrong, and it is not a small wrong, it is the exact kind of wrong that is now showing up in how people are wiring up AI agents. Four acronyms, four different jobs, and treating them as one blob is where the actual security bugs come from.&lt;/p&gt;

&lt;p&gt;Let me put it plainly. SAML and OIDC answer "who are you." OAuth answers "what are you allowed to do." JWT is not a protocol at all, it is just a format, a container that any of the above can choose to put its output in. If you remember only one sentence from this post, remember that one, full stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication versus authorization, actually explained
&lt;/h2&gt;

&lt;p&gt;Every confused conversation I have had on this topic traces back to mixing up two different questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt; is proving identity. You log into your company's SSO portal, it checks your credentials (maybe with an MFA prompt), and it tells the application "yes, this is genuinely Priya, verified." SAML and OIDC both live here. They are competing standards for the same job, not complements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt; is a separate question that comes after: now that we know it is Priya, what is she actually permitted to do. Can she read this calendar. Can she call this API. Can her AI agent, acting for her, delete a record. OAuth 2.0 lives here, and only here. OAuth was never designed to answer "who is this," which is exactly why bolting OIDC on top of it (which is literally what OIDC is, an identity layer added on top of OAuth 2.0) was necessary in the first place.&lt;/p&gt;

&lt;p&gt;JWT (RFC 7519) sits underneath both, as a token format. An OIDC ID token can be a JWT. An OAuth access token can be a JWT, or it can be an opaque reference string, both are valid, people forget this. SAML, being older, uses XML assertions instead of JWT, which is one of the practical reasons SAML feels clunky to work with compared to the newer JSON-based standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each one actually shows up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SAML&lt;/strong&gt;: enterprise SSO, the classic case being logging into Salesforce or Workday through your company's identity provider. Heavy XML, browser redirect based, still everywhere in large enterprises even though nobody is excited about it anymore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OIDC&lt;/strong&gt;: modern consumer and enterprise login, "Sign in with Google" being the most familiar example. Built on OAuth 2.0, uses JSON and JWTs, much lighter than SAML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OAuth 2.0&lt;/strong&gt;: delegated authorization, letting an app or an agent act with limited permission on your behalf, without ever handing over your password. This is the one that matters most once agents enter the picture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT&lt;/strong&gt;: the token format that carries the actual claims (who, what scope, when it expires) in a compact, signable, verifiable way. Not a standard for login or authorization by itself, just the envelope.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this confusion gets expensive with AI agents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrgg7ofzqo2x05vg69jy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrgg7ofzqo2x05vg69jy.png" alt="Four protocols, four different jobs" width="720" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the pattern I am actually seeing go wrong. A team builds an AI agent, the agent needs to call three internal services, and someone reaches for "let's just use SAML" or worse, "let's just give it a JWT" as if JWT alone was a security model. JWT is not a security model, it is a shipping container, what matters is who signed it, what claims are inside it, and who is checking those claims before trusting the box. An unsigned or unverified JWT is basically a sticky note that says "trust me."&lt;/p&gt;

&lt;p&gt;The pattern that actually works: OIDC (or SAML, if you are stuck in an enterprise IdP that has not modernized) authenticates the human who is delegating work to the agent. OAuth 2.0, specifically client credentials or token exchange (RFC 8693) grants, authorizes the agent for a narrow, expiring scope. The resulting access token happens to be encoded as a JWT, so the resource server can verify it without a network round trip back to the issuer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdwxqn4cvcu3fqgvaxo15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdwxqn4cvcu3fqgvaxo15.png" alt="How the four pieces chain together for an agent request" width="760" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice that JWT is not doing any of the actual security work in that second diagram. It is the format the token happens to travel in. The security work is being done by OIDC establishing who the human is, and OAuth deciding what the agent gets to do on that human's behalf, for how long.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick code example
&lt;/h2&gt;

&lt;p&gt;Decoding (not verifying, an important distinction) a JWT to see what is actually inside one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;  &lt;span class="c1"&gt;# PyJWT
&lt;/span&gt;
&lt;span class="c1"&gt;# NEVER trust a token just because you can decode it.
# This only shows you the claims, it proves nothing by itself.
&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify_signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# {'sub': 'agent-invoice-reader', 'scope': 'invoices:read',
#  'aud': 'https://api.internal-service.com', 'exp': 1735689600,
#  'iss': 'https://idp.example.com'}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is the part people skip, actually verifying it before trusting anything in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PyJWKClient&lt;/span&gt;

&lt;span class="n"&gt;jwks_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PyJWKClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://idp.example.com/.well-known/jwks.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signing_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwks_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_signing_key_from_jwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signing_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;algorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.internal-service.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://idp.example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Now the claims are actually trustworthy, because the signature,
# audience, and issuer were all checked, not just the payload read.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second snippet is the whole point of this post basically. Decoding is free and proves nothing. Verifying against a trusted issuer's public key is the actual work, and it is where most homegrown "just use JWT" implementations quietly skip a step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standards worth reading, not just skimming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SAML 2.0&lt;/strong&gt; (OASIS standard) for the XML assertion based SSO model, still relevant in large enterprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenID Connect Core 1.0&lt;/strong&gt; for how identity got layered on top of OAuth 2.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 6749&lt;/strong&gt;, The OAuth 2.0 Authorization Framework, for the base delegation model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 7519&lt;/strong&gt;, JSON Web Token (JWT), for the token format itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 8693&lt;/strong&gt;, OAuth 2.0 Token Exchange, for the agent-to-agent delegation case specifically.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Stop treating these four as synonyms with different logos. SAML and OIDC prove who someone is. OAuth decides what they, or their agent, can do. JWT is just the envelope the decision travels in, not the decision itself. The moment you are clear on which of these three jobs you are actually solving for, half the "how do we secure our AI agents" confusion in your team's Slack channel basically disappears on its own.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Akash Devdhar is a Senior Software Engineer specializing in enterprise identity, authentication, authorization, and AI infrastructure. He writes about building secure AI systems using OAuth, OIDC, RBAC, and modern identity architectures.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>oidc</category>
      <category>saml</category>
      <category>jwt</category>
    </item>
    <item>
      <title>Why API Keys Are Not an Identity Strategy</title>
      <dc:creator>Akash Devdhar</dc:creator>
      <pubDate>Mon, 10 Aug 2026 20:20:57 +0000</pubDate>
      <link>https://dev.to/neospeed83/why-api-keys-are-not-an-identity-strategy-4oa5</link>
      <guid>https://dev.to/neospeed83/why-api-keys-are-not-an-identity-strategy-4oa5</guid>
      <description>&lt;p&gt;Every team building AI agents right now is basically doing the same thing: they generate a long random string, put it in an environment variable, call it “authentication,” and move on. I am seeing this pattern in almost every AI infrastructure discussion I am part of, and I want to say clearly: an API key is a secret, not an identity. Treating it as the same thing is a disaster waiting to happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The confusion, explained simply
&lt;/h2&gt;

&lt;p&gt;A secret answers one question: do you know this string or not? An identity answers a completely different set of questions. Who are you, actually? What are you allowed to do? Who granted you that permission, and for how long? When an API key is your whole security model, you have answered the first question and quietly skipped the other three.&lt;/p&gt;

&lt;p&gt;This was tolerable when the caller was a predictable backend service that your own team wrote and deployed. It is not tolerable when the caller is an AI agent that is making autonomous decisions, chaining tool calls, and sometimes acting on behalf of a human user it has never directly authenticated. The agent is not “your service” anymore in the old sense. It is closer to a semi-independent actor that needs its own verifiable identity, its own scoped permissions, and its own audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the API key model actually breaks
&lt;/h2&gt;

&lt;p&gt;Let me list where I am seeing the cracks, without pretending this is some neat universal law:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No delegation.&lt;/strong&gt; An API key cannot say “I am acting on behalf of user X, with X’s consent, for the next 15 minutes.” It is a flat, all-or-nothing credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No expiry that means anything.&lt;/strong&gt; Most API keys live forever until someone remembers to rotate them, which basically means never.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audience or scope binding.&lt;/strong&gt; The same key that reads a customer record can usually also delete it. There is no built-in concept of “this token is only valid for this one resource server, for this one action.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terrible audit story.&lt;/strong&gt; When three different AI agents share the same key—and in practice, they often do—your logs cannot tell you which agent actually did what. You get a name in a log line, not a proof.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation is a blunt instrument.&lt;/strong&gt; You cannot revoke “this one agent’s access to this one tool” without breaking everything else that shares the key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a new problem, actually. Enterprise software went through this exact argument fifteen years ago when everyone moved off shared static credentials toward OAuth-based delegated access. What is different now is that the caller on the other end is not a human clicking “allow” on a consent screen; it is an autonomous agent, and the whole flow needs to happen without a human sitting there approving each step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an identity-based model looks like instead
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdjujp6h1aa2em01pey3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdjujp6h1aa2em01pey3t.png" alt="API key model versus identity model" width="700" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the API key model above, there is nothing to reason about. The key either works or it does not, and once it is compromised, everything behind it is compromised also. Now compare this to what a proper identity flow looks like for an agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffzyqpf1bm49080nis214.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffzyqpf1bm49080nis214.png" alt="Identity based token flow for an AI agent" width="760" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important shift here is that the agent is not holding a permanent secret that grants everything. It is holding a token, and that token is a claim about identity, scope, and time. The resource server does not trust the token because it is hard to guess; it trusts the token because it is signed by an identity provider it already trusts, and it can inspect exactly what the token is allowed to do before it does anything.&lt;/p&gt;

&lt;p&gt;This is where OAuth 2.0 client credentials grant, or better, OAuth 2.0 token exchange (RFC 8693), becomes relevant for agent-to-agent and agent-to-tool calls. Token exchange in particular is built for exactly this situation: an agent that holds one token needs to call a downstream service with a narrower, more specific token, possibly acting on behalf of the original user. That is delegation done properly, not a shared secret pretending to be delegation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick code comparison
&lt;/h2&gt;

&lt;p&gt;Here is roughly what the API key version looks like. Simple, and also the whole problem in one snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The "identity strategy" most agents ship with today
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.internal-service.com/v1/customer-records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_live_9f8a7...never_rotated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# No subject, no scope, no expiry, no way to tell which agent called this.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now compare with a token acquired through client credentials and passed as a bearer token, where the token itself carries claims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;token_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://idp.example.com/oauth/token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grant_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_credentials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent-invoice-reader&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AGENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# short lived, rotated, scoped to this one agent
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoices:read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audience&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.internal-service.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.internal-service.com/v1/customer-records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# The token carries sub, scope, aud, and exp claims.
# The resource server can verify all four before doing anything.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version has more moving parts; I am not denying that. But those moving parts are exactly what let you answer “which agent did this, was it allowed to, and for how long” without guessing. If you decode that access token (it is typically a JWT, RFC 7519), you get a &lt;code&gt;sub&lt;/code&gt; claim identifying the client, a &lt;code&gt;scope&lt;/code&gt; claim limiting what it can do, an &lt;code&gt;aud&lt;/code&gt; claim binding it to one resource server, and an &lt;code&gt;exp&lt;/code&gt; claim that makes the whole thing self-expiring. An API key gives you none of these.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standards worth actually reading
&lt;/h2&gt;

&lt;p&gt;If you are designing this for real, three documents are worth your time and not just a skim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RFC 6749&lt;/strong&gt;, The OAuth 2.0 Authorization Framework, for the base grant types including client credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 8693&lt;/strong&gt;, OAuth 2.0 Token Exchange, which is basically the spec that AI agent architectures keep re-inventing badly when they do not use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RFC 7519&lt;/strong&gt;, JSON Web Token (JWT), for how claims-based tokens are structured and verified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenID Connect is also worth knowing here, though it solves a slightly different problem: authenticating a human end user. Most agent-to-service calls are closer to pure OAuth client credentials or token exchange territory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;An API key tells you that a caller knows a string. It does not tell you who the caller is, what it is allowed to do, or when that permission runs out. AI agents are autonomous enough now that this gap is not theoretical anymore; it is showing up as real incidents. If your agent’s entire security model is one static header value, you do not have an identity strategy—you have a shared password with extra steps.&lt;/p&gt;

&lt;p&gt;Treat your agents like the semi-independent actors they are: give them real, scoped, expiring identity, not a key copied into a config file three deployments ago.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>ai</category>
      <category>oauth</category>
      <category>security</category>
    </item>
    <item>
      <title>OAuth Was Designed for Humans. AI Agents Change the Rules.</title>
      <dc:creator>Akash Devdhar</dc:creator>
      <pubDate>Tue, 04 Aug 2026 18:18:52 +0000</pubDate>
      <link>https://dev.to/neospeed83/oauth-was-designed-for-humans-ai-agents-change-the-rules-2d7m</link>
      <guid>https://dev.to/neospeed83/oauth-was-designed-for-humans-ai-agents-change-the-rules-2d7m</guid>
      <description>&lt;p&gt;I will say it plainly. OAuth 2.0, as good as it is, was never built with the idea that the “user” on the other end could be a software agent taking fifty decisions a minute without anybody watching. We have been bending the spec to fit agents, when actually the spec was assuming a human sitting in front of a browser, clicking “Allow.” That assumption is now breaking, and most teams building AI agents have not fully understood how much.&lt;/p&gt;

&lt;p&gt;Let me explain what I mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The human assumption baked into OAuth
&lt;/h2&gt;

&lt;p&gt;Classic OAuth 2.0 authorization code flow goes something like this. A user clicks login, gets redirected to an identity provider, sees a consent screen, clicks “Allow,” and comes back with an authorization code that the app exchanges for a token. Every single step in this flow assumes a human is present to look at a screen and make a judgment call.&lt;/p&gt;

&lt;p&gt;Now put an AI agent in that loop. There is no browser session waiting on the agent’s side. There is no human eyeball reading “This app wants to access your calendar and email.” The agent is not clicking “Allow”; it is calling an API, and it needs to do this at 2 AM, unattended, sometimes chaining five or six calls to different services in a matter of seconds.&lt;/p&gt;

&lt;p&gt;The three-legged OAuth dance was built for exactly one thing: a human giving informed consent, in the moment, for a specific access grant. Agents break this in at least three ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually breaks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. No human present for consent at request time.&lt;/strong&gt; Consent has to be captured upfront, or delegated in advance, because the agent cannot pause mid-task and wait for someone to click a button. This changes consent from an interactive, moment-of-request thing into a policy that has to be defined beforehand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Identity gets layered, not singular.&lt;/strong&gt; In classic OAuth, the token represents “this user, authorized this app, for this scope.” With agents, you actually have three identities stacked together: the human who initiated the task, the agent that is executing it, and sometimes a sub-agent that the first agent spawned to do a smaller piece of work. Standard OAuth tokens were not built to carry this chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Session lifetime assumptions do not hold.&lt;/strong&gt; A human session might last an hour, a day at most, with natural boundaries like closing the laptop. An agent’s “session” could span a long-running workflow, spin off parallel sub-tasks, and outlive any reasonable human-session pattern, while still needing the same scoped, revocable access a human would get.&lt;/p&gt;

&lt;p&gt;Here is the classic flow first, stripped down to its four essential hops:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswg29fzdjcxay2fx9a0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswg29fzdjcxay2fx9a0n.png" alt="Classic OAuth 2.0 flow: user signs in, identity provider issues a token, app holds the token, app calls the resource API" width="680" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One human decision point, right at the start, and one hop from app to resource. Simple.&lt;/p&gt;

&lt;p&gt;Now look at what an agent delegation chain actually needs. The human still decides only once, upfront, but everything downstream has to be carried and enforced through tokens instead of another click:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2x2nbkuul1gbfl4jw0pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2x2nbkuul1gbfl4jw0pk.png" alt="Agent delegation chain: user delegates task, primary agent executes it, identity provider issues a short-lived token, a sub-agent is spawned, resource API receives the scoped token" width="680" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same one human decision at the top. But underneath it, two extra hops that a human never sees or approves in real time, and each one has to answer for itself through the token it is carrying, not through a person watching a screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually helps here
&lt;/h2&gt;

&lt;p&gt;The good news is, we are not starting from zero. RFC 8693, the OAuth 2.0 Token Exchange spec, already gives us a pattern for “on-behalf-of” tokens, where one party can exchange a token for a narrower, downstream token while preserving a chain of who is acting for whom. This maps almost perfectly onto agent delegation, if teams actually use it instead of just minting a fresh static key for every agent.&lt;/p&gt;

&lt;p&gt;A rough version of what this looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Agent requests a narrower, short-lived token to call a downstream API,
# on behalf of the user who originally delegated the task.
&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;token_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grant_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urn:ietf:params:oauth:grant-type:token-exchange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent_session_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject_token_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urn:ietf:params:oauth:token-type:access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requested_token_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urn:ietf:params:oauth:token-type:access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calendar.read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# narrower than the agent's own scope
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audience&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calendar-service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# explicit downstream target
&lt;/span&gt;    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;downstream_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# This token should be short-lived, auditable, and traceable
# back to both the agent and the human who delegated the task.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combine this with JWT-based claims (RFC 7519) carrying an explicit “acting on behalf of” field, and you get tokens that actually answer the question an auditor will ask you six months later: which agent did this, on whose authority, and with what scope?&lt;/p&gt;

&lt;p&gt;There is also real movement happening around authentication patterns specifically for MCP servers, since MCP is quickly becoming the way agents talk to tools and data sources. Worth watching this space closely, because whatever pattern wins there will likely become the default for agent-to-tool auth industry-wide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;OAuth is not broken; it is just incomplete for this new kind of actor. The fix is not to throw it out. It is to extend it: upfront consent instead of interactive consent, token exchange for layered identity instead of one flat token, and short-lived scoped grants instead of a static key living in an environment file for six months.&lt;/p&gt;

&lt;p&gt;If your agent architecture still treats the agent as “just another OAuth client” with a long-lived key, you are one incident away from finding out the hard way why the humans-only assumptions in OAuth mattered in the first place.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>ai</category>
      <category>security</category>
    </item>
    <item>
      <title>Authentication for AI Agents: Lessons We Can Borrow from Enterprise Identity</title>
      <dc:creator>Akash Devdhar</dc:creator>
      <pubDate>Sun, 02 Aug 2026 14:44:24 +0000</pubDate>
      <link>https://dev.to/neospeed83/authentication-for-ai-agents-lessons-we-can-borrow-from-enterprise-identity-4c2g</link>
      <guid>https://dev.to/neospeed83/authentication-for-ai-agents-lessons-we-can-borrow-from-enterprise-identity-4c2g</guid>
      <description>&lt;p&gt;Last week I was debugging an issue where our AI agent kept losing its session mid-task. And it struck me: we are basically solving the same problem enterprise IT teams solved 15 years back with SSO and IAM. The only difference is that this time the “user” logging in is not a human—it’s a piece of software making decisions on its own.&lt;/p&gt;

&lt;p&gt;Most teams building AI agents right now are treating auth as an afterthought. An API key hardcoded somewhere, one service account shared across ten agents, nobody knowing which agent did what action. I have seen this pattern more than once now, and honestly, it is a disaster waiting to happen.&lt;/p&gt;

&lt;p&gt;So let’s talk about what enterprise identity already figured out, and how we can borrow it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every identity, human or not, needs its own credential
&lt;/h2&gt;

&lt;p&gt;In the enterprise world, we don’t let two employees share one login. The audit trail becomes useless, accountability goes out the window, basically. The same logic applies to agents. If Agent A and Agent B are sharing one API key, and something goes wrong, how will you even trace back which agent did the damage? Each agent should get its own machine identity, full stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Short-lived tokens over static keys
&lt;/h2&gt;

&lt;p&gt;This one is not new. OAuth2 and SAML taught us this ages ago. A static API key sitting in an environment file for months is a ticking bomb. Agents should be requesting short-lived tokens, refreshing them, and the moment an agent’s task is done, that token should expire. Enterprise IAM tools like Okta or Azure AD do this for humans automatically; we need the equivalent for agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Least privilege is non-negotiable
&lt;/h2&gt;

&lt;p&gt;I know it’s tempting to give your agent broad access “just in case it needs it later.” But this is exactly the mistake enterprises made in the early days, before RBAC became standard practice. An agent that only needs to read a calendar should not have write access to your entire email account. Scope it down, always.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Delegated authority needs to be explicit
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting, actually. When an agent acts “on behalf of” a user, enterprise identity already has a pattern for this. OAuth’s delegation model, or even the older Kerberos constrained delegation. The agent should carry proof of who it’s acting for, not just its own identity. Otherwise, you can’t even answer the basic question: was this action authorized by the user, or did the agent go rogue on its own?&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Continuous verification, not one-time login
&lt;/h2&gt;

&lt;p&gt;Enterprises moved from “log in once, trust forever” to Zero Trust. Verify every request, every time, based on context. Agents are even more unpredictable than humans in their behaviour, so this applies double. Don’t authenticate an agent once at session start and forget about it; keep checking.&lt;/p&gt;

&lt;p&gt;Honestly, none of this is very new thinking. The identity and access management folks have already fought this battle, made the mistakes, and written the standards for it. We are just now applying the same to a new kind of actor in the system.&lt;/p&gt;

&lt;p&gt;What I am noticing is that a lot of AI infra teams are reinventing auth from scratch, when they could simply be adapting what already exists in the IAM world, with some tweaks for the fact that agents can act faster, more autonomously, and sometimes unpredictably compared to a human clicking buttons. Worth thinking about before the AI agent version of a credential leak becomes the next big headline.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>ai</category>
      <category>security</category>
    </item>
  </channel>
</rss>
