A JWT (JSON Web Token) is a compact, self-contained token used to represent claims between two parties — most commonly between a client and a server for authentication. When a user logs in, the server creates a JWT and returns it to the client, which then sends it with every subsequent request. The server can verify and read the token without a database lookup.

The Three Parts of a JWT

A JWT is three Base64URL-encoded strings separated by dots. Here is a real JWT broken into its parts:

Header
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Signature
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The Header

The header is a Base64URL-encoded JSON object containing the token type and the signing algorithm. Decoded, the header above is:

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

alg tells you which algorithm was used to sign the token — HS256 (HMAC-SHA256) is symmetric (same key to sign and verify), while RS256 (RSA-SHA256) is asymmetric (private key to sign, public key to verify). Never accept a token with "alg": "none" — this is a known attack vector.

The Payload

The payload contains the claims — statements about the subject (usually the user) and metadata. Decoded:

{ "sub": "1234567890", // subject — the user ID "name": "John Doe", // custom claim "iat": 1516239022 // issued at — Unix timestamp }

Standard registered claims include sub (subject), iss (issuer), exp (expiration time), iat (issued at), nbf (not before), and aud (audience). Custom claims — like name, role, or email — can be added freely.

The payload is readable by anyone. It is Base64URL-encoded, not encrypted. Do not put sensitive data in a JWT payload unless the token is encrypted (JWE). A JWT used for authentication should contain only what is needed — user ID, role, expiry.

The Signature

The signature is computed from the encoded header, encoded payload, and a secret key. For HS256: HMAC-SHA256(base64url(header) + "." + base64url(payload), secret). It proves the token was not tampered with — changing any character in the header or payload will produce a different signature, which the server will reject. But the signature does not encrypt the content.

Decode any JWT instantly Paste a token to see the decoded header and payload, check expiry status, and inspect all claims.
Open JWT Decoder →

Decoding a JWT Without a Library

Because the header and payload are just Base64URL-encoded JSON, you can decode them in any language without a JWT library — useful for debugging or inspecting tokens in environments where you cannot install packages.

// JavaScript — decode payload without a library function decodeJWT(token) { const [header, payload, sig] = token.split('.'); const decode = str => JSON.parse( atob(str.replace(/-/g, '+').replace(/_/g, '/')) ); return { header: decode(header), payload: decode(payload), sig: sig }; } # Python import base64, json def decode_jwt(token): parts = token.split('.') def decode(part): padding = 4 - len(part) % 4 part += '=' * (padding % 4) return json.loads(base64.urlsafe_b64decode(part)) return { 'header': decode(parts[0]), 'payload': decode(parts[1]) } # Command line (requires jq) TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig" echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq .

Checking Token Expiry

The exp claim is a Unix timestamp. If the current time is past exp, the token is expired and should be rejected. The nbf (not before) claim is the opposite — the token is not valid before this timestamp.

// Check expiry in JavaScript const payload = decodeJWT(token).payload; const now = Math.floor(Date.now() / 1000); if (payload.exp && payload.exp < now) { console.log('Token expired', new Date(payload.exp * 1000)); } else { console.log('Token valid until', new Date(payload.exp * 1000)); }

JWT Security Considerations

Decoding a JWT is not the same as verifying it. Anyone can decode the payload — that is by design. Verification means checking the signature against the secret key, confirming the token was issued by who it claims, and checking that it has not expired. Always verify tokens on the server. Client-side decoding is for inspection only, not for making trust decisions.

The alg: none attack: some older JWT libraries would accept a token with the algorithm set to none, skipping signature verification entirely. Always check that your library rejects alg: none explicitly. Use the Hash Generator if you need to generate HMAC values manually for debugging.


Frequently Asked Questions

Can anyone read the contents of a JWT?
Yes — the header and payload are Base64URL-encoded, not encrypted. Anyone who intercepts the token can decode and read the claims. This is why JWTs should only be transmitted over HTTPS, and sensitive data should not be included in the payload. The signature proves the token is authentic and untampered, but it does not hide the contents.
What is the difference between HS256 and RS256?
HS256 (HMAC-SHA256) uses a single shared secret — the same key is used to both sign and verify the token. This is simpler but requires both the issuing service and the verifying service to know the secret. RS256 (RSA-SHA256) uses a key pair — the issuer signs with the private key and any verifier can check with the public key. RS256 is preferred in distributed systems where multiple services verify tokens but only one should issue them.
How do I invalidate a JWT before it expires?
JWTs are stateless by design — the server does not store them, so there is no built-in revocation mechanism. Common workarounds are: maintaining a blocklist of revoked token IDs (jti claim), using short expiry times (15 minutes) combined with refresh tokens, rotating the signing secret (invalidates all tokens at once), or storing the last password change time and rejecting tokens issued before it.
Where should I store a JWT in the browser?
This is a genuine security trade-off. localStorage is accessible to JavaScript, making it vulnerable to XSS attacks. httpOnly cookies are not accessible to JavaScript, protecting against XSS, but are vulnerable to CSRF (mitigated with SameSite=Strict or CSRF tokens). For most applications, httpOnly cookies with SameSite=Strict is the recommended approach. Avoid storing JWTs in localStorage if your application handles sensitive data.

Related reading

Explainer
What Is Base64 Encoding?
Guide
How to Validate JSON