← Back to Node.js Menu | 🏠 Hub
# Install packages
npm install jsonwebtoken bcryptjs express dotenv
# .env
JWT_SECRET=your_super_secret_key_min_32_chars_long
JWT_EXPIRES=7d
BCRYPT_ROUNDS=12
# Structure
src/
middleware/
auth.js ← protect routes
routes/
auth.js ← login/register/refresh
users.js ← protected routes
utils/
jwt.js ← token helpers
// utils/password.js
const bcrypt = require('bcryptjs');
const ROUNDS = parseInt(process.env.BCRYPT_ROUNDS) || 12;
async function hashPassword(plain) {
return bcrypt.hash(plain, ROUNDS);
}
async function verifyPassword(plain, hashed) {
return bcrypt.compare(plain, hashed);
}
module.exports = { hashPassword, verifyPassword };
// Usage
const hashed = await hashPassword("secret123");
// "$2b$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxx" — 60 chars, includes salt
const ok = await verifyPassword("secret123", hashed); // true
const no = await verifyPassword("wrong", hashed); // false
// utils/jwt.js
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
const EXPIRES = process.env.JWT_EXPIRES || '7d';
function signToken(payload) {
return jwt.sign(payload, SECRET, { expiresIn: EXPIRES });
}
function verifyToken(token) {
try {
return { data: jwt.verify(token, SECRET), error: null };
} catch (err) {
const msg = err.name === 'TokenExpiredError'
? 'Token expired'
: 'Invalid token';
return { data: null, error: msg };
}
}
function refreshToken(token) {
const { data, error } = verifyToken(token);
if (error) return { token: null, error };
// Re-sign with same payload (minus old iat/exp)
const { iat, exp, ...payload } = data;
return { token: signToken(payload), error: null };
}
module.exports = { signToken, verifyToken, refreshToken };
// middleware/auth.js
const { verifyToken } = require('../utils/jwt');
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
const { data, error } = verifyToken(token);
if (error) {
return res.status(401).json({ error });
}
req.user = data; // attach user to request
next();
}
// Role-based auth
function authorize(...roles) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
module.exports = { authenticate, authorize };
// routes/auth.js
const express = require('express');
const router = express.Router();
const { hashPassword, verifyPassword } = require('../utils/password');
const { signToken } = require('../utils/jwt');
// In-memory users (replace with DB)
const users = [];
// POST /api/auth/register
router.post('/register', async (req, res) => {
try {
const { name, email, password } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ error: 'All fields required' });
}
if (users.find(u => u.email === email)) {
return res.status(409).json({ error: 'Email already registered' });
}
if (password.length < 8) {
return res.status(400).json({ error: 'Password min 8 characters' });
}
const hashed = await hashPassword(password);
const user = { id: Date.now(), name, email, password: hashed, role: 'user' };
users.push(user);
const token = signToken({ id: user.id, email, role: user.role });
const { password: _, ...safeUser } = user; // exclude password
res.status(201).json({ token, user: safeUser });
} catch (err) {
res.status(500).json({ error: 'Registration failed' });
}
});
// POST /api/auth/login
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = users.find(u => u.email === email);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const valid = await verifyPassword(password, user.password);
if (!valid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = signToken({ id: user.id, email, role: user.role });
const { password: _, ...safeUser } = user;
res.json({ token, user: safeUser });
} catch (err) {
res.status(500).json({ error: 'Login failed' });
}
});
module.exports = router;
// routes/users.js
const express = require('express');
const router = express.Router();
const { authenticate, authorize } = require('../middleware/auth');
// Public
router.get('/public', (req, res) => {
res.json({ message: 'Anyone can see this' });
});
// Protected — need valid JWT
router.get('/profile', authenticate, (req, res) => {
res.json({ user: req.user, message: 'Your profile' });
});
// Admin only
router.get('/admin', authenticate, authorize('admin'), (req, res) => {
res.json({ message: 'Admin dashboard' });
});
module.exports = router;
// server.js
const express = require('express');
const app = express();
app.use(express.json());
require('dotenv').config();
app.use('/api/auth', require('./routes/auth'));
app.use('/api/users', require('./routes/users'));
// Test:
// POST /api/auth/register { name, email, password }
// POST /api/auth/login { email, password }
// GET /api/users/profile Authorization: Bearer <token>
← Node.js 06 | Next: Node.js 08 → WebSockets →