Skip to content
ProfitTool
Back to all articles
Developer Tools
6 min read

What Is a JWT? JSON Web Tokens Explained

A plain-language guide to JSON Web Tokens — what they contain, how they are structured, and where they are used in modern authentication systems.

ProfitTool Team

Security & Dev

What a JWT Is

A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe way to represent a set of claims — statements about a subject — that a recipient can verify was not tampered with. It is defined by RFC 7519.

A JWT looks like three Base64URL-encoded segments joined by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKYW5lIiwiZXhwIjoxNzY5OTk5OTk5fQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Split on the dots and decode the first two segments and you get ordinary JSON:

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

// Payload
{ "sub": "12345", "name": "Jane", "exp": 1769999999 }

The third segment is a signature over the first two.

The Single Most Important Property

A JWT is signed, not encrypted. Anyone holding the token can read the payload — no key required, no attack required. Base64URL is an encoding, not a cipher.

The signature guarantees integrity and authenticity: it proves the token was issued by someone holding the signing key and has not been altered since. It guarantees nothing about confidentiality.

This single fact determines what you may put in a token. Never place passwords, API keys, full payment details, medical information, or anything else you would not print in a log line. If you genuinely need a confidential payload, that is JWE (RFC 7516), a different and considerably heavier construction. Plain JWTs — the kind almost everyone means — are JWS (RFC 7515) and are readable by design.

The Three Segments

Declares how the token is signed.

FieldPurpose
algSigning algorithm — HS256, RS256, ES256, EdDSA
typMedia type, conventionally JWT
kidKey ID, so the verifier can pick the right key from a set

Payload

Carries the claims. RFC 7519 registers seven, all optional but all with fixed meaning:

ClaimNameMeaning
issIssuerWho minted the token
subSubjectWho or what the token is about — typically a user ID
audAudienceWho the token is intended for
expExpiration TimeReject at or after this time
nbfNot BeforeReject before this time
iatIssued AtWhen it was minted
jtiJWT IDUnique identifier, used for replay and revocation tracking

exp, nbf and iat are NumericDate values: seconds since the Unix epoch, not milliseconds. Passing Date.now() from JavaScript directly into exp produces a token that appears to expire roughly fifty thousand years from now. This is the most common JWT bug in existence.

Beyond the registered set you may add your own claims — role, tenant_id, permissions. Keep them small: JWTs usually travel in an Authorization header, and servers cap header size (nginx defaults to 8 KB across all headers). A token stuffed with a full user profile and a permission matrix will eventually be rejected by a proxy rather than by your application.

Signature

Computed over base64url(header) + "." + base64url(payload) using the algorithm named in the header.

Signing Algorithms

AlgorithmTypeKey modelUse when
HS256HMAC-SHA256One shared secret signs and verifiesOne service issues and verifies its own tokens
RS256RSA + SHA-256Private key signs, public key verifiesMany services verify tokens from one issuer
ES256ECDSA P-256 + SHA-256Same split, much smaller keys and signaturesSame as RS256, where token size matters
EdDSAEd25519Same split, fast and misuse-resistantGreenfield systems with modern library support

The choice is really about key distribution. HMAC requires every verifier to hold the secret that can also mint tokens — acceptable within one service, dangerous across a fleet. Asymmetric algorithms let you publish a verification key freely (typically as a JWKS endpoint) while the signing key stays in one place. This is why every OIDC provider uses RS256 or ES256.

Why Base64URL Rather Than Base64

Standard Base64 uses +, / and =, all of which have meaning in URLs and are mangled by percent-encoding. Base64URL (RFC 4648 §5) substitutes - for + and _ for /, and drops the = padding, so a token can sit safely in a query string, a path segment, a cookie, or a header without further escaping.

The practical consequence: atob() and standard Base64 decoders will not decode a JWT segment correctly without translating those characters first, and without re-adding the stripped padding. That is covered step by step in How to Decode a JWT.

What JWTs Are Good For

  • Stateless API authentication. The token carries the identity, so any instance of any service can authorise a request without a database lookup or a shared session store.
  • Federated identity. OpenID Connect ID tokens are JWTs. This is the single largest deployment of the format.
  • Service-to-service authorisation in a microservice estate, where the calling service's identity travels with the request.
  • Short-lived, single-purpose links — email verification, password reset, signed download URLs — where the token is the capability and expires quickly.

What They Are Not Good For

  • Sessions you need to revoke instantly. A signed token is valid until it expires, full stop. There is no "log out everywhere" without adding server-side state, which forfeits the statelessness that motivated the choice.
  • Anything confidential, as above.
  • Long-lived credentials. A stolen 30-day token is a 30-day compromise.
  • Frequently changing data. Claims are frozen at issuance. Revoke a permission and every outstanding token still asserts it.
  • Replacing a session cookie in a plain server-rendered app. If you already have a session store and a single backend, a session cookie is simpler, revocable, and keeps nothing sensitive on the client.

JWT vs Server-Side Sessions

JWTSession cookie
StateStateless — nothing stored server-sideSession record per user
RevocationOnly at expiry, unless you add a denylistImmediate
Horizontal scalingAny instance verifies independentlyNeeds a shared session store
Data freshnessFrozen at issuanceRead live on every request
Size on the wireHundreds of bytes to a few KBAn opaque ID

The honest summary: JWTs trade revocability for statelessness. If you find yourself building a denylist, a revocation table, and a lookup on every request, you have rebuilt sessions with extra steps — and a session store would have been the simpler design.

Security: The Failures That Actually Happen

alg: none. RFC 7519 defines an "unsecured JWT" with no signature. A verifier that trusts the header's alg field will accept a token with the signature stripped and alg set to none — forged by anyone. Real libraries shipped this default; it is the origin of the rule below.

Algorithm confusion. A verifier expecting RS256 that also accepts HS256 can be attacked by signing a token with the issuer's public key as the HMAC secret. The public key is public, so the attacker can mint valid tokens.

Both failures share one root cause, and one fix: never let the token tell you how to verify it. Pin the expected algorithm in your verification call.

// Wrong — the attacker chooses the algorithm
jwt.verify(token, key);

// Right — you choose it
jwt.verify(token, key, { algorithms: ["RS256"] });

The rest of the checklist:

  • Verify before you read. Decoding is not validation. A decoded-but-unverified payload is attacker-controlled input.
  • Always set exp. A token without one never expires.
  • Check aud and iss. A valid token issued for a different service is still a valid signature.
  • Keep lifetimes short and use refresh tokens for continuity — see JWT Expiration Explained.
  • Mind storage in browsers. localStorage is readable by any injected script; an httpOnly cookie is not, but needs CSRF protection.
  • Rotate signing keys using kid so old and new keys can coexist during a rollover.

Frequently Asked Questions

Is a JWT encrypted? No. A standard JWT is signed (JWS) and its payload is plainly readable. Encryption is JWE, a separate specification.

Can a JWT be revoked? Not by itself. Revocation requires server-side state — a jti denylist, a token version counter on the user record, or short expiry plus refresh-token rotation.

How big can a JWT be? There is no limit in the specification, but proxies and servers cap header size, commonly around 8 KB total. Keep tokens to a few hundred bytes.

Are JWT and OAuth the same thing? No. OAuth 2.0 is an authorisation framework; a JWT is a token format. OAuth access tokens are sometimes JWTs and sometimes opaque strings. OpenID Connect ID tokens are always JWTs.

Should I use a JWT for my login system? If you have one backend and a session store, a session cookie is usually simpler and safer. JWTs earn their complexity when multiple independent services must verify identity without a shared store.

Decode and pretty-print any token with the JWT Decoder, or inspect every claim with expiry countdowns and human-readable timestamps using the JWT Claims Inspector. For the encoding underneath the dots, see the Base64 & Base64URL Encoder / Decoder.

Tags:#jwt#authentication#security#web development

ProfitTool Team

Security & Dev

Written and maintained by the ProfitTool team. Where an article references a ProfitTool utility, the behaviour described is the behaviour of the tool on this site — you can open it and check the output yourself.

Try the Interactive Tools Mentioned

Free to use with no ProfitTool signup. Data handling varies by tool.