GHGetHubApps

Understanding JSON Web Tokens (JWT) and token security

Security8 min readPublished

JSON Web Tokens (JWTs) have become the default standard for stateless authentication in modern web applications, microservices, and single-page apps. Yet a pervasive misunderstanding remains: many developers treat a JWT as an encrypted, secret container.

A standard JWT is merely signed and Base64URL-encoded — anyone who intercepts it can read its full payload instantly. This guide breaks down how the three parts of a token function, how cryptographic verification works, and the most common architectural mistakes that compromise token-based auth.

The three parts of a JWT

A JWT consists of three strings separated by dots: header.payload.signature.

Header: A JSON object specifying the token type ('JWT') and the signing algorithm (such as HS256 or RS256), Base64URL-encoded.

Payload: The claims — user ID, role, issued-at timestamp ('iat'), and expiration ('exp'), Base64URL-encoded. Because this is simply encoding and not encryption, you must never store sensitive secrets like passwords, credit card details, or private API keys inside a JWT payload.

Signature: Created by taking the encoded header, a dot, the encoded payload, and signing them with a cryptographic secret or private key. The signature guarantees data integrity: if an attacker modifies the payload to grant themselves admin privileges, the signature calculation will fail.

Symmetric (HS256) versus Asymmetric (RS256) signing

Choosing between symmetric and asymmetric algorithms depends on your system architecture:

AlgorithmKey TypeBest Suited ForTrade-off
HS256 (HMAC-SHA256)Single shared secretMonolithic apps where same server issues and verifiesEvery service that verifies tokens must know the signing secret
RS256 (RSA Signature)Private key / Public key pairMicroservices, third-party APIs, OAuth2 / OIDCSlightly higher compute overhead; public key can be shared openly
ES256 (ECDSA)Elliptic curve key pairHigh-throughput mobile and edge APIsShorter signatures with equivalent cryptographic strength

In distributed microservice architectures, RS256 or ES256 is preferred because consumer services only need the public key (often published via JWKS) to verify authenticity, without the risk of leaking the signing key.

Critical vulnerabilities to defend against

  • The 'none' algorithm bypass: Early JWT libraries allowed the header to declare 'alg': 'none', causing buggy verification functions to treat un-signed tokens as valid. Always enforce acceptable algorithms explicitly in backend verification code.
  • Weak HS256 secrets: Using short or predictable signing secrets (like 'secret123') allows attackers to brute-force the secret offline using tools like Hashcat in seconds. Signing secrets should be at least 256 bits of high-entropy randomness.
  • Storage vulnerabilities (LocalStorage vs HttpOnly cookies): Storing JWTs in browser localStorage makes them accessible to any JavaScript running on the page, exposing them to Cross-Site Scripting (XSS) attacks. Storing tokens in HttpOnly, Secure, SameSite cookies protects them from script access.

The refresh token rotation pattern

Because JWTs are stateless, you cannot easily revoke a token before it expires without maintaining a database blacklist (which defeats the stateless advantage). The standard solution is a dual-token pattern:

  1. Access token: Short lifespan (5 to 15 minutes). Used for authorization on API requests.
  2. Refresh token: Longer lifespan (7 to 30 days). Stored in an HttpOnly secure cookie and kept in the database.
  3. When the access token expires, the client sends the refresh token to issue a new access token.
  4. Implement refresh token rotation: Each time a refresh token is used, invalidate it and issue a new pair. If an invalidated refresh token is ever presented again, revoke the entire session family immediately, as it indicates a stolen token.

Frequently asked questions

Is a JWT encrypted?
No. Standard JWS (JSON Web Signature) tokens are signed, not encrypted. Their content is publicly readable by anyone who decodes the Base64 string. If you need confidential payloads, use JWE (JSON Web Encryption).
How can I inspect what's inside a JWT without sending it to a server?
You can decode it directly in your browser using our client-side JWT Decoder. Because decoding only requires splitting the dots and Base64-decoding the JSON strings, your token never leaves your machine.
What does 'sub', 'iss', and 'exp' mean?
These are standard registered claim names defined in RFC 7519: 'sub' (subject, typically the user ID), 'iss' (issuer, the auth server URL), and 'exp' (expiration Unix timestamp).

Tools for this

More guides