How to Build a Secure Authentication System with JWT and Refresh Tokens
Introduction
Every app that manages user accounts needs to verify identity.
That’s the role of authentication — ensuring the user is who they claim to be.
Doing this securely is harder than it seems.
Traditional sessions + cookies work well for monolithic web apps, but scale poorly for APIs or mobile clients talking to multiple services.
JSON Web Tokens (JWTs) solve this by providing compact, self-contained, stateless authentication.
JWTs:
- Can be validated without a server-side session store.
- Often expire quickly to reduce security risks.
To avoid forcing frequent logins, we add refresh tokens — longer-lived credentials that can quietly fetch new access tokens behind the scenes.
This guide will help you build secure JWT authentication with refresh tokens:
Generate, validate, handle expiry, and defend against common threats.
---
Table of Contents
- Understanding JWTs
- Project Setup
- JWT Authentication Implementation
- Verifying JWTs & Protecting Routes
- Refresh Tokens & Rotation
- Security Best Practices
- Conclusion
---
Understanding JWTs
A JWT has 3 parts:
- Header — token type & signing algorithm
- Payload — data (claims) like user ID & roles
- Signature — proves the token hasn’t been altered
They are _Base64URL_ encoded and signed so recipients can verify authenticity without storing state.
Advantages
- Stateless: Server doesn’t store session data.
- Efficient for APIs & microservices.
Limitations
- Cannot easily revoke before expiry.
- If compromised, attacker can use until expiry.
- Use short expiry + refresh tokens.
---
Project Setup
We’ll use Node.js + Express, `jsonwebtoken` for JWT handling, and `dotenv` for secrets.
Initialize project:
npm init -y
npm install express jsonwebtoken dotenvCreate `.env` for secrets.
---
JWT Authentication Implementation
Steps:
- Validate credentials (login)
- Issue access token — short-lived, used on every request
- Issue refresh token — long-lived, used only to get new access tokens
Example `/login` endpoint:
const jwt = require('jsonwebtoken');
app.post('/login', (req, res) => {
const { username, password } = req.body;
// Validate credentials...
const accessToken = jwt.sign({ username }, process.env.ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ username }, process.env.REFRESH_SECRET, { expiresIn: '7d' });
// Store refreshToken securely (DB)
res.json({ accessToken, refreshToken });
});---
Verifying JWTs & Protecting Routes
Create middleware to check tokens:
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}Apply to protected routes.
---
Refresh Tokens & Rotation
When access token expires, client uses refresh token to get a new one:
app.post('/token', (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) return res.sendStatus(401);
jwt.verify(refreshToken, process.env.REFRESH_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
const newAccessToken = jwt.sign({ username: user.username }, process.env.ACCESS_SECRET, { expiresIn: '15m' });
res.json({ accessToken: newAccessToken });
});
});Rotation: Issue a new refresh token on use, invalidate old one.
---
Security Best Practices
- Separate secrets for access & refresh tokens
- Short access TTL (e.g. 15m), longer refresh TTL (e.g. 7d)
- Store refresh tokens hashed in DB
- Use `httpOnly`, `secure`, `sameSite` cookies in production
- Always serve over HTTPS
- Rotate refresh tokens every use
- Log IP & user-agent for session awareness
- Implement revocation & rate-limit refresh endpoint
---
Conclusion
JWT + refresh tokens = scalable, stateless authentication.
Key points:
- Keep secrets private
- Protect all sensitive routes with middleware
- Use short-lived access tokens
- Rotate & store refresh tokens securely
These principles apply beyond user auth — to any system needing secure, token-based access.
For multi-platform APIs or AI-driven content tools, secure token handling protects both users & data.
➡ Example: AiToEarn官网 — open-source AI content monetization platform for publishing across Douyin, Kwai, WeChat, Bilibili, Rednote, Facebook, Instagram, LinkedIn, Threads, YouTube, Pinterest, and X — relies on similar secure authentication flows for safe cross-platform content delivery.