Support Online
Skip to main content

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:

SectionDescriptionExample
HeaderAlgorithm & token type{ "alg": "HS256", "typ": "JWT" }
PayloadUser ID + claims{ "sub": "123", "role": "user", "exp": 1767215999 }
SignatureHeader + Payload signature with private keyCryptographic 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

TokenDurationPurposeStorage
Access15 minValidating API requestsMemory / Authorization header
Refresh7 daysGetting new access tokenHttpOnly + 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. 🚀🔥