Guides JWT authentication tokens developer

What Is a JWT? How JSON Web Tokens Work and How to Decode One

By ClickSolveTools Editorial TeamUpdated 11 min read

A JSON Web Token (JWT) is a compact, URL-safe string that carries claims, such as a user ID and an expiry time, between two parties. It has three Base64URL-encoded parts separated by dots (a header, a payload and a signature) and is defined in RFC 7519.

The header and payload are only encoded, not encrypted, so anyone holding the token can read them. The signature is what lets a server detect changes. To decode a JWT, split it at the dots and Base64URL-decode the first two parts, or paste it into our JWT Decoder. Decoding shows what a token claims. Only checking the signature with the right key shows whether you should trust it.

What Is a JWT?

Firebase, Auth0, Amazon Cognito and OpenID Connect providers all issue JWTs, and so do many custom login systems. Here is a working demo token. Everything before the first dot is the header, the middle is the payload, and everything after the second dot is the signature:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzhmM2E5YjJjIiwibmFtZSI6IkFsaWNlIENoZW4iLCJyb2xlIjoiYWRtaW4iLCJpc3MiOiJodHRwczovL2F1dGgubXlhcHAuY29tIiwiYXVkIjoiaHR0cHM6Ly9hcGkubXlhcHAuY29tIiwiaWF0IjoxNzg5OTk5MjAwLCJleHAiOjE3OTAwMDAxMDB9.A_pKlpe1TtyVdqp85eca4-WPAbbyoBKihlJr0Cg8tWU

It was signed with the made-up secret demo-secret-do-not-use-in-production-0123456789, so never reuse that secret for anything real. Paste the token into the JWT Decoder to see each part. The next three sections take them one at a time.

Part 1: The Header

The header says what kind of token this is and how it was signed. Decoded, ours looks like this:

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

Part 2: The Payload (Claims)

The payload holds the claims, which are key-value pairs. RFC 7519 registers seven standard names:

Claim Name Meaning
iss Issuer Who issued the token, for example https://auth.myapp.com
sub Subject Who the token is about, usually a user ID
aud Audience Who the token is meant for, for example https://api.myapp.com
exp Expiration time The time on or after which the token must not be accepted
nbf Not before The time before which the token must not be accepted
iat Issued at When the token was issued
jti JWT ID A unique ID for the token, which can be used to prevent replay

The payload of the demo token decodes to this:

{ "sub": "user_8f3a9b2c", "name": "Alice Chen", "role": "admin", "iss": "https://auth.myapp.com", "aud": "https://api.myapp.com", "iat": 1789999200, "exp": 1790000100 }

The exp, nbf and iat values are seconds since 1970-01-01 UTC, not milliseconds. Here iat is September 21, 2026 at 14:00:00 UTC and exp is 900 seconds (15 minutes) later. The Timestamp Converter turns any of them into a readable date, and a UUID Generator is a handy source of unique jti values.

Critical reminder: the payload is readable by anyone who has the token. Never put passwords, card numbers or API secrets in it.

Part 3: The Signature

The signature is computed from the encoded header, a dot, the encoded payload, and a key:

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )

When a request arrives, the server recomputes the signature with its key and compares. If one character of the header or payload changed, the signatures no longer match and the token is rejected. That is why a user cannot edit "role": "user" into "role": "admin" without the key.

HS256 vs RS256 vs ES256

Algorithm Type How it works Best for
HS256 Symmetric One shared secret signs and verifies One service that both issues and checks tokens
RS256 Asymmetric Private key signs, public key verifies Several services or third-party login, since verifiers only need the public key
ES256 Asymmetric ECDSA with the P-256 curve Same as RS256, with much smaller keys and signatures

Key strength matters. RFC 7518 requires an HS256 key of at least 256 bits, and RFC 8725 says a human-memorizable password must not be used directly as an HMAC key. Generate the secret randomly.

How the JWT Authentication Flow Works

1. The user signs in POST /auth/login { "email": "alice@example.com", "password": "..." } 2. The server checks the credentials and signs a JWT claims: { sub, role, iss, aud, iat, exp } 3. The server returns the token { "access_token": "eyJhbGciOi...", "expires_in": 900 } 4. The client sends it with each API request Authorization: Bearer eyJhbGciOi... 5. The server verifies the signature and the claims on every request valid -> handle the request invalid or expired -> 401 Unauthorized

This is why JWTs are called stateless: any server that has the right key can check a token without asking a session database. The price is that a token stays valid until it expires unless you build a revocation path, which the security section below covers.

How to Decode a JWT

Base64URL is not quite Base64: it swaps + and / for - and _ and usually drops the = padding. A bare atob() call throws on tokens that contain those characters, and it garbles non-English text, so convert first and decode as UTF-8:

// JavaScript: decode header and payload (no verification) function decodeJwt(token) { const [h, p] = token.split('.'); const decode = part => { const b64 = part.replace(/-/g, '+').replace(/_/g, '/'); const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0)); return JSON.parse(new TextDecoder().decode(bytes)); }; return { header: decode(h), payload: decode(p) }; } const { payload } = decodeJwt(token); console.log(payload.sub); // "user_8f3a9b2c" console.log(payload.exp * 1000 < Date.now()); // true once the token has expired # Python: decode without verifying (inspection only) import base64, json def decode_part(part): padded = part + '=' * (-len(part) % 4) return json.loads(base64.urlsafe_b64decode(padded)) header, payload = (decode_part(p) for p in token.split('.')[:2]) # Shell with jq (GNU base64) echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

For a no-code option, paste the token into the JWT Decoder. It shows the header, payload, signature and whether the token has expired, and it runs in your browser, so the token is not uploaded. It does not verify the signature, because that needs your key. If you only need the plain Base64 conversion, use the Base64 Encoder / Decoder.

Common Security Mistakes With JWTs

1. Trusting a token you only decoded

Anyone can build a token that decodes cleanly. Your API must verify the signature with a key it trusts before it reads a single claim. Decoders, including ours, are for debugging, not for authorization.

2. Not pinning the allowed algorithms

The header is attacker-controlled input. The spec allows "alg": "none" (an Unsecured JWT with no signature), and some older libraries accepted it or let a token switch from RS256 to HS256 and be checked against the public key as if it were a shared secret. RFC 8725 tells implementations to verify the algorithm against an allow-list, and OWASP says to make sure none is not accepted. Current libraries guard against the worst cases (jsonwebtoken picks its defaults from the key type), but state the algorithm yourself and check the claims that identify your service:

// Node.js with jsonwebtoken const claims = jwt.verify(token, secret, { algorithms: ['HS256'], // pin it, never trust the header alone issuer: 'https://auth.myapp.com', audience: 'https://api.myapp.com', });

3. Skipping the expiry and audience checks

The exp and nbf claims are just numbers until your code enforces them. Most libraries check them automatically, but iss and aud usually need to be passed in. Without an audience check, a token issued for your billing API could be accepted by your admin API.

4. Using a weak HMAC secret

A short or guessable HS256 secret can be brute-forced offline from any one token. Use at least 256 random bits, or use RS256 or ES256 so that verifiers never hold the signing key.

5. Keeping tokens where scripts can read them

OWASP's guidance is not to keep session identifiers in local storage, because any script running on the page can read it, so one cross-site scripting (XSS) hole exposes every user's token. An HttpOnly cookie cannot be read by scripts, which is better, but MDN notes the browser still attaches it to requests that injected scripts send. So HttpOnly reduces the damage rather than removing the problem. Fix the XSS holes (a strict Content Security Policy helps), and if the token lives in a cookie, set Secure and SameSite and add CSRF protection.

6. Issuing long-lived access tokens with no revocation plan

A signed JWT stays valid until exp. A 30-day access token that leaks is a 30-day problem. Keep access tokens short and put the long-lived credential in a refresh token you can revoke on the server.

JWTs vs. Session Cookies

Feature JWT (self-contained) Server session cookie
State Claims travel in the token, so the server can verify without a lookup The server stores session data; the cookie holds only an ID
Revocation Hard: valid until it expires unless you add a denylist or keep lifetimes short Delete the session record and it ends immediately
Scaling Any service that has the key can verify Needs a shared session store (database or Redis) or sticky routing
Size Larger, and sent on every request Small session ID
Other sites and apps Works through the Authorization header for APIs and mobile apps Tied to a site; cross-site use needs SameSite=None and Secure, and some browsers block third-party cookies
XSS A token in localStorage can be stolen; in an HttpOnly cookie it cannot be read, but injected script can still use it Same for HttpOnly cookies: unreadable, but usable by injected script
CSRF Not sent automatically when you attach the header yourself; needs protection if stored in a cookie Cookies are sent automatically, so SameSite and CSRF tokens are needed

Use JWTs when several services must verify identity independently, when you serve mobile or native clients, or when you rely on an outside identity provider (OpenID Connect ID tokens are JWTs). Use server sessions when you run a conventional server-rendered site, or when you need to end a session immediately, as with banking or admin consoles. Many teams use a hybrid: a short-lived JWT in an HttpOnly cookie, backed by a server-side refresh session.

For the password side of sign-in, see our password security guide.

Access Tokens and Refresh Tokens

Production systems usually issue two tokens:

// Refresh flow 1. The API answers 401 because the access token expired 2. The client sends the refresh token to POST /auth/refresh 3. The server validates it and issues a new access token 4. The client retries the original request // If the refresh token is expired or revoked, the user signs in again

There is no universal lifetime. OWASP only says a short expiration limits reuse. Amazon Cognito lets you set the access token anywhere from 5 minutes to 1 day and defaults refresh tokens to 30 days, while the Auth0 default access token lifetime is 24 hours. Five to 15 minutes is a common target for user-facing APIs, but that is a design choice, not a standard. For public clients such as single-page apps, RFC 9700 says refresh tokens must be sender-constrained or rotated, meaning each use returns a new refresh token and retires the old one.

Working with tokens all day? The Developer Tools hub has decoders, formatters and encoders that run in your browser.

Frequently Asked Questions

Are JWTs encrypted?

Usually not. A standard signed JWT (a JWS, defined in RFC 7515) is only Base64URL-encoded, which anyone can reverse, so whoever holds the token can read its payload. If the contents must stay hidden, use JSON Web Encryption (JWE, RFC 7516), or keep sensitive data on the server and put only an ID in the token. Either way, send tokens over HTTPS.

Can I revoke a JWT before it expires?

Not by itself, because verifying a token does not involve your database. Common fixes are short-lived access tokens with revocable refresh tokens, or a denylist keyed on the jti and iss claims. OWASP advises against storing the raw token or its hash in the denylist because of token malleability. A denylist adds one lookup per request, which gives up part of the stateless benefit.

Where should I store a JWT in the browser?

OWASP advises against keeping session tokens in localStorage because any script on the page can read it. An HttpOnly, Secure, SameSite cookie cannot be read by scripts, but the browser still sends it with requests that injected script makes, so you still need XSS defenses, and cookie-based tokens need CSRF protection such as SameSite. If you must send the token in an Authorization header, keep it in memory and get a new one from a refresh token cookie after a page reload.

What happens if my JWT signing secret leaks?

Anyone with the secret or private key can create tokens with any claims that your server will accept, which is a full authentication bypass. Rotate the key at once, publish the new key ID, and expect old tokens to be rejected so users must sign in again. With RS256 or ES256 only the private key is sensitive, so services that only verify tokens never hold the signing secret.

How long should a JWT access token last?

There is no standard number. OWASP says a short expiration limits how long a stolen token can be reused, and providers differ: Amazon Cognito allows 5 minutes to 1 day, and the Auth0 default is 24 hours. A common design is 5 to 15 minutes for user-facing APIs, backed by a revocable refresh token. Use the shorter end for sensitive actions, and go longer only when you accept the exposure.

Sources

Figures and rules in this guide were checked against these primary references in September 2026: