What Is a JWT?
Objectives
By the end of this lesson, you should be able to:
- Explain the three parts of a JWT
- Explain what “stateless” means, and how it differs from sessions
- Explain what a JWT’s signature actually protects against
💡 Why this matters: Sessions (Module 2) need server-side storage, a session store, that the server checks on every request. A JWT needs none of that, this lesson covers what a JWT actually is before Lesson 2 starts signing and verifying real ones.
⚠️ A note on verification: every snippet and every output in this lesson was actually run.
The Three Parts
A JWT (JSON Web Token) is a single string, made of three parts separated by periods: header.payload.signature.
const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 1, email: 'erin@example.com' }, 'dev-only-secret', { expiresIn: '1h' });
console.log(token);
console.log('Parts:', token.split('.').length);
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImVtYWlsIjoiZXJpbkBleGFtcGxlLmNvbSIsImlhdCI6MTc4NTg2NTUzNiwiZXhwIjoxNzg1ODY5MTM2fQ.bYj0De32VvE0f4SAWhb6SD36zuQME3WLbAb54S37boM
Parts: 3
Decoding the Header and Payload
The first two parts are just base64url-encoded JSON, readable by anyone, no secret needed:
const [headerB64, payloadB64] = token.split('.');
console.log('Header:', JSON.parse(Buffer.from(headerB64, 'base64url').toString()));
console.log('Payload:', JSON.parse(Buffer.from(payloadB64, 'base64url').toString()));
Header: { alg: 'HS256', typ: 'JWT' }
Payload: {
userId: 1,
email: 'erin@example.com',
iat: 1785865536,
exp: 1785869136
}
The header describes the token itself (the signing algorithm). The payload (also called “claims”) holds the actual data, here userId and email, plus iat (issued at) and exp (expires at), both added automatically by expiresIn: '1h'.
An Important Point: JWTs Are Not Encrypted
Anyone who has a JWT can decode its header and payload without any secret at all, as just shown, Buffer.from(..., 'base64url') needed nothing but the token itself. Never put a password, a secret, or anything genuinely sensitive in a JWT’s payload. A user ID and email, as shown here, are fine, anything meant to stay private is not.
The Third Part: the Signature
The signature is what actually makes a JWT trustworthy. It’s generated from the header, the payload, and a secret key known only to the server, verifying a token means recomputing that signature and checking it matches. If anyone tampers with the payload, even changing one character, the signature no longer matches, and verification fails, covered concretely in the next lesson.
Stateless: What It Actually Means
A session (Module 2) requires the server to check a session store on every request, “does this session ID still exist, and what’s in it?” A JWT carries its own data and its own proof of authenticity in the token itself, verifying it is a pure computation, no database or session store lookup required at all. That’s what “stateless” means here: the server doesn’t need to remember anything about a specific token to trust it, it only needs to know its own secret key.
Try It
- Generate a JWT with
jwt.sign(), and decode its header and payload manually, without callingjwt.verify(). - Explain, in your own words, why putting a user’s plain-text password in a JWT’s payload would be a serious mistake.
- Explain the difference between “stateless” (JWT) and “stateful” (session) authentication, in terms of what the server needs to check on each request.
Recap
- A JWT is
header.payload.signature, the header and payload are just base64url-encoded JSON, readable without any secret. - The signature, generated from a server-only secret key, is what makes a JWT trustworthy, tampering with the payload invalidates it.
- JWTs are stateless: verifying one is a pure computation against the server’s secret, no session store lookup required.
Next lesson: actually signing and verifying tokens, and what happens when one is tampered with or expired.