Entrance
JWT (JSON Web Token) is the most widely used stateless authentication method in modern APIs. Since traditional session structures require state storage on the server, they create additional burden in distributed and microservice-based systems. JWT eliminates this situation and allows the server to verify the request without keeping state.
In this guide, you will set up end-to-end JWT-based authentication with Express.js, apply the access-renewal token logic, and learn all security stages.
What Will You Learn in This Guide?
- JWT structure (Header, Payload, Signature)
- Access & Refresh token architecture
- writing authenticateToken middleware
- Secure refresh token storage with HttpOnly cookie
- Token refresh flow (/auth/refresh)
- Preventing replay attacks with token versioning
- Role-based authorization (RBAC)
1. Creating the Express.js Project
Project start
npm init -y
Opening ES Modules
"type": "module"
Required packages
npm install express dotenv jsonwebtoken cookie-parser
Simple project structure
src/
app.js
middleware/
routes/
controllers/
config/
2. Express Startup File
import express from 'express';
import dotenv from 'dotenv';
// Ortam değişkenlerini yükler
dotenv.config();
const app = express();
// JSON gövdesini ayrıştırır
app.use(express.json());
// Sunucuyu başlatır
app.listen(3000, () => console.log('Sunucu 3000 portunda çalışıyor'));
3. Understanding JWT Structure (REINFORCED Section)
JWT consists of three parts:
| Section | Description | Example |
|---|---|---|
| Header | Algorithm & token type | { "alg": "HS256", "typ": "JWT" } |
| Payload | User ID + claims | { "sub": "123", "role": "user", "exp": 1767215999 } |
| Signature | Header + Payload signature with private key | Cryptographic hash |
✔ Payload is not encrypted, just Base64URL encoded. ✔ Sensitive data is NEVER added to the payload.
4. Creating Access Tokens
JWT_SECRET="guclu-gizli-anahtar"
import jwt from "jsonwebtoken";
// Access token üretir (15 dk)
function generateAccessToken(userId, role) {
const payload = { sub: userId, role };
const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: "15m",
});
return token;
}
✔ This token must be short-term. ✔ If it is stolen, the damage will be low.
5. JWT Authentication Middleware
import jwt from "jsonwebtoken";
// Korunan rotalar için doğrulama middleware'i
export function authenticateToken(req, res, next) {
const header = req.headers["authorization"];
const token = header?.split(" ")[1]; // Bearer kısmını atla
if (!token)
return res.status(401).json({ message: "Token bulunamadı" });
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err)
return res.status(403).json({ message: "Token geçersiz" });
req.user = decoded;
next();
});
}
✔ There is no token → 401 Unauthorized ✔ There is a token but it is invalid → 403 Forbidden
6. Access Token & Refresh Token Architecture
| Token | Duration | Purpose | Storage |
|---|---|---|---|
| Access | 15 min | Validating API requests | Memory / Authorization header |
| Refresh | 7 days | Getting new access token | HttpOnly + Secure cookie |
✔ Refresh token is never placed in localStorage. ✔ HttpOnly is a must to protect from XSS.
7. Refresh Token Endpoint (/auth/refresh)
import jwt from "jsonwebtoken";
// HttpOnly cookie okumak için cookie-parser gerekir
export async function refreshTokenHandler(req, res) {
const refresh = req.cookies?.refreshToken;
if (!refresh)
return res.status(401).json({ message: "Refresh token yok" });
// Refresh doğrulama
jwt.verify(refresh, process.env.REFRESH_SECRET, (err, decoded) => {
if (err)
return res.status(403).json({ message: "Refresh token geçersiz" });
// Yeni access token oluştur
const newAccess = jwt.sign(
{ sub: decoded.sub, role: decoded.role },
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
res.json({ accessToken: newAccess });
});
}
8. Token Versioning & Replay Attack Protection (REINFORCED Section)
Long-lived refresh tokens are very suitable for stealing. There are two ways to prevent this:
1. Token Versioning (Most powerful method)
To the database:
user.tokenVersion = 1
Into refresh token:
{ "sub": "123", "role": "user", "ver": 1 }
When renewal comes:
Give in token
Compared to tokenVersion in DB
When a new refresh token is produced, version → +1 is made
Old tokens automatically become trash
2. Revocation List
The refresh token is given a UUID. When logout, this ID is added to the cancellation list.
Redis is the fastest solution here.
9. Role Based Authorization (RBAC)
export function adminOnly(req, res, next) {
if (req.user?.role !== "admin") {
return res.status(403).json({ message: "Yetkin yok" });
}
next();
}
Usage:
app.get("/admin/panel", authenticateToken, adminOnly, (req, res) => {
res.json({ message: "Admin paneline hoş geldin!" });
});
10. Security Recommendations (Updated)
Access token → short-lived
Refresh token → HttpOnly + Secure cookie
Private keys in .env
Use HS256 or RS256 as algorithm
Putting confidential data in Payload
Use versioning for refresh token
HTTPS should be mandatory
Putting JWT in localStorage
11. Frequently Asked Questions
1. Is JWT encrypted?
No, it's just signed.
2. Why are there two tokens?
Access is short-lived, refresh is long-lived. Provides balanced security.
3. Where is the refresh token stored?
HttpOnly + Secure cookies.
4. How to detect expired token?
jwt.verify automatically returns an error.
✔ Result
This integrated guide can have stronger Google rankings because:
Level transitions have been strengthened
Token versioning detailed
JWT structure explained in tabular form
You can directly test and distribute Node.js projects on GenixNode. 🚀🔥

