<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>GNAP on IAMDevBox</title><link>https://www.iamdevbox.com/tags/gnap/</link><description>Recent content in GNAP on IAMDevBox</description><image><title>IAMDevBox</title><url>https://www.iamdevbox.com/IAMDevBox.com.jpg</url><link>https://www.iamdevbox.com/IAMDevBox.com.jpg</link></image><generator>Hugo -- 0.146.0</generator><language>en-us</language><lastBuildDate>Tue, 03 Nov 2026 19:53:49 -0500</lastBuildDate><atom:link href="https://www.iamdevbox.com/tags/gnap/index.xml" rel="self" type="application/rss+xml"/><item><title>GNAP vs OAuth 2.0: Is RFC 9635 Ready for Production?</title><link>https://www.iamdevbox.com/posts/gnap-vs-oauth-2-grant-negotiation-protocol-production-readiness/</link><pubDate>Tue, 03 Nov 2026 19:30:00 +0000</pubDate><guid>https://www.iamdevbox.com/posts/gnap-vs-oauth-2-grant-negotiation-protocol-production-readiness/</guid><description>GNAP (RFC 9635) replaces OAuth 2.0 redirects, client_id registration and scopes with one JSON grant request; here is how it differs and when to adopt it.</description><content:encoded><![CDATA[<p>GNAP (the Grant Negotiation and Authorization Protocol, <a href="https://www.rfc-editor.org/rfc/rfc9635.html">RFC 9635</a>, published October 2024 by Justin Richer and Fabien Imbault) is a new delegation protocol, not an OAuth 2.0 extension. Instead of a redirect with <code>client_id</code> and <code>scope</code>, the client POSTs a JSON document describing what access it wants, how the user can be reached and which key it will sign with. Production readiness is the open question: the protocol is a Proposed Standard, but I could not find evidence of a mainstream identity provider shipping it. For today&rsquo;s production work, the better investment is hardening OAuth with <a href="/posts/oauth-21-security-best-practices-mandatory-pkce-and-token-binding/">PKCE and token binding</a> and <a href="/posts/dpop-next-gen-oauth-token-security/">DPoP</a>. GNAP is worth understanding now because its ideas keep leaking into those specs.</p>
<p>We also walked through this comparison on video: <a href="https://www.youtube.com/watch?v=D_iav-9m8sw">GNAP vs OAuth 2.0: Is the Grant Negotiation Protocol Ready for Production</a>.</p>
<h2 id="what-problem-gnap-is-solving">What problem GNAP is solving</h2>
<p>OAuth 2.0 (RFC 6749) was written around one scenario: a user in a browser approves a pre-registered web app. Everything since has been extensions bolted on: PKCE, device flow, PAR, RAR, DPoP, token exchange. RFC 9635 Appendix A lists the assumptions GNAP drops:</p>
<table>
  <thead>
      <tr>
          <th>Concern</th>
          <th>OAuth 2.0</th>
          <th>GNAP (RFC 9635)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Who approves</td>
          <td>Assumes the user at the client is the user at the AS</td>
          <td>Allows various patterns; interaction &ldquo;can use a browser, but doesn&rsquo;t have to&rdquo;</td>
      </tr>
      <tr>
          <td>Client identity</td>
          <td>Pre-registered <code>client_id</code> known to the AS</td>
          <td>Client instance presents a key; AS may accept an unknown key</td>
      </tr>
      <tr>
          <td>Requesting access</td>
          <td><code>scope</code> strings (RAR adds structured objects)</td>
          <td>Structured <code>access</code> objects, with plain strings as an optimization</td>
      </tr>
      <tr>
          <td>Entry point</td>
          <td>Different endpoints and grant types per flow</td>
          <td>Every request starts with the same request to one endpoint</td>
      </tr>
      <tr>
          <td>Token security</td>
          <td>Bearer by default; DPoP/mTLS optional</td>
          <td>Key proof is built in; bound tokens are the normal case</td>
      </tr>
      <tr>
          <td>Refresh</td>
          <td>Separate refresh tokens</td>
          <td>Not needed when access tokens are rotated (Section 6.1)</td>
      </tr>
      <tr>
          <td>Public clients</td>
          <td>Distinct client type with weaker guarantees</td>
          <td>No such notion; key material can always be sent</td>
      </tr>
  </tbody>
</table>
<p>If you have used <a href="/posts/oauth-20-complete-developer-guide-authorization-authentication/">Rich Authorization Requests</a> or DPoP, much of this will feel familiar. GNAP makes them the baseline instead of optional add-ons.</p>
<h2 id="a-gnap-grant-request-field-by-field">A GNAP grant request, field by field</h2>
<p>The client sends one <code>POST</code> with <code>Content-Type: application/json</code> to the AS grant endpoint. This is the non-normative example from RFC 9635 Section 2:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;access_token&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;access&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;photo-api&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;actions&#34;</span>: [<span style="color:#e6db74">&#34;read&#34;</span>, <span style="color:#e6db74">&#34;write&#34;</span>, <span style="color:#e6db74">&#34;dolphin&#34;</span>],
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;locations&#34;</span>: [<span style="color:#e6db74">&#34;https://server.example.net/&#34;</span>, <span style="color:#e6db74">&#34;https://resource.local/other&#34;</span>],
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;datatypes&#34;</span>: [<span style="color:#e6db74">&#34;metadata&#34;</span>, <span style="color:#e6db74">&#34;images&#34;</span>]
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;dolphin-metadata&#34;</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;client&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;display&#34;</span>: { <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;My Client Display Name&#34;</span>, <span style="color:#f92672">&#34;uri&#34;</span>: <span style="color:#e6db74">&#34;https://example.net/client&#34;</span> },
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;key&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;proof&#34;</span>: <span style="color:#e6db74">&#34;httpsig&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;jwk&#34;</span>: { <span style="color:#f92672">&#34;kty&#34;</span>: <span style="color:#e6db74">&#34;RSA&#34;</span>, <span style="color:#f92672">&#34;e&#34;</span>: <span style="color:#e6db74">&#34;AQAB&#34;</span>, <span style="color:#f92672">&#34;kid&#34;</span>: <span style="color:#e6db74">&#34;xyz-1&#34;</span>, <span style="color:#f92672">&#34;alg&#34;</span>: <span style="color:#e6db74">&#34;RS256&#34;</span>, <span style="color:#f92672">&#34;n&#34;</span>: <span style="color:#e6db74">&#34;kOB5rR4Jv0GMeL....&#34;</span> }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;interact&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;start&#34;</span>: [<span style="color:#e6db74">&#34;redirect&#34;</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;finish&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;method&#34;</span>: <span style="color:#e6db74">&#34;redirect&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;uri&#34;</span>: <span style="color:#e6db74">&#34;https://client.example.net/return/123455&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;nonce&#34;</span>: <span style="color:#e6db74">&#34;LKLTI25DK82FX4T4QFZC&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;subject&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;sub_id_formats&#34;</span>: [<span style="color:#e6db74">&#34;iss_sub&#34;</span>, <span style="color:#e6db74">&#34;opaque&#34;</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;assertion_formats&#34;</span>: [<span style="color:#e6db74">&#34;id_token&#34;</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li><strong><code>access_token.access</code></strong> replaces <code>scope</code>. The first element is a RAR-style object (<code>type</code>, <code>actions</code>, <code>locations</code>, <code>datatypes</code>); <code>&quot;dolphin-metadata&quot;</code> is a plain string reference, the scope-like shortcut.</li>
<li><strong><code>client.key</code></strong> replaces <code>client_id</code> + secret. <code>proof</code> names the proofing method, and <code>jwk</code> carries the public key. The registered proofing methods are <code>httpsig</code>, <code>mtls</code>, <code>jwsd</code> and <code>jws</code> (Section 10.16.2).</li>
<li><strong><code>interact</code></strong> says how the AS can reach a human. Start modes are <code>redirect</code>, <code>app</code>, <code>user_code</code> and <code>user_code_uri</code>; finish methods are <code>redirect</code> (browser callback) and <code>push</code> (AS POSTs to the client). The <code>nonce</code> is mandatory for all finish methods, which gives you CSRF and replay protection that OAuth leaves to the optional <code>state</code> parameter.</li>
<li><strong><code>subject</code></strong> asks for identity information in the same call, which is why GNAP can stand in for a separate OIDC authentication request.</li>
</ul>
<h2 id="the-response-and-the-continuation-loop">The response and the continuation loop</h2>
<p>When user interaction is needed, the AS replies with a redirect URI, a finish value and a continuation handle (Section 3):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;interact&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;redirect&#34;</span>: <span style="color:#e6db74">&#34;https://server.example.com/interact/4CF492MLVMSW9MKMXKHQ&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;finish&#34;</span>: <span style="color:#e6db74">&#34;MBDOFXG4Y5CVJCX821LH&#34;</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;continue&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;access_token&#34;</span>: { <span style="color:#f92672">&#34;value&#34;</span>: <span style="color:#e6db74">&#34;80UPRY5NM33OMUKMKSKU&#34;</span> },
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;uri&#34;</span>: <span style="color:#e6db74">&#34;https://server.example.com/tx&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The client sends the user to <code>interact.redirect</code>. After approval, the AS sends the user back to the client&rsquo;s finish <code>uri</code> with an <code>interact_ref</code>, and the client closes the transaction with a <strong>continuation request</strong>. Per Section 5.1 it is a signed POST using the <code>GNAP</code> scheme:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-http" data-lang="http"><span style="display:flex;"><span><span style="color:#a6e22e">POST</span> /continue <span style="color:#66d9ef">HTTP</span><span style="color:#f92672">/</span><span style="color:#ae81ff">1.1</span>
</span></span><span style="display:flex;"><span>Host<span style="color:#f92672">:</span> <span style="color:#ae81ff">server.example.com</span>
</span></span><span style="display:flex;"><span>Content-Type<span style="color:#f92672">:</span> <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>Authorization<span style="color:#f92672">:</span> <span style="color:#ae81ff">GNAP 80UPRY5NM33OMUKMKSKU</span>
</span></span><span style="display:flex;"><span>Signature-Input<span style="color:#f92672">:</span> <span style="color:#ae81ff">sig1=...</span>
</span></span><span style="display:flex;"><span>Signature<span style="color:#f92672">:</span> <span style="color:#ae81ff">sig1=...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{ <span style="color:#f92672">&#34;interact_ref&#34;</span>: <span style="color:#e6db74">&#34;4IFWWIKYBC2PQ6U56NL1&#34;</span> }
</span></span></code></pre></div><p>Two things differ from OAuth&rsquo;s token endpoint. First, the continuation token is itself a bound credential: the client &ldquo;MUST sign all continuation requests with its key&rdquo; (Section 3.1). Second, the same continuation URI supports more verbs: <code>POST</code> without a body to poll, <code>PATCH</code> to modify the grant mid-flight, and <code>DELETE</code> to cancel it (Sections 5.2 to 5.4). If the AS gives a <code>wait</code> value (never less than five seconds), polling sooner earns a <code>too_fast</code> error.</p>
<p>That modify-in-flight capability is the &ldquo;negotiation&rdquo; in the name. A client can start with a small request, learn what the user will approve, and ask for more without restarting the whole flow.</p>
<h2 id="where-gnap-is-clearly-better-than-oauth-20">Where GNAP is clearly better than OAuth 2.0</h2>
<ol>
<li><strong>Software-only and asynchronous approval.</strong> A backend agent can request access, the AS can contact a human out of band, and the client polls the continuation URI. Compare this with the <a href="/posts/oauth-device-code-flow-security-prevent-device-code-phishing/">device code flow</a>, which has well-known phishing exposure because the user types a code they did not initiate.</li>
<li><strong>Different user at the client and at the AS.</strong> OAuth 2.0 assumes they are the same person. In GNAP, the approver can be someone else entirely, which fits delegated-approval workflows and agentic AI that needs a human sign-off.</li>
<li><strong>Unregistered clients.</strong> Open ecosystems where per-client registration does not scale (think Dynamic Client Registration pain) can use keys directly.</li>
<li><strong>One request shape.</strong> No more choosing among authorization code, device, CIBA and client credentials at design time.</li>
</ol>
<h2 id="where-it-is-not-ready">Where it is not ready</h2>
<ul>
<li><strong>Ecosystem.</strong> Searching for libraries and IdP support turned up the RFC and commentary, but no confirmed GNAP support in Keycloak, Okta, Auth0 or Entra ID. Simon Willison&rsquo;s October 2024 write-up noted that two of the listed example implementations had not been committed to in over four years. Treat any claim of &ldquo;production GNAP&rdquo; skeptically until you have run the server&rsquo;s conformance behavior yourself.</li>
<li><strong>Resource server connections.</strong> The core spec defers AS-to-RS interoperability (token introspection, key binding at the RS) to a companion document, which RFC 9635 cites as a work-in-progress draft. Without it, you cannot build a multi-vendor deployment from the core RFC alone.</li>
<li><strong>Compatibility.</strong> RFC 9635 says GNAP is not meant to be directly compatible with OAuth 2.0. It includes migration examples, but existing API gateways, SDKs and token validation middleware do not understand <code>Authorization: GNAP</code>.</li>
<li><strong>Operational cost.</strong> Every request is key-signed. HTTP Message Signatures are the default <code>httpsig</code> proof, and signing and verifying them correctly (canonicalization, <code>tag=&quot;gnap&quot;</code>, clock skew) is more work than validating a bearer JWT.</li>
</ul>
<h2 id="a-pragmatic-adoption-path">A pragmatic adoption path</h2>
<p>Hold off on a protocol swap. Instead, take the GNAP ideas that OAuth already supports:</p>
<ol>
<li>Move high-value APIs to sender-constrained tokens with <a href="/posts/dpop-next-gen-oauth-token-security/">DPoP</a>. It gives you GNAP-style key proof inside OAuth.</li>
<li>Replace broad scopes with RAR <code>authorization_details</code> for fine-grained consent (see the <a href="/posts/oauth-20-complete-developer-guide-authorization-authentication/">OAuth 2.0 developer guide</a>).</li>
<li>Keep refresh token lifetimes short or rotate them; the GNAP position is that rotation makes separate refresh tokens unnecessary.</li>
<li>Prototype GNAP only where OAuth is a poor fit: asynchronous human approval, or software agents that need delegated authority. For agent scenarios, compare with <a href="/posts/keycloak-token-exchange-implementing-oauth-20-token-exchange/">OAuth token exchange</a> and the <a href="/posts/mcp-oauth-21-authentication-how-ai-agents-securely-connect-to-tools/">MCP OAuth 2.1 approach</a>.</li>
<li>Inspect any tokens you are prototyping with the <a href="/tools/jwt-decode/">JWT Decoder</a>.</li>
</ol>
<h2 id="verdict">Verdict</h2>
<p>GNAP is a well-reasoned redesign, and Appendix A of RFC 9635 is worth reading even if you never deploy it. As of November 2026 it is a standard without a visible mainstream implementation base, so the production answer is &ldquo;not yet&rdquo; for most teams. Build on OAuth 2.1 plus DPoP and RAR today, watch for IdP announcements, and revisit when an AS you already run ships a GNAP endpoint.</p>
]]></content:encoded></item></channel></rss>