What is a JSON Web Token (JWT)?

Last updated: July 2026

A JSON Web Token is a compact, URL-safe token that carries signed JSON claims, letting servers verify requests without stored sessions. That is RFC 7519’s own framing — “a compact, URL-safe means of representing claims to be transferred between two parties” — and the two ideas in it carry everything that follows: the token contains its facts, and a signature makes those facts checkable by anyone holding the right key, with no database in the loop.

Key takeaways

QuestionAnswer
The shapeheader.payload.signature — three Base64Url parts joined by dots
The trickAnyone can decode it; only key-holders can forge it
Not encryptionThe payload is readable — signed ≠ secret (that’s JWE’s job)
The tradeStateless verification ↔ no built-in revocation until exp
The disciplinePin algorithms · validate iss/aud/exp · short lifetimes + refresh rotation

A JWT, decoded

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJ1c3ItOGZrMiIsInJvbGUi… . dBjftJeZ4CVP…

header     { "alg": "HS256", "typ": "JWT" }
payload    { "sub": "usr-8fk2",  "role": "editor",
             "iss": "https://api.example.com",  "aud": "example-web",
             "iat": 1767024900,  "exp": 1767025800 }        ← 15-minute life
signature  HMACSHA256( base64url(header) + "." + base64url(payload), secret )

Anyone can DECODE the first two parts — Base64Url is packaging, not encryption.
Only key-holders can FORGE the third — which is the entire trick.

The contrast worth seeing in code — the stateful alternative every JWT decision is measured against:

// JavaScript / Node.js — Back4app JS SDK
// The stateful contrast: Back4app issues revocable session tokens
const user = await Parse.User.logIn('ada', 'correct-horse-battery');
const token = user.getSessionToken(); // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
await Parse.User.logOut(); // token invalid NOW — no waiting for an exp claim

How verification works

JWT issue and verify flowAt login the server signs a token containing claims and returns it to the client. The client stores it and sends it as a bearer token on each request. The server verifies the signature with its key and validates the claims, accepting or rejecting without any session lookup.

Authorization: Bearer eyJ…

valid

tampered / expired / wrong aud

Login
(credentials verified once)

Server signs JWT
claims + key

Client holds token

Any server with the key:
verify signature · validate claims

Request proceeds
no session lookup

401

At login the server signs a token containing claims and returns it to the client. The client stores it and sends it as a bearer token on each request. The server verifies the signature with its key and validates the claims, accepting or rejecting without any session lookup.

Precision the explainers skip: “JWT” names the claims format; what everyone actually passes around is a JWS (RFC 7515) — the signed serialization — while JWE is the encrypted sibling for payloads that must stay unreadable. And verification is two jobs, not one: check the signature, then validate the claimsexp and nbf against the clock, iss against your allowlist of issuers, aud against this service’s identifier. A perfectly signed token that’s expired, from the wrong issuer, or minted for a different audience is a perfectly signed attack.

Claims: the payload’s vocabulary

ClaimNameVerifier’s job
issIssuerIs this an issuer I trust?
subSubjectWho is this about — the stable user ID
audAudienceWas this minted for me? Reject others’ tokens
expExpirationReject after this Unix timestamp
iat / nbfIssued at / not beforeSanity-check the validity window
jtiToken IDUnique handle — the hook a denylist needs
customRoles, tenant, plan…App semantics — keep minimal, never secret

Keep payloads lean twice over: tokens ride every request as headers (cookies cap near 4 KB, and each claim is repeated bandwidth), and everything in them is legible to whoever holds the token.

JWT vs. session tokens

JWT (stateless)Session token (stateful)
The token isThe state itself, signedAn opaque pointer to server state
Per-request costSignature check, no lookupOne session-store lookup
RevocationNone until exp — by designInstant — delete the session
Cross-service authAny service with the key verifiesServices must share the session store
Logout meansClient discards; token stays validToken actually dies
Best homeAPIs, microservices, third-party verificationSingle-backend apps, sensitive sessions

The unfashionable truth several best-practice guides now lead with: for a server-rendered app with one backend, framework sessions are simpler and more controllable — JWTs earn their keep when tokens must be verified across services or by parties who shouldn’t phone home per request.

The revocation problem, honestly

Statelessness is the inability to revoke — the same property, described twice. A signed token is valid until exp no matter what happened since: logout, password change, account ban. Every fix reintroduces state, so choose which flavor: a denylist keyed on jti + iss (the OWASP-recommended construction) checked per request — small state, but state; key versioning, which revokes everyone at once; or the standard architecture — short-lived access tokens (5–15 minutes) plus revocable refresh tokens with rotation: each refresh issues a new refresh token and retires the old, so a stolen one dies on first replay, and reuse of a retired token flags theft and kills the whole token family. Revocation latency then equals the access token’s lifetime — which is the real reason those lifetimes are short.

Choosing an algorithm — and the attacks on the choice

HS256 (HMAC)RS256 (RSA)EdDSA / ES256
KeysOne shared secretPrivate signs, public verifiesPrivate signs, public verifies
Verifiers needThe secret (can also forge!)Public key onlyPublic key only
FitsIssuer = verifier, single partyMulti-service, third-partySame, smaller/faster signatures
Sharp edgeShort secrets brute-force offline from one captured token — use ≥256 random bitsLarger tokens, slowerNewer library support

The attack section RFC 8725 exists for, in three sentences. alg: "none" is a legal header value — libraries that honor it accept unsigned tokens. The confusion attack: a verifier that lets the token’s header pick the algorithm can be handed an “HS256” token signed with the server’s public RSA key as the HMAC secret — a public value used as a private one. Both die the same way: the verifier pins its accepted algorithms and keys in configuration and never trusts the header to choose.

Where to store JWTs in a browser

LocationXSS steals it?CSRF sends it?Survives refresh?Verdict
localStorageYesNoYesAvoid — script-readable
Plain cookieYes (script-readable)YesYesWorst of both
HttpOnly + Secure + SameSite cookieNoMitigated by SameSiteYesGood — for the refresh token
In memoryOnly while injectedNoNoGood — for the access token
Backend-for-frontend holds tokensBrowser never has themCookie rules applyYesStrongest for SPAs

The threat model, not the folklore: localStorage trades CSRF-immunity for XSS-theft, cookies trade the reverse — which is why the consensus splits the pair: access token in memory, refresh token in a hardened cookie.

Common use cases

  • API authentication — the bearer token behind Authorization headers on REST and GraphQL APIs.
  • Microservice identity — one gateway-issued token verified independently by every service, no shared session store.
  • OpenID Connect ID tokens — the signed identity assertion in every social login — always a JWT.
  • Cross-system handoffs — signed links, webhook payloads, download grants: claims verified by a party that can’t call you back.
  • Stateless authorization hints — roles and tenant IDs carried in claims, with the authoritative check still server-side.

Should you use JWTs or sessions? A decision matrix

Your situationReach for
Single backend, server-rendered appSessions — simpler, instantly revocable
Public API consumed by many servicesJWTs, asymmetric keys
Microservices behind a gatewayJWTs — verify locally, no shared store
Instant lockout is a hard requirementSessions, or JWTs + denylist and short exp
Third parties must verify your assertionsJWTs — that’s the format’s home game
Mobile app against a BaaSThe platform’s session mechanism — it chose already

Limitations and trade-offs

  • Irrevocability is structural. Every mitigation — denylists, short lifetimes, rotation — is a partial return of the state you removed; price that in before choosing stateless.
  • Claims go stale. Roles snapshot at issuance; a demoted admin stays an admin until expiry. Short lifetimes bound the staleness window.
  • The payload is public. Treat it as readable by the user and any token thief — identifiers yes, secrets and PII no.
  • Library defaults have burned people. Algorithm pinning, claim validation, and typ checking are your configuration responsibilities, not the library’s guarantees.
  • Size compounds. Every claim rides every request; generous payloads tax mobile bandwidth and can overflow cookie limits.

Tokens on Back4app

Back4app is an open-source Backend-as-a-Service (BaaS) platform that combines a managed database, auto-generated REST and GraphQL APIs, authentication, file storage, and Cloud Code serverless functions. The platform’s own choice illustrates the decision matrix: client sessions use revocable, server-side session tokens — the code tabs above — so logout, password changes, and dashboard-side session termination take effect immediately, with no expiry window to wait out. JWTs appear where they belong: identity providers’ OIDC ID tokens are verified server-side by auth adapters during social login, and Cloud Code functions can mint or verify JWTs for third-party handoffs using standard libraries — with the signing secret in server-side configuration, never shipped to clients. Stateful where control matters, stateless at the edges where verification must travel: the architecture this article argues for, preassembled.

Frequently asked questions

What is a JWT in simple terms?

A signed, Base64-encoded JSON "ID card" a server issues at login. The client presents it on every request, and the server verifies the signature instead of looking up a session — the token itself carries who you are and until when. Officially it rhymes with "jot," per the RFC that defines it.

What are the three parts of a JWT?

Header (token type and signing algorithm), payload (the claims — the actual JSON data), and signature, each Base64Url-encoded and joined by dots into header.payload.signature. The signature is computed over the first two parts, so any tampering with them breaks verification.

Is a JWT encrypted?

No — encoded, not encrypted. Base64Url is a transport format anyone can reverse; paste any JWT into a decoder and the payload is readable. The signature prevents tampering, not reading. Never put secrets or sensitive personal data in a JWT payload; the encrypted variant (JWE) exists for genuine confidentiality needs.

What is the difference between a JWT and a session token?

Where the state lives. A session token is an opaque ID pointing at server-side state — one lookup per request, revocable instantly. A JWT carries the state inside itself — no lookup, verifiable by any service holding the key, but valid until expiry no matter what. Stateless scaling versus instant control.

Where should I store a JWT in the browser?

Not in localStorage — any injected script can read it, making XSS a token theft. Current consensus: keep the access token in memory, the refresh token in an HttpOnly, Secure, SameSite cookie, and consider the backend-for-frontend pattern that keeps tokens out of the browser entirely.

Can a JWT be revoked?

Not by design — a signed token is valid until its exp claim, which is the price of statelessness. The workarounds all reintroduce state: a denylist keyed on the jti claim, versioned keys, or — the standard answer — very short access-token lifetimes paired with revocable, rotating refresh tokens.

What are JWT claims?

The payload's fields. The RFC registers seven: iss (issuer), sub (subject), aud (audience), exp (expiration), iat (issued at), nbf (not before), and jti (token ID) — plus whatever custom claims you add, like roles. Verifiers must actually validate them; a signature check alone accepts expired tokens meant for other services.

What is the difference between HS256 and RS256?

Symmetry. HS256 signs and verifies with one shared secret — fine only when issuer and verifier are the same party, and only with a long random secret. RS256 (and the newer EdDSA) signs with a private key while anyone verifies with the public one — the default whenever more than one service checks tokens.

Is JWT secure?

The format is sound; implementations fail. The classic holes: accepting alg none, letting the token's header choose the algorithm (the RS256-to-HS256 confusion attack), brute-forceable short HMAC secrets, and skipping aud/iss/exp validation. Pin your algorithms, validate every claim, keep lifetimes short.

What is the difference between JWT and OAuth?

Different categories: JWT is a token format; OAuth 2.0 is an authorization framework. They compose — OAuth flows commonly issue access tokens that happen to be JWTs, and OpenID Connect ID tokens always are. One says how a token is built; the other says how tokens get handed out.

Related terms

Compare with

Further reading

Ready to build your backend?

Start your project on Back4app in minutes — database, auth, APIs, and Cloud Code included. No credit card required.

Written and reviewed by Back4app Engineering, Back4app Engineering · Published 2026-07-24