Understanding JWTs: Structure, Claims, and Security

6 min read

JSON Web Tokens (JWTs) are everywhere in modern authentication and authorization, yet they are widely misunderstood. Developers often treat them as opaque, encrypted secrets when they are neither opaque nor encrypted. Misreading what a JWT actually guarantees leads directly to serious vulnerabilities.

This guide breaks down what a JWT is, how its three parts fit together, what the standard claims mean, how signing works, and the security rules you must follow. The goal is a precise mental model so you can use JWTs correctly in real backend and auth systems.

What a JWT Actually Is

A JSON Web Token is a compact, URL-safe token format defined by RFC 7519. It packages a set of claims (assertions about a subject, such as a user) into a string that can be passed in HTTP headers, query parameters, or cookies without further encoding. Because it is URL-safe and self-contained, a JWT can carry identity and authorization data between parties without a shared database lookup on every request.

JWTs are most commonly used for authentication and authorization. After a user logs in, a server issues a signed JWT; the client then sends that token on subsequent requests, and the server validates the signature to confirm the token was issued by a trusted party and has not been tampered with. The token is stateless from the server's perspective, which is what makes JWTs attractive for distributed and microservice architectures.

The Three Parts: Header, Payload, Signature

A JWT consists of three parts separated by dots: the header, the payload, and the signature, written as header.payload.signature. The first two parts are JSON objects that are each Base64url-encoded (a URL-safe variant of Base64). The signature is computed over the encoded header and payload. This means a JWT is just text you can split on the dots and decode.

The header describes how the token is signed. It typically contains an 'alg' field naming the signing algorithm (for example 'HS256' or 'RS256') and a 'typ' field, usually set to 'JWT'. The payload holds the claims. The signature is the cryptographic proof binding the header and payload together so they cannot be altered without detection. DevFmt's JWT decoder runs entirely in your browser and shows you these decoded parts directly, so the token is never sent to a server.

Claims: Standard and Custom

The payload is a collection of claims. RFC 7519 defines a set of registered claims with reserved meanings. Common ones include 'iss' (issuer, who created the token), 'sub' (subject, who the token is about), 'aud' (audience, who the token is intended for), 'exp' (expiration time), 'iat' (issued-at time), 'nbf' (not-before time, before which the token is invalid), and 'jti' (a unique token identifier useful for revocation lists). Times are expressed as Unix timestamps.

Beyond the registered claims, you can include custom (private) claims for application-specific data, such as a user's roles, tenant identifier, or permission scopes. There is no fixed schema, but keep the payload small: every claim travels with every request, and oversized tokens bloat headers and can hit size limits.

How Signing Works

Signing is what makes a JWT trustworthy. There are two broad approaches. Symmetric signing, such as HS256 (HMAC with SHA-256), uses a single shared secret to both create and verify the signature. Anyone holding that secret can issue valid tokens, so it suits cases where the same party signs and verifies.

Asymmetric signing, such as RS256 (RSA) or ES256 (ECDSA), uses a private key to sign and a separate public key to verify. The issuer keeps the private key secret, while any number of services can verify tokens using the freely distributable public key. This is the right choice when one authority issues tokens that many independent services must validate, because verifiers never need access to the signing secret.

Critical Security Rules

The single most important fact: the payload is only Base64url-encoded, not encrypted. Anyone who possesses the token can decode and read every claim. Never put passwords, secrets, or sensitive personal data in a JWT. Encoding is not encryption; if you need confidentiality, use JWE or transport-level protection and keep secrets out of the token entirely.

Always verify the signature server-side before trusting any claim, and pin the expected algorithm. Two classic attacks target verification. The 'alg: none' attack exploits libraries that honor a header claiming no signature is required, letting an attacker forge tokens; reject 'none' outright. Algorithm confusion occurs when an attacker changes an RS256 token to HS256 and tricks the server into verifying it with the RSA public key (which is not secret) as if it were an HMAC secret; prevent this by configuring your library to accept only the specific algorithm you expect.

Beyond the signature, validate the claims. Check 'exp' to reject expired tokens, confirm 'aud' matches your service so a token minted for another audience cannot be replayed against you, and verify 'iss' is a trusted issuer. Prefer short token lifetimes paired with refresh tokens to limit the blast radius of a leaked token. Remember that decoding a token, such as with DevFmt's decoder, does not verify it; decoding only reveals the contents, while verification requires the key and must happen in your backend.

Storage and Transport

Because a JWT is a bearer token, anyone who holds it can use it, so protecting it in transit and at rest matters as much as signing it. Always transmit tokens over HTTPS so they cannot be intercepted on the wire. Treat every token as a credential for its entire lifetime.

On the client, storage involves real trade-offs. Storing a token in localStorage makes it readable by any JavaScript on the page, so a single cross-site scripting (XSS) flaw can exfiltrate it. Storing it in an httpOnly cookie keeps it out of JavaScript's reach and mitigates XSS theft, but cookies introduce cross-site request forgery (CSRF) concerns that you must counter with SameSite attributes and anti-CSRF tokens. There is no universally correct answer; choose based on your threat model, and combine short lifetimes, strict transport security, and signature plus claim validation to keep JWT-based auth robust.

We use cookies for anonymous analytics and ads. Your tool data never leaves your browser.