Web Security August 10, 2026 • 8 min read • By DevBuildTool Security Team

Understanding JSON Web Tokens (JWT): Structure, Security Risks, and Client-Side Verification

A comprehensive developer guide on JWT Base64URL encoding, header/payload structures, token expiration logic, signature validation algorithms, and pure browser Web Crypto API execution.

Advertisement

JSON Web Tokens (JWT) have become the default standard (RFC 7519) for statelessly passing authentication claims between microservices, single-page applications (SPAs), and mobile clients. However, misinterpreting the difference between token decoding and token verification remains one of the top causes of authorization security flaws in web applications.

1. Anatomy of a JSON Web Token

A JSON Web Token consists of three distinct strings separated by dots (.):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

These segments correspond to:

  • Header (Segment 1): Metadata identifying the cryptographic signing algorithm (e.g. HS256, RS256, ES256) and token type ("JWT").
  • Payload (Segment 2): JSON object containing registered, public, or private claims (statements about the user and context).
  • Signature (Segment 3): Cryptographic hash created by signing the Base64URL-encoded header and payload with a secret key or private key.

2. Decoding vs. Signature Verification

A critical concept every frontend developer must understand is that Base64URL encoding is NOT encryption. Anyone who holds a JWT can easily split the token by dots and decode the Base64URL string to view all claims contained in the payload.

Decoding extracts readable JSON data from the token string without verifying if the token was tampered with. Verification, on the other hand, recalculates the cryptographic signature over the header and payload using the public key or HMAC secret key to guarantee data integrity and authenticity.

3. Common JWT Security Vulnerabilities

In modern web authorization architecture, improper handling of JWTs exposes applications to several attack vectors:

  1. The "alg": "none" Exploit: Early JWT library implementations allowed attackers to replace "alg": "HS256" with "alg": "none" and strip the signature entirely. Unpatched backend servers accepting unsigned tokens would treat tampered payload claims (such as "role": "admin") as valid.
  2. HMAC vs RSA Key Confusion: If an API endpoint expects an RSA public key (RS256) but accepts a token signed with HMAC (HS256), an attacker can sign a forged token using the server's published RSA public key as the secret HMAC key.
  3. Replay Attacks & Stale Expirations: Tokens lacking an explicit exp (Expiration Time) claim or having excessive TTLs (Time-to-Live) remain vulnerable to replay attacks if stolen from browser storage.

4. Pure Client-Side Decoding Architecture

When developers use online token decoders that transmit bearer tokens over HTTPS to a central backend server, they risk exposing sensitive session tokens in third-party request logs.

To inspect tokens safely, client-side utility functions convert standard Base64URL characters (- and _) back into standard Base64 (+ and /) and decode strings using standard browser Web APIs:

function decodeJwt(token) {
  try {
    const parts = token.split('.');
    if (parts.length !== 3) {
      throw new Error('Invalid JWT format');
    }
    
    // Convert Base64URL to Base64
    const base64Header = parts[0].replace(/-/g, '+').replace(/_/g, '/');
    const base64Payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
    
    // Decode JSON strings
    const header = JSON.parse(atob(base64Header));
    const payload = JSON.parse(atob(base64Payload));
    
    return { header, payload, rawSignature: parts[2] };
  } catch (err) {
    return { error: err.message };
  }
}

5. Verifying Signatures via Web Crypto API

Modern browsers provide native cryptographic primitives through window.crypto.subtle. You can verify HMAC-SHA256 signatures directly inside client applications without external libraries:

async function verifyHmacJwt(token, secretKey) {
  const [headerB64, payloadB64, signatureB64] = token.split('.');
  const encoder = new TextEncoder();

  // Import key into Web Crypto API
  const cryptoKey = await crypto.subtle.importKey(
    'raw',
    encoder.encode(secretKey),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['verify']
  );

  // Convert Base64URL signature back to ArrayBuffer
  const signatureBin = Uint8Array.from(
    atob(signatureB64.replace(/-/g, '+').replace(/_/g, '/')),
    c => c.charCodeAt(0)
  );

  // Signed data is 'headerB64.payloadB64'
  const data = encoder.encode(`${headerB64}.${payloadB64}`);

  // Perform cryptographic verification
  return await crypto.subtle.verify('HMAC', cryptoKey, signatureBin, data);
}

6. Frontend JWT Best Practices Checklist

  • ✓ Store Tokens in HttpOnly Cookies: Avoid storing long-lived access tokens in localStorage or sessionStorage where XSS vulnerabilities can read them.
  • ✓ Keep TTL Short: Set short expiration periods (5 to 15 minutes) for access tokens and use refresh token rotation strategies.
  • ✓ Enforce Exact Claim Auditing: Always validate exp, nbf, iss (Issuer), and aud (Audience) claims on every request.
  • ✓ Inspect Tokens Locally: Use tools that execute client-side decoding inside your local browser memory sandbox.
Sponsored Content