<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jwks on Sauvik Biswas</title>
    <link>https://sauvikbiswas.com/tags/jwks/</link>
    <description>Recent content in Jwks 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/jwks/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>
  </channel>
</rss>
