If you build apps, you’ll deal with login and security sooner or later. One of the most common ways to handle login in modern APIs is JWT (JSON Web Token).
In this post, you’ll learn what a JWT is, how it works step-by-step, what’s inside it, and how to use it safely.
Watch my full video explanation here:
Why JWT matters (some real numbers)
APIs run the internet now, and attackers know it.
- Cloudflare found that successful API requests made up 57% of dynamic Internet traffic on its network (based on data observed Oct 2022–Aug 2023).
- Akamai reported 108 billion API attacks from January 2023 to June 2024.
- Akamai also observed 311 billion web app + API attacks in 2024, a 33% year-over-year increase.
- In a 2024 Salt Security report survey, 95% of respondents said they experienced API security problems.
So yes—authentication is not a “later” problem. JWT is popular because it’s fast, works well with APIs, and scales nicely.
What is a JWT token?
A JWT is a compact string that carries “claims” (information) in a way that can be verified because it’s digitally signed. The official definition is in RFC 7519.
Most JWTs you see in real projects are signed tokens (JWS). They help the server confirm:
- this token was created by us
- it wasn’t changed in the middle
- it hasn’t expired
Important detail: JWT is not automatically encrypted. Most JWT payloads are only Base64URL encoded, which means anyone who has the token can decode and read the payload. That’s why you should never store sensitive data (passwords, OTPs, card info, etc.) inside it.
JWT format (the 3 parts)
A JWT usually looks like this:
xxxxx.yyyyy.zzzzz
Those three parts are:
- Header
Tells which algorithm is used for signing (example: RS256, HS256) - Payload
The data (claims). Example: user id, role, expiry time. - Signature
This is what makes JWT trustworthy. It’s created using the header + payload + a secret key (or private key).
If payload changes, signature verification fails.
JWT is “self-contained”: it carries the info + proof it wasn’t tampered with.
How JWT authentication works (step by step)
Here’s the most common real-world flow:
Step 1: User logs in
User sends username/password to your server.
Step 2: Server verifies credentials
If valid, the server creates a JWT (usually an access token).
Step 3: Server sends JWT to client
Client stores it (more on safe storage later).
Step 4: Client calls your API with the token
Client sends:
- Authorization: Bearer <JWT>
Step 5: Server verifies the token
Server checks:
- signature is valid
- token is not expired (
exp) - token was issued by the right system (
iss) - token is meant for this API (
aud)
Only after that, the API returns protected data.
OWASP also recommends validating tokens carefully to avoid common JWT mistakes.
Common JWT claims you should know
Here are the claims you’ll see a lot:
- exp (Expiry time): token is invalid after this
- iat (Issued at): when token was created
- iss (Issuer): who created this token
- aud (Audience): who this token is meant for
- sub (Subject): usually the user id
- jti (JWT ID): a unique id (useful for preventing replay or tracking)
JWT vs Session cookies (simple comparison)
JWT is often called “stateless auth” because the server doesn’t need to store session data in memory/database for every user session.
- Session-based auth: server stores session, client stores session id
- JWT-based auth: client stores token, server verifies token signature
JWT can be great for:
- mobile apps
- SPAs (React/Angular/Vue)
- microservices
- distributed systems
But it’s not “always better”. It’s just a different tradeoff.
Access token vs Refresh token (very important)
Many people mix these up.
Access Token
- short-lived (example: 5–15 minutes)
- sent with every request
- used to access APIs
Refresh Token
- longer-lived (example: days/weeks)
- used only to get a new access token
- should be stored extra carefully
This setup reduces risk because even if an access token leaks, it expires quickly.
JWT security best practices (don’t skip this)
JWT is safe only if you implement it safely. The IETF published best-current-practices guidance in RFC 8725.
Here are the practical rules that save you from trouble:
1) Don’t put sensitive data in the payload
People can decode JWT payload easily. Signed ≠ encrypted.
Good payload: userId, role, permissions, token version
Bad payload: passwords, OTP, email verification codes, personal secrets
2) Keep access tokens short-lived
Short expiry limits damage if a token leaks.
3) Always validate signature + claims on the server
Never “trust” claims just because they exist. Validate:
- signature algorithm
expissaud
This is a core point in JWT best-practices docs.
4) Never accept alg: none
Some old libraries used to accept tokens with alg=none, which is dangerous. OWASP calls this out.
5) Prefer strong algorithms and good key management
- HS256 uses a shared secret (same key signs and verifies)
- RS256 uses private/public key pair (better for distributed systems)
(There’s no single perfect answer, but don’t use weak keys.)
6) Use HTTPS everywhere
JWT must not travel over HTTP. OWASP recommends HTTPS for REST APIs to protect credentials/tokens in transit.
7) Store tokens safely (this is where most apps fail)
Best option for many web apps: Secure, HttpOnly cookies (reduces XSS token theft)
But if you use cookies, also think about CSRF protection (SameSite + CSRF token patterns).
If you store JWT in localStorage, it’s more exposed to XSS. (Not always wrong, but it increases risk.)
“Logout” with JWT (how it really works)
Because JWT is stateless, you can’t magically “delete it from the server” unless you design for it.
Common solutions:
- Use short-lived access tokens + refresh tokens
- Maintain a refresh token allowlist (and revoke refresh tokens on logout)
- Use token versioning (like
tokenVersion) stored on user record - For high-security apps: keep a blocklist using
jti(but this reintroduces state)
Quick JWT checklist (copy/paste)
Before deploying JWT auth, check this:
- Access token expires fast (5–15 min)
- Refresh token is protected and revocable
- Server verifies signature + exp + iss + aud
- No sensitive data in payload
- HTTPS enforced
alg=nonerejected- Keys are rotated / manageable (especially if using RS256/JWKS)
- Storage plan is safe (cookies + CSRF, or other controlled approach)