<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Oauth2 on Sauvik Biswas</title>
    <link>https://sauvikbiswas.com/tags/oauth2/</link>
    <description>Recent content in Oauth2 on Sauvik Biswas</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 22 Jun 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://sauvikbiswas.com/tags/oauth2/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>JWKS and RS256: Dropping the Shared JWT Secret</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-08/</link>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-08/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-verify-only-apps-shouldnt-hold-the-signing-key&#34;&gt;&#xA;  Why verify-only apps shouldn&amp;rsquo;t hold the signing key&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-verify-only-apps-shouldnt-hold-the-signing-key&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-07/&#34;&gt;v07&lt;/a&gt; added OpenID Connect on top of v06&amp;rsquo;s split architecture. The &lt;code&gt;id_token&lt;/code&gt; is a signed JWT. Mode B access tokens are JWTs too. Both used HS256 with a shared &lt;code&gt;JWT_SECRET&lt;/code&gt; copied into three &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;&#xA;&lt;p&gt;That worked in a toy lab. It is not how production IdPs ship.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Concern&lt;/th&gt;&#xA;          &lt;th&gt;v07 (HS256 + &lt;code&gt;JWT_SECRET&lt;/code&gt;)&lt;/th&gt;&#xA;          &lt;th&gt;v08 (RS256 + JWKS)&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Who can &lt;em&gt;sign&lt;/em&gt; tokens?&lt;/td&gt;&#xA;          &lt;td&gt;Any process with &lt;code&gt;JWT_SECRET&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Only the auth server (private key)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Who can &lt;em&gt;verify&lt;/em&gt; tokens?&lt;/td&gt;&#xA;          &lt;td&gt;Any process with &lt;code&gt;JWT_SECRET&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Anyone with the public key from &lt;code&gt;jwks_uri&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Key rotation&lt;/td&gt;&#xA;          &lt;td&gt;Re-sync &lt;code&gt;JWT_SECRET&lt;/code&gt; everywhere&lt;/td&gt;&#xA;          &lt;td&gt;Publish new key in JWKS; verifiers pick by &lt;code&gt;kid&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Discovery&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;id_token_signing_alg_values_supported: [&amp;quot;HS256&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[&amp;quot;RS256&amp;quot;]&lt;/code&gt; + &lt;code&gt;jwks_uri&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Real IdPs (Google, Okta, Auth0)&lt;/td&gt;&#xA;          &lt;td&gt;Not available (no shared secret)&lt;/td&gt;&#xA;          &lt;td&gt;RS256; public keys at &lt;code&gt;jwks_uri&lt;/code&gt; in discovery&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;what-hs256-actually-means&#34;&gt;&#xA;  What HS256 actually means&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#what-hs256-actually-means&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;HS256 is symmetric: one secret signs and verifies. In v07, the client verified &lt;code&gt;id_token&lt;/code&gt; with &lt;code&gt;JWT_SECRET&lt;/code&gt; and the resource server verified Mode B access tokens with the same secret while the auth server signed with it.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Intermission: What Industry Ships and Who Gets Paid</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-intermission-01/</link>
      <pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-intermission-01/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;a-deliberate-pause&#34;&gt;&#xA;  A deliberate pause&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#a-deliberate-pause&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-07/&#34;&gt;v07&lt;/a&gt; is in the repo. It consists of OpenID Connect on top of the &lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-06/&#34;&gt;v06&lt;/a&gt; split; i.e. &lt;code&gt;id_token&lt;/code&gt;, discovery, UserInfo, &lt;code&gt;nonce&lt;/code&gt;, and the &lt;code&gt;openid&lt;/code&gt; scope; while keeping v06&amp;rsquo;s opaque-or-JWT access-token modes. The runnable snapshot is &lt;a href=&#34;https://github.com/sauvikbiswas/oauth-lab/tree/main/versions/v07-openid-connect&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;versions/v07-openid-connect/&lt;/code&gt;&lt;/a&gt;. In my opinion, that is a lot of ground for seven incremental snapshots.&lt;/p&gt;&#xA;&lt;p&gt;I started drafting this market-research post earlier, then paused the write-up to implement OIDC first. Almost every commercial IdP ships OAuth and OIDC together; reading vendor pricing without knowing what an &lt;code&gt;id_token&lt;/code&gt; would not be correct. With v07 done, the OAuth+OIDC spine is complete enough to read the landscape.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding OpenID Connect on Top of OAuth 2</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-07/</link>
      <pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-07/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-v06-is-not-enough&#34;&gt;&#xA;  Why v06 is not enough&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-v06-is-not-enough&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-06/&#34;&gt;v06&lt;/a&gt; split the authorization server from the resource server and taught token validation across process boundaries. That is production-shaped OAuth 2.0.&lt;/p&gt;&#xA;&lt;p&gt;It is not OpenID Connect. Vendors market themselves as OpenID Providers because OIDC standardizes &lt;em&gt;authentication&lt;/em&gt; (who logged in) on top of OAuth&amp;rsquo;s &lt;em&gt;authorization&lt;/em&gt; (what APIs this token may call). While technically it&amp;rsquo;s not part of OAuth 2 specs; and I had steered clear of implementing it; I decided to tackle this as well since I was working on a piece that covered what&amp;rsquo;s available out there and what their business was. In almost all cases IdP (Identity providers) implement OIDC on top of OAuth.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Splitting the Auth Server from the Resource Server</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-06/</link>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-06/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-v05s-single-process-is-not-the-finish-line&#34;&gt;&#xA;  Why v05&amp;rsquo;s single process is not the finish line&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-v05s-single-process-is-not-the-finish-line&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-05/&#34;&gt;v05&lt;/a&gt; closed the refresh loop: short-lived access tokens, silent renewal via &lt;code&gt;grant_type=refresh_token&lt;/code&gt;, and a protected &lt;code&gt;GET /api/me&lt;/code&gt;. One convenience hid an architectural lie.&lt;/p&gt;&#xA;&lt;p&gt;In v05, the authorization server and resource server share one Flask process on &lt;code&gt;:25000&lt;/code&gt;. The client calls the same host for &lt;code&gt;POST /token&lt;/code&gt; and &lt;code&gt;GET /api/me&lt;/code&gt;. Token minting and token validation both read &lt;code&gt;memory.access_tokens&lt;/code&gt; in the same Python dict. That works in a toy lab; it is not how production OAuth is deployed.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Refresh Tokens and Silent Re-authentication</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-05/</link>
      <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-05/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-v04-was-not-the-finish-line&#34;&gt;&#xA;  Why v04 was not the finish line&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-v04-was-not-the-finish-line&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-04/&#34;&gt;v04&lt;/a&gt; closed the loop through a protected API: the client app stores an &lt;code&gt;access_token&lt;/code&gt;, calls &lt;code&gt;GET /api/me&lt;/code&gt; with &lt;code&gt;Authorization: Bearer …&lt;/code&gt;, and shows a profile page. Login means the token works and the resource server agrees.&lt;/p&gt;&#xA;&lt;p&gt;That token has a TTL. v04 minted access tokens with &lt;code&gt;expires_in: 3600&lt;/code&gt;. In production, short-lived access tokens are deliberate: if one leaks, damage is bounded. When it expires, &lt;code&gt;/api/me&lt;/code&gt; returns 401 and the user would otherwise go through &lt;code&gt;/authorize&lt;/code&gt; again: browser redirect, login form, callback.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Using the Access Token on a Protected API</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-04/</link>
      <pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-04/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-v03-was-not-the-finish-line&#34;&gt;&#xA;  Why v03 was not the finish line&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-v03-was-not-the-finish-line&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-03/&#34;&gt;v03&lt;/a&gt; closed the authorization half of OAuth: &lt;code&gt;state&lt;/code&gt; for CSRF, PKCE for code interception, and &lt;code&gt;POST /token&lt;/code&gt; to mint an &lt;code&gt;access_token&lt;/code&gt;. After a successful login, the callback page displayed that token as a string.&lt;/p&gt;&#xA;&lt;p&gt;That is a useful checkpoint. It is not a complete application experience. The token is supposed to be a credential. Something has to accept it and return data only the authenticated user should see.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding PKCE to Stop Authorization Code Interception</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-03/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-03/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-v02-needed-a-fix&#34;&gt;&#xA;  Why v02 needed a fix&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-v02-needed-a-fix&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-02/&#34;&gt;v02&lt;/a&gt; closed the login CSRF hole. The client generates random &lt;code&gt;state&lt;/code&gt;, the auth server echoes it on the callback, and the client rejects mismatches. That answers: &amp;ldquo;Did &lt;em&gt;I&lt;/em&gt; start this login?&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;It does not answer a different question: &amp;ldquo;Is the person trying to redeem this &lt;code&gt;code&lt;/code&gt; the &lt;em&gt;same&lt;/em&gt; app that requested it?&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;The authorization &lt;code&gt;code&lt;/code&gt; itself is a secret, but for a short time. It travels in the browser redirect URL:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Adding OAuth State to Stop CSRF</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-02/</link>
      <pubDate>Sun, 07 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-02/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;why-v01-needed-a-fix&#34;&gt;&#xA;  Why v01 needed a fix&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-v01-needed-a-fix&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://sauvikbiswas.com/posts/learning-oauth-2-01/&#34;&gt;v01&lt;/a&gt; got the Authorization Code redirect working, but it deliberately left out OAuth &lt;code&gt;state&lt;/code&gt;. That absence is fine for learning the spine of the flow; it is not fine for anything you would ship.&lt;/p&gt;&#xA;&lt;p&gt;The next piece to add is a mechanism to stop CSRF (Cross-Site Request Forgery). CSRF is when a site you are on tricks your browser into making a request you did not mean to make. The browser automatically sends along your cookies and session, so the server thinks you did it. &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6749#section-10.12&#34; target=&#34;_blank&#34;&gt;RFC 6749 §10.12&lt;/a&gt; calls this out for the authorization endpoint.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Learning OAuth 2 by Building It, One Version at a Time</title>
      <link>https://sauvikbiswas.com/posts/learning-oauth-2-01/</link>
      <pubDate>Sat, 06 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://sauvikbiswas.com/posts/learning-oauth-2-01/</guid>
      <description>&lt;h2 class=&#34;heading&#34; id=&#34;a-preamble&#34;&gt;&#xA;  A preamble&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#a-preamble&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;what-is-my-motivation-behind-this&#34;&gt;&#xA;  What is my motivation behind this&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#what-is-my-motivation-behind-this&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://in.linkedin.com/company/andromeda-security&#34; target=&#34;_blank&#34;&gt;Andromeda&lt;/a&gt; has been working on creating a comprehensive solution around Agents. If we look past the hype, the buzzword, and the scepticism, Agents and Agentic AI are here to stay and security is a real concern. Anthropic came up with an &lt;a href=&#34;https://www.anthropic.com/news/model-context-protocol&#34; target=&#34;_blank&#34;&gt;initial specification for something called Model Context Protocol (MCP)&lt;/a&gt;, which has eventually become a standard. The standard &lt;a href=&#34;https://modelcontextprotocol.io/specification/draft/basic/authorization&#34; target=&#34;_blank&#34;&gt;specifies OAuth 2.1 as the authorization mechanism&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
