What a token is and why you need it

HTTP is stateless: the server does not know who you are on each request. After sign-in the server gives you proof (a token) and the client attaches it to every request: Authorization: Bearer <token>. There are two main models: a server session (the token is only a key, state stays on the server) and a self-verifying token such as a JWT.

JWT: header, payload, signature and server verification
JWT: header, payload, signature and server verification

A JWT has three parts: header.payload.signature. Each part is base64url-encoded JSON (except the signature). The header holds the algorithm (alg), the payload holds claims (sub, exp, iat, iss, aud). The signature is header.payload signed with a secret (HS256) or a private key (RS256, ES256).

⚠️ A JWT is signed, not encrypted: anyone can read the payload. Never put passwords, card numbers or other sensitive data in it.


Reading a JWT on the client

The client does not verify the signature (it has no key) but can read useful things from the payload: name, role (for the UI), exp (when to renew). Base64url differs from plain base64 by using - and _ and having no =. For UTF-8, decode the atob result with TextDecoder.

javascript
const token =
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
  'eyJzdWIiOiI0MiIsIm5hbWUiOiJBeXNlbCIsInJvbGUiOiJlZGl0b3IiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTkwMDAwMDAwMH0.' +
  'c2lnbmF0dXJl';

function decodePart(part) {
  const base64 = part.replace(/-/g, '+').replace(/_/g, '/');
  const bytes = Uint8Array.from(atob(base64), (ch) => ch.charCodeAt(0));
  return JSON.parse(new TextDecoder().decode(bytes));
}

function decodeJwt(jwt) {
  const [header, payload] = jwt.split('.');
  return { header: decodePart(header), payload: decodePart(payload) };
}

function isExpired(payload, now = Date.now()) {
  return payload.exp * 1000 <= now;
}

const { header, payload } = decodeJwt(token);
console.log(header.alg, payload.sub, payload.role);
console.log('expired in 2026:', isExpired(payload, Date.UTC(2026, 0, 1)));
console.log('expired in 2031:', isExpired(payload, Date.UTC(2031, 0, 1)));

Output:

text
HS256 42 editor
expired in 2026: false
expired in 2031: true