JWT simulator
A JSON Web Token is a signed string that tells an API who you are without the API looking anything up. Log in below and the server signs a real token in your browser; call the API, let the token expire, refresh it, log out, and try to forge one. Further down: how a JWT is built, what to validate, refresh tokens, and the classic attacks.
No token yet. Press Log in.
- Nothing yet.
How to use the simulator
There are three parties: a client (a browser or app), an authorization server that checks passwords and issues tokens, and an API that accepts requests carrying an access token. Press Log in and the server signs an access token with HMAC-SHA256 using the Web Crypto API in your browser; the left panel shows it, split into its three parts and decoded. Every API call verifies the signature and the claims for real. Time is simulated, so you can jump ahead instead of waiting fifteen minutes.
- Log in, call the API, then add 15 minutes and call again: 401, expired. Press Refresh to get a new pair of tokens, and the call works.
- Press Change role to admin: the payload is edited and the old signature copied across. The API rejects it, because the signature no longer matches.
- Press alg: none: the forged token claims to need no signature. The API refuses any algorithm but the one it expects.
- Refresh once, then press Replay an old refresh token, as an attacker who copied it would. The server sees a used token come back, revokes the whole session, and the real client's next refresh fails too.
- Log out, then call the API: it still works until the access token expires. That is the revocation problem.
The tokens are genuine: one copied from the left panel decodes and verifies with any JWT library given the server's demo secret, semicolony-demo-secret-change-me. Nothing is sent over the network.
What is a JWT?
A JSON Web Token (JWT) is a compact string that carries a set of claims, such as a user's ID, their role and an expiry time, together with a signature that proves who issued them and that they have not been altered. It is designed to be passed around in HTTP headers and URLs, most often as Authorization: Bearer <token>.
The point of a JWT is that it is self-contained. A server that receives one checks the signature with a key it already has and reads the claims directly. It does not need to look up a session in a database, which is why JWTs are popular for APIs, for systems with many services, and in OAuth 2.0 and OpenID Connect, where an ID token is always a JWT.
JWT structure, decoded
Here is the example token from jwt.io, signed with HS256 and the secret your-256-bit-secret. It was signed again when this page was built, and the result matched jwt.io's published token.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
| Part | Encoded | Decoded |
|---|---|---|
| Header | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 | {"alg":"HS256","typ":"JWT"} |
| Payload | eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ | {"sub":"1234567890","name":"John Doe","iat":1516239022} |
| Signature | SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c | HMAC-SHA256 of the first two parts joined by a dot, keyed with the secret, then base64url-encoded |
base64url is base64 with - and _ instead of + and /, and no = padding, so the token is safe in URLs. It is an encoding, not encryption: anyone can reverse it. To verify, the receiver recomputes the HMAC over eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ with its own copy of the secret and compares the result with the third part, using a constant-time comparison.
Claims
The payload is a JSON object of claims. RFC 7519 registers seven short names; all are optional, but most access tokens use several. Applications add their own, such as role or scope.
| Claim | Name | Meaning |
|---|---|---|
| iss | Issuer | Who created and signed the token, such as the authorization server's URL. |
| sub | Subject | Who the token is about, usually a user ID. |
| aud | Audience | Which service the token is meant for. An API should reject tokens addressed to someone else. |
| exp | Expiration time | Seconds since 1970 (UTC) after which the token must be rejected. |
| nbf | Not before | The token must not be accepted before this time. |
| iat | Issued at | When the token was created. |
| jti | JWT ID | A unique ID, useful for detecting replays or listing revoked tokens. |
HS256, RS256 and ES256
The alg header names the signature algorithm, from the list in RFC 7518. The choice decides who can create tokens and who can only check them.
| Algorithm | Kind | Signs with | Verifies with | Use when |
|---|---|---|---|---|
| HS256 | HMAC with SHA-256 | Shared secret | The same secret | One service both issues and checks tokens |
| RS256 | RSA with SHA-256 | Private key | Public key | Many services verify tokens from one issuer |
| ES256 | ECDSA, P-256 curve | Private key | Public key | As RS256, with much smaller keys and signatures |
| EdDSA | Ed25519 | Private key | Public key | As ES256; fast and hard to misuse |
With HS256, every service that can verify tokens holds the secret and could therefore mint its own. With the public-key algorithms, only the issuer holds the private key, and services fetch the public keys from a JWKS (JSON Web Key Set) URL, picking the right key by the kid (key ID) header. Publishing several keys at once lets the issuer rotate keys without breaking tokens already in use. An HS256 secret should be at least 256 random bits; a short or guessable secret can be found offline from a single token, for example with hashcat's JWT mode.
Validating a JWT
Decoding a JWT is not validating it. An API that accepts a token should, in this order:
- Check that it has three parts and that the header and payload are valid JSON.
- Check the algorithm against a fixed allowlist chosen by the API. Never let the token's header decide.
- Verify the signature with the expected key.
- Check
exp(andnbfif present) against the current time, allowing at most a minute or so of clock skew. - Check
issandaud, so a token from another issuer, or one meant for another service, is refused. - Then apply the application's own rules, such as the user's role or scopes.
RFC 8725, "JSON Web Token Best Current Practices", lists these checks and the attacks they prevent. Use a maintained library and configure it with the expected algorithm, issuer and audience, rather than writing verification by hand.
Access tokens and refresh tokens
Because an API accepts a valid JWT without asking anyone, a stolen access token works until it expires. So access tokens are kept short-lived, typically 5 to 60 minutes. To avoid making users log in again that often, the authorization server also issues a refresh token: a long-lived credential that the client sends only to the authorization server, to get a new access token.
Refresh tokens are usually opaque random strings that the server stores, as in the simulator, so that it can check and revoke them; the API never sees them. With rotation, each refresh returns a new refresh token and marks the old one as used. If a used one comes back, either the client is confused or someone copied it, and the safe response is to revoke every token from that login. RFC 9700 (2025), the OAuth 2.0 security best current practice, recommends rotation, or tokens bound to the client's key, for public clients such as browser and mobile apps.
Logout and revocation
Logging out with JWTs is harder than it looks. The server can revoke the refresh token at once, but the access token is a signed string in the client's hands, and every API will keep accepting it until exp. The options:
- Short access tokens. The simplest approach: accept that access lasts a few more minutes after logout.
- A denylist. Record the
jtiof revoked tokens until they expire, and have APIs check it. This brings back a lookup on every request, though a small, fast one. - Token introspection. APIs ask the authorization server about each token (RFC 7662), which works best with opaque tokens.
- Key rotation. Changing the signing key invalidates every token signed with the old one, for all users at once.
JWT vs session cookies
| Aspect | Server-side session | JWT |
|---|---|---|
| What the client holds | A random session ID | The claims themselves, signed |
| Checking a request | Look up the session in a store | Verify the signature; no lookup |
| Revoking | Delete the session | Wait for expiry, or keep a denylist |
| Across many services | Needs a shared session store | Any service with the key can verify |
| Size per request | A few dozen bytes | Often several hundred bytes or more |
| Good fit | A single web application | APIs, microservices, single sign-on |
They are not mutually exclusive: a JWT can be stored in a cookie, and many systems use a session cookie with the browser while services behind it pass JWTs to each other.
Storing tokens in the browser
A token in localStorage or a JavaScript variable can be read by any script on the page, so one cross-site scripting (XSS) bug lets an attacker take it. A token in a cookie marked HttpOnly cannot be read by scripts, Secure keeps it off unencrypted connections, and SameSite=Lax or Strict limits when it is sent from other sites, though state-changing requests still need protection against cross-site request forgery (CSRF). A common pattern for single-page apps is a backend-for-frontend: a small server that holds the tokens and gives the browser only a session cookie.
Common JWT vulnerabilities
- alg: none. The specifications allow an "unsecured JWT" with no signature. In 2015, Tim McLean showed that several libraries would accept one when the token said so, letting anyone forge any claims. The simulator's API refuses it, as every verifier should.
- Algorithm confusion. If a server expects RS256 but lets the header choose, an attacker can sign a token with HS256 using the server's public key as the HMAC secret, and a naive library will verify it with that same public key. Pinning the algorithm per key prevents it.
- Weak HMAC secrets. An HS256 token can be attacked offline: guess the secret, recompute the signature, compare. Short or dictionary-word secrets fall quickly.
- Trusting the payload without verifying. Decoding is not verifying. Code that reads claims from an unverified token trusts anyone.
- Header-controlled key lookup. The
kid,jkuandx5uheaders point to keys; a server that fetches keys from URLs in the token, or useskidunsafely in a file path or query, can be tricked into using an attacker's key. - Secrets in the payload. The payload is readable by anyone who sees the token, including browser extensions and logs.
JWTs in interviews
Typical questions: what the three parts are and whether the payload is secret (it is not); how an API verifies a token without a database; HS256 versus RS256 and when each fits; why access tokens are short and how refresh tokens work; how you would log a user out everywhere; JWT versus sessions; and where to store tokens in a browser. In system design questions, mention that the API gateway or each service validates tokens locally using the issuer's public keys, cached from its JWKS endpoint.
JWT questions and answers
What is a JWT?
A JSON Web Token is a compact, signed string that carries claims, such as who a user is and when the token expires. It has three base64url-encoded parts separated by dots: a header naming the signing algorithm, a payload of claims, and a signature over the first two. A server that knows the key can check the signature and trust the claims without a database lookup. JWT is defined in RFC 7519.
Is a JWT encrypted?
A normal JWT is signed, not encrypted. Anyone who has the token can decode the header and payload; the signature only proves they have not been changed. Do not put secrets or sensitive personal data in a JWT payload. If the contents must be hidden, use an encrypted token (JWE).
What is the difference between HS256 and RS256?
HS256 signs with HMAC-SHA256 and one shared secret, so every service that verifies tokens can also create them. RS256 signs with an RSA private key and verifies with the public key, so the authorization server alone can issue tokens and any service can verify them using public keys, usually published at a JWKS URL. ES256 and EdDSA are similar to RS256 with smaller keys and signatures.
What is a refresh token?
A refresh token is a long-lived credential that the client sends only to the authorization server, to get a new short-lived access token when the old one expires. Keeping access tokens short limits the damage if one leaks; the refresh token lets users stay logged in without entering their password again. It is usually an opaque random string stored by the server, so it can be revoked.
What is refresh token rotation?
With rotation, every use of a refresh token returns a new one and invalidates the old. If an old refresh token is ever presented again, the server knows one copy was stolen and revokes the whole session. RFC 9700, the OAuth 2.0 security best current practice, recommends rotation or sender-constrained tokens for clients that cannot keep a secret, such as browser apps.
How do you revoke a JWT?
A JWT stays valid until its exp time, because an API checks it without asking anyone. To end access sooner, keep access tokens short-lived and revoke the refresh token, or have APIs check a denylist of revoked token IDs (the jti claim), which reintroduces a lookup on every request. Rotating the signing key revokes every token at once.
JWT vs session cookie: which is better?
A server-side session stores state on the server and gives the browser a random session ID in a cookie; it is easy to revoke and simple to secure. A JWT carries its state in the token, so any service can verify it without shared storage, which suits APIs and systems with many services, but revocation is harder. For a single web application, sessions are usually simpler; JWTs fit token-based APIs and single sign-on.
Where should I store a JWT in the browser?
In an HttpOnly, Secure cookie with SameSite set, which JavaScript cannot read, so cross-site scripting cannot steal it; CSRF protection is then needed for state-changing requests. Storing tokens in localStorage is simpler but exposes them to any script running on the page. Many teams avoid the question with a backend-for-frontend that keeps tokens on the server.
What is the alg none vulnerability?
The JWT standards define an "unsecured" token with "alg": "none" and no signature. Some early libraries trusted the header’s alg value, so an attacker could change the claims, set alg to none, drop the signature, and be accepted. In 2015 several libraries were found vulnerable. The fix is for the verifier to decide which algorithm it accepts and ignore the header’s choice.
Further reading
- RFC 7519, 2015JSON Web Token (JWT)The format and the registered claims.
- RFC 8725, 2020JSON Web Token Best Current PracticesThe validation rules and the attacks they prevent.
- RFC 9700, 2025Best Current Practice for OAuth 2.0 SecurityRefresh token rotation, sender-constrained tokens and more.
- RFC 7515, 2015JSON Web Signature (JWS)How the signature and compact serialization work.
- OWASPJSON Web Token Cheat SheetPractical defences, including token storage and revocation.
- SemicolonyHow OAuth worksWhere access and refresh tokens come from.