<?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: ToolsMatic</title>
    <description>The latest articles on DEV Community by ToolsMatic (@toolsmatic).</description>
    <link>https://dev.to/toolsmatic</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908449%2F94f592fb-7e8f-4fdf-a75f-7305c833538c.png</url>
      <title>DEV Community: ToolsMatic</title>
      <link>https://dev.to/toolsmatic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/toolsmatic"/>
    <language>en</language>
    <item>
      <title>Stop using basic JSON formatters. I built one that actually fixes your errors.</title>
      <dc:creator>ToolsMatic</dc:creator>
      <pubDate>Sat, 02 May 2026 05:29:19 +0000</pubDate>
      <link>https://dev.to/toolsmatic/stop-using-basic-json-formatters-i-built-one-that-actually-fixes-your-errors-567i</link>
      <guid>https://dev.to/toolsmatic/stop-using-basic-json-formatters-i-built-one-that-actually-fixes-your-errors-567i</guid>
      <description>&lt;p&gt;If you are a developer, you probably deal with JSON every single day. And if you are like me, you probably have a bookmark to some random "JSON Formatter Online" site from 2014.&lt;/p&gt;

&lt;p&gt;You paste your payload, click "Format", and... &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Error: Parse error on line 42: ... "status": "active", } ... Expecting 'STRING', got '}'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Because of one tiny &lt;strong&gt;trailing comma&lt;/strong&gt;, the tool refuses to work. Now you have to manually hunt down the error, fix it, and try again. Oh, and you just pasted a production API response containing user data into a random website that probably logged it.&lt;/p&gt;

&lt;p&gt;I got so frustrated with this workflow that I decided to build &lt;strong&gt;&lt;a href="https://toolsmatic.me/tools/json-formatter.html" rel="noopener noreferrer"&gt;ToolsMatic JSON Formatter Pro&lt;/a&gt;&lt;/strong&gt;--a 100% free, client-side tool that actually solves developer pain points.&lt;/p&gt;

&lt;p&gt;Here are 8 features I built into it that make it the last JSON tool you'll ever need.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Auto-Repair Invalid JSON
&lt;/h3&gt;

&lt;p&gt;Instead of throwing a useless error when your JSON is slightly malformed, ToolsMatic includes an &lt;strong&gt;Auto-Repair&lt;/strong&gt; button. &lt;br&gt;
If your payload has trailing commas, single quotes instead of double quotes, or unquoted keys ({ name: "John" }), clicking Auto-Repair will automatically normalize and fix the syntax for you, saving you from hunting down typos in a 5,000-line payload.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Handling Massive 100MB Files Without Crashing
&lt;/h3&gt;

&lt;p&gt;Most browser-based JSON tools completely freeze if you paste anything larger than 5MB. I built ToolsMatic to handle massive database dumps and logging exports. It supports &lt;strong&gt;streaming uploads and downloads up to 100MB&lt;/strong&gt;, so you can format and analyze massive payloads without crashing your browser tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The JSON Path Finder
&lt;/h3&gt;

&lt;p&gt;Have you ever stared at a massive API response in your browser, found the exact value you need, and then spent 2 minutes trying to figure out the exact dot-notation path to extract it in your code?&lt;/p&gt;

&lt;p&gt;In ToolsMatic, you can switch to &lt;strong&gt;Tree View&lt;/strong&gt;. When you find the value you want, simply &lt;strong&gt;click it&lt;/strong&gt;. &lt;br&gt;
The tool will automatically copy the exact path to your clipboard (e.g., data.users[3].profile.avatar_url). It's a massive time-saver when writing frontend data-fetching logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Native JSON Schema Validation
&lt;/h3&gt;

&lt;p&gt;If you are working with strict APIs, you can switch to the "Schema" mode, paste your JSON Schema, and instantly validate your payload against it. It gives you detailed error messages with exact field locations, making it incredibly easy to ensure your payloads match your OpenAPI/Swagger specs.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Real-Time Regex Search
&lt;/h3&gt;

&lt;p&gt;When you are looking for specific values in a 20,000-line JSON file, standard Ctrl+F doesn't always cut it. ToolsMatic includes a built-in search bar that supports &lt;strong&gt;Regex patterns&lt;/strong&gt;, highlighting matches in real-time as you type through massive nested structures.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Side-by-Side Compare Mode
&lt;/h3&gt;

&lt;p&gt;Trying to figure out what changed between two API responses or config files? Instead of dropping them into a generic text diff tool, ToolsMatic has a built-in JSON Compare Mode. It formats both payloads and highlights the exact structural differences, added keys, and changed values. &lt;/p&gt;

&lt;h3&gt;
  
  
  7. Sort Keys Alphabetically
&lt;/h3&gt;

&lt;p&gt;Sometimes you just need to normalize data before diffing it or saving a configuration file. With one click, you can sort every single key in the entire JSON tree alphabetically, at every depth level. &lt;/p&gt;

&lt;h3&gt;
  
  
  8. 100% Privacy (Zero Server Uploads)
&lt;/h3&gt;

&lt;p&gt;This is the most important part. When we debug API responses, they often contain sensitive PII, Bearer Tokens, or proprietary config structures. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ToolsMatic does not have a backend.&lt;/strong&gt; The formatting, minification, schema validation, and tree generation all happen entirely inside your browser using Vanilla JavaScript. Once the page loads, you can turn off your Wi-Fi and the tool will still work perfectly. Your data never leaves your machine.&lt;/p&gt;




&lt;h3&gt;
  
  
  Try it out!
&lt;/h3&gt;

&lt;p&gt;It's completely free, has no ads covering the editor, requires no login, and is packed with keyboard shortcuts (Ctrl+Enter to format, Ctrl+M to minify). &lt;/p&gt;

&lt;p&gt;You can try it here: &lt;strong&gt;&lt;a href="https://toolsmatic.me/tools/json-formatter.html" rel="noopener noreferrer"&gt;ToolsMatic JSON Formatter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built this to scratch my own itch, but I'd love to hear what features you think are missing! What is the most annoying thing about working with JSON for you? Let me know in the comments!&lt;/p&gt;

</description>
      <category>json</category>
      <category>webdev</category>
      <category>tools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How I built a zero-dependency, 100% client-side JWT Verifier using the Web Crypto API</title>
      <dc:creator>ToolsMatic</dc:creator>
      <pubDate>Sat, 02 May 2026 05:16:37 +0000</pubDate>
      <link>https://dev.to/toolsmatic/how-i-built-a-zero-dependency-100-client-side-jwt-verifier-using-the-web-crypto-api-477a</link>
      <guid>https://dev.to/toolsmatic/how-i-built-a-zero-dependency-100-client-side-jwt-verifier-using-the-web-crypto-api-477a</guid>
      <description>&lt;p&gt;JSON Web Tokens (JWTs) are everywhere. Whether you're debugging an OAuth flow, a rogue microservice, or a broken single-page application, inspecting a JWT is a daily task for most developers. &lt;/p&gt;

&lt;p&gt;But there's a massive, glaring problem with how we usually do it: &lt;strong&gt;We paste production tokens into random third-party websites.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many online JWT decoders send your token to their backend to parse or verify it. If that token contains sensitive claims, PII, or internal routing data - and if it hasn't expired - you've just leaked it. &lt;/p&gt;

&lt;p&gt;I was tired of wondering if a random tool was logging my tokens, so I decided to build a &lt;strong&gt;&lt;a href="https://toolsmatic.me/tools/jwt-inspector.html" rel="noopener noreferrer"&gt;privacy-first JWT Inspector&lt;/a&gt;&lt;/strong&gt; for my tool hub, ToolsMatic. &lt;/p&gt;

&lt;p&gt;The goal? &lt;strong&gt;Zero backend. Zero dependencies. 100% client-side processing.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Here's how I built it using nothing but Vanilla JavaScript and the native Web Crypto API.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Safely Decoding Base64URL in the Browser
&lt;/h3&gt;

&lt;p&gt;A JWT is just three strings separated by dots (&lt;code&gt;header.payload.signature&lt;/code&gt;), encoded in Base64URL. &lt;/p&gt;

&lt;p&gt;The first challenge is that the browser's native &lt;code&gt;atob()&lt;/code&gt; function only understands standard Base64, not Base64URL (which swaps &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; for &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;_&lt;/code&gt;, and removes the &lt;code&gt;=&lt;/code&gt; padding). &lt;/p&gt;

&lt;p&gt;To decode the token without a library, we have to normalize the string back to standard Base64 first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b64urlDecodeToString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Swap characters back to standard Base64&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Add back the missing '=' padding&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Decode to binary, then convert to a string&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Malformed padding or characters&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, decoding the &lt;code&gt;header&lt;/code&gt; and &lt;code&gt;payload&lt;/code&gt; is as simple as splitting the token by &lt;code&gt;.&lt;/code&gt; and running &lt;code&gt;JSON.parse()&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: The Hard Part - Cryptographic Verification
&lt;/h3&gt;

&lt;p&gt;Decoding the JSON is easy, but a JWT is useless if you can't verify its cryptographic signature. &lt;/p&gt;

&lt;p&gt;Most people reach for the &lt;code&gt;jsonwebtoken&lt;/code&gt; npm package for this, but that requires a Node.js backend. Instead, we can use the browser's native &lt;code&gt;window.crypto.subtle&lt;/code&gt; API.&lt;/p&gt;

&lt;h4&gt;
  
  
  Verifying HMAC (HS256)
&lt;/h4&gt;

&lt;p&gt;HMAC algorithms (like &lt;code&gt;HS256&lt;/code&gt;) use a shared secret. We need to import the secret key as raw bytes, and then calculate the signature over the &lt;code&gt;header.payload&lt;/code&gt; string to see if it matches the token's signature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The data we are verifying is the first two parts of the token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretMaterial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-super-secret-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Import the raw secret into the Web Crypto API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretMaterial&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HMAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
  &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sign&lt;/span&gt;&lt;span class="dl"&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;// 2. Generate a signature for our data using the secret&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HMAC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Encode our generated signature and compare it!&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;b64urlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verifying RSA (RS256)
&lt;/h4&gt;

&lt;p&gt;RSA algorithms (like &lt;code&gt;RS256&lt;/code&gt;) are more complex because they use a public/private key pair. You verify the token using a PEM-formatted Public Key.&lt;/p&gt;

&lt;p&gt;The Web Crypto API requires us to strip out the &lt;code&gt;-----BEGIN PUBLIC KEY-----&lt;/code&gt; headers, convert the base64 payload into an &lt;code&gt;ArrayBuffer&lt;/code&gt;, and import it as an &lt;code&gt;spki&lt;/code&gt; key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Clean the PEM string and convert to ArrayBuffer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-----BEGIN PUBLIC KEY-----|-----END PUBLIC KEY-----|&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;keyBuffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Import the Public Key&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spki&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;keyBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RSASSA-PKCS1-v1_5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; 
  &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verify&lt;/span&gt;&lt;span class="dl"&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;// 3. Verify the signature against the data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sigBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;b64urlDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RSASSA-PKCS1-v1_5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;sigBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Accounting for Clock Skew
&lt;/h3&gt;

&lt;p&gt;One of the most frustrating JWT bugs happens when your authorization server and your API server's clocks are out of sync by a few seconds. A token might be rejected as "expired" (&lt;code&gt;exp&lt;/code&gt;) or "not valid yet" (&lt;code&gt;nbf&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;To make this a &lt;em&gt;professional&lt;/em&gt; debugging tool, I added a manual Clock Skew slider. When validating claims, the tool simply offsets the current time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nowSec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Allow 60 seconds of drift&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;skew&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nowSec&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Expired &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nowSec&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;s ago`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;By combining basic base64url parsing with the native &lt;code&gt;crypto.subtle&lt;/code&gt; API, I was able to build a robust JWT inspector that supports &lt;code&gt;HS256/384/512&lt;/code&gt;, &lt;code&gt;RS256/384/512&lt;/code&gt;, and claim validation - all without sending a single byte over the network. &lt;/p&gt;

&lt;p&gt;If you're debugging tokens and want to make sure your data stays on your machine, you can use the live tool here: &lt;strong&gt;&lt;a href="https://toolsmatic.me/tools/jwt-inspector.html" rel="noopener noreferrer"&gt;ToolsMatic JWT Inspector&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you ever accidentally leaked a token to a debugging tool? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
  </channel>
</rss>
