Security & Authentication

How to Read and Inspect JSON Web Tokens (JWT) Safely: Decoding, Claims, and Attack Prevention

10 min read • Technical Reference & In-Depth Guide

JSON Web Tokens (JWT, RFC 7519) have become the de facto industry standard for stateless authentication and authorization across modern distributed architectures. Every time you log into a SaaS platform or mobile app, your browser or client runtime exchanges a compact string of alphanumeric characters divided by two periods. Yet despite their ubiquity, widespread misconceptions regarding how JWTs function continue to produce severe security breaches in production software.

Author Note: JWTs are useful because they carry claims that applications can inspect, but decoding a token only reveals its contents. It does not prove that the token was issued by a trusted source or that its contents are authentic. Applications should validate the signature and relevant claims, including expiration and issuer where appropriate, before trusting the token.

1. Myth vs Reality: Encoding Is Not Encryption

The single most dangerous misconception among junior developers is assuming that a JWT hides or encrypts sensitive data. Unless you are specifically employing JSON Web Encryption (JWE, RFC 7516)—which is relatively rare in standard web apps—a standard JWT is merely a signed, Base64URL-encoded JSON structure.

Anyone who intercepts or possesses your JWT can read every single claim, user ID, email address, and role permission contained inside the payload in plain text within two milliseconds. Never store passwords, API secret keys, social security numbers, or confidential customer data inside a standard JWT payload.

2. The Tripartite Anatomy of a Token

A standard JWT consists of three distinct segments delimited by literal period (.) characters:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsZXgiLCJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0MjYyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header (Red)

Specifies token type (usually JWT) and the cryptographic signing algorithm used (e.g. HS256 or RS256).

2. Payload (Purple)

Contains the claims: statements about an entity (typically the authenticated user) and metadata such as expiration timestamps.

3. Signature (Green)

Cryptographic hash calculated over Base64URL(Header) + "." + Base64URL(Payload) using a secret key or private key.

3. Step-by-Step Worked Example: Safe Browser-Side Decoding

In client-side single page applications (React, Vue, Svelte, vanilla JS), frontend code frequently needs to inspect claims—for example, reading exp to determine whether to trigger a silent refresh token exchange, or reading name to display a user profile header.

Standard Base64 decoding (atob()) in browsers will fail on JWTs that contain Unicode characters or omit standard padding (=). Here is the robust, production-grade function to decode a JWT payload safely in the browser:

function parseJwtPayload(token) {
  try {
    // 1. Split the token and grab the payload segment
    const base64Url = token.split('.')[1];
    if (!base64Url) throw new Error('Invalid JWT format');

    // 2. Replace URL-safe characters with standard Base64 characters
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');

    // 3. Handle Unicode escape sequences safely via percent-encoding
    const jsonPayload = decodeURIComponent(
      atob(base64)
        .split('')
        .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
        .join('')
    );

    return JSON.parse(jsonPayload);
  } catch (error) {
    console.error('Failed to decode JWT payload:', error);
    return null;
  }
}

4. Essential Registered Claims Reference

RFC 7519 defines several standard registered claim keys that every engineer should recognize:

exp (Expiration Time):

Unix epoch timestamp (seconds) after which the token must be rejected. Always compare Date.now() / 1000 > exp.

nbf (Not Before):

Identifies the time before which the JWT must not be accepted for processing.

iss (Issuer) & aud (Audience):

iss identifies the authorization server that minted the token; aud identifies the intended recipient microservice. Backend gateways must verify both.

5. Cryptographic Verification: How Backends Verify Signatures

While frontend web apps decode payloads for display purposes, backend APIs must verify cryptographic signatures before trusting any claim:

  • Symmetric Signing (HMAC SHA-256 / HS256): Both the token issuer and token verifier share an identical secret string. If an API gateway shares the secret, it recomputes HMAC-SHA256(header + "." + payload, secret) and verifies that the output matches the signature segment byte-for-byte.
  • Asymmetric Signing (RSA / RS256 or ECDSA / ES256): The authorization server signs the token with a private key. Downstream microservices download the public key from the identity provider's JSON Web Key Set (JWKS) endpoint (e.g., https://auth.example.com/.well-known/jwks.json) and verify the signature mathematically without possessing the private signing key.

6. Two Critical Attack Vectors to Guard Against

The "Algorithm None" Exploit (CVE-2015-9235)

The JWT specification permits an unverified token with "alg": "none". Early JWT libraries mistakenly accepted tokens where an attacker changed the header to "alg": "none" and stripped the signature entirely, allowing total elevation of privilege to admin. Modern verification libraries strictly whitelist approved algorithms (e.g., algorithms: ['RS256']).

Key Confusion Attack (RS256 to HS256)

If an application expects RSA signatures (RS256) using a public key, an attacker can modify the header to symmetric HMAC (HS256) and sign the token using the server's publicly accessible RSA public key as the HMAC secret! If the backend fails to verify the exact algorithm type, it will validate the forged signature against its own public key string.

7. Secure Token Storage in Web Applications

Where you store JWTs in the browser directly dictates your vulnerability to Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF):

The LocalStorage Vulnerability (High XSS Risk)

Storing access tokens in window.localStorage makes them vulnerable to any third-party script, malicious npm dependency, or XSS flaw. A single injected script can execute localStorage.getItem('token') and exfiltrate credentials instantly.

Recommended Architecture: HttpOnly Cookies with Short-Lived In-Memory Tokens

Keep short-lived access tokens (5-minute TTL) strictly in client JavaScript memory (variables). Store the long-lived refresh token in an HttpOnly; Secure; SameSite=Strict cookie inaccessible to JavaScript. When the in-memory token expires, make a silent POST request to the refresh endpoint to obtain a new access token.

8. Interactive Inspection Tools on Black Claaw

To inspect headers, claims, and expiration timestamps securely without transmitting bearer tokens to third-party servers:

Have feedback or questions on this guide?

Our engineering guides are continually refined with real production feedback.

Contact Technical Desk