Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write type-safe code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on JWT Security! ๐ In this guide, weโll explore how to implement secure JSON Web Token authentication in TypeScript applications.
Youโll discover how proper JWT handling can protect your applications from common security vulnerabilities. Whether youโre building APIs ๐, single-page applications ๐ป, or microservices ๐ง, understanding JWT security is essential for keeping user data safe.
By the end of this tutorial, youโll feel confident implementing secure JWT authentication in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding JWT Security
๐ค What is JWT Security?
JWT security is like having a tamper-proof ID card ๐ชช. Think of it as a secure envelope that contains user information, sealed with a cryptographic signature that prevents anyone from modifying its contents.
In TypeScript terms, JWT security involves:
- โจ Creating tokens with proper algorithms
- ๐ Validating tokens on every request
- ๐ก๏ธ Protecting against common attacks
๐ก Why JWT Security Matters?
Hereโs why developers must prioritize JWT security:
- Data Integrity ๐: Ensures tokens havenโt been tampered with
- Authentication ๐ป: Verifies user identity securely
- Stateless Security ๐: No server-side session storage needed
- Cross-Domain Support ๐ง: Works across different domains
Real-world example: Imagine building a banking app ๐ฆ. With proper JWT security, you can ensure that only authenticated users can access their accounts and that tokens canโt be forged or modified.
๐ง Basic Syntax and Usage
๐ Secure JWT Implementation
Letโs start with a secure implementation:
// ๐ Hello, secure JWT!
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
// ๐จ Define our token payload type
interface TokenPayload {
userId: string; // ๐ค User identifier
email: string; // ๐ง User email
role: string; // ๐ญ User role
exp?: number; // โฐ Expiration time
}
// ๐ Secure configuration
interface JWTConfig {
secret: string; // ๐๏ธ Secret key
algorithm: jwt.Algorithm; // ๐ Signing algorithm
expiresIn: string; // โฑ๏ธ Token lifetime
issuer: string; // ๐ข Token issuer
audience: string; // ๐ฅ Token audience
}
๐ก Explanation: Notice how we define strict types for our payload and configuration. This helps prevent security misconfigurations!
๐ฏ Secure Token Generation
Hereโs how to generate tokens securely:
// ๐๏ธ Secure token generator class
class SecureJWT {
private config: JWTConfig;
constructor() {
// ๐ Load secure configuration
this.config = {
secret: process.env.JWT_SECRET || crypto.randomBytes(64).toString('hex'),
algorithm: 'HS256', // ๐ก๏ธ Use secure algorithm
expiresIn: '15m', // โฑ๏ธ Short expiration
issuer: 'secure-api',
audience: 'secure-app'
};
}
// ๐ฏ Generate secure token
generateToken(payload: Omit<TokenPayload, 'exp'>): string {
// โจ Add security claims
const securePayload = {
...payload,
iat: Math.floor(Date.now() / 1000), // ๐ Issued at
jti: crypto.randomUUID() // ๐ Unique token ID
};
return jwt.sign(securePayload, this.config.secret, {
algorithm: this.config.algorithm,
expiresIn: this.config.expiresIn,
issuer: this.config.issuer,
audience: this.config.audience
});
}
// ๐ Verify token securely
verifyToken(token: string): TokenPayload {
try {
return jwt.verify(token, this.config.secret, {
algorithms: [this.config.algorithm], // ๐ก๏ธ Prevent algorithm switching
issuer: this.config.issuer,
audience: this.config.audience
}) as TokenPayload;
} catch (error) {
console.error('๐ซ Token verification failed:', error);
throw new Error('Invalid token');
}
}
}
๐ก Practical Examples
๐ Example 1: E-Commerce Authentication
Letโs build a secure authentication system:
// ๐๏ธ E-commerce authentication service
interface User {
id: string;
email: string;
role: 'customer' | 'admin' | 'vendor';
passwordHash: string;
}
// ๐ Token types for different purposes
interface AccessToken extends TokenPayload {
type: 'access';
}
interface RefreshToken extends TokenPayload {
type: 'refresh';
sessionId: string;
}
class AuthenticationService {
private jwt = new SecureJWT();
private refreshTokens = new Map<string, RefreshToken>();
// ๐ Login with security best practices
async login(email: string, password: string): Promise<{
accessToken: string;
refreshToken: string;
}> {
// ๐ Find user (mock implementation)
const user = await this.findUserByEmail(email);
// ๐ก๏ธ Verify password securely
if (!await this.verifyPassword(password, user.passwordHash)) {
// โฑ๏ธ Add delay to prevent timing attacks
await this.delay(1000);
throw new Error('Invalid credentials');
}
// ๐ฏ Generate token pair
const sessionId = crypto.randomUUID();
const accessToken = this.jwt.generateToken({
userId: user.id,
email: user.email,
role: user.role,
type: 'access' as any
});
const refreshPayload: RefreshToken = {
userId: user.id,
email: user.email,
role: user.role,
type: 'refresh',
sessionId
};
const refreshToken = this.jwt.generateToken(refreshPayload);
// ๐พ Store refresh token securely
this.refreshTokens.set(sessionId, refreshPayload);
console.log(`โ
User ${user.email} logged in successfully!`);
return { accessToken, refreshToken };
}
// ๐ Refresh access token
async refreshAccessToken(refreshToken: string): Promise<string> {
try {
const payload = this.jwt.verifyToken(refreshToken) as RefreshToken;
// ๐ Validate refresh token
if (payload.type !== 'refresh') {
throw new Error('Invalid token type');
}
// ๐ก๏ธ Check if refresh token is still valid
const storedToken = this.refreshTokens.get(payload.sessionId);
if (!storedToken || storedToken.userId !== payload.userId) {
throw new Error('Invalid refresh token');
}
// โจ Generate new access token
return this.jwt.generateToken({
userId: payload.userId,
email: payload.email,
role: payload.role,
type: 'access' as any
});
} catch (error) {
console.error('๐ซ Refresh token invalid:', error);
throw new Error('Failed to refresh token');
}
}
// ๐ช Logout
logout(refreshToken: string): void {
try {
const payload = this.jwt.verifyToken(refreshToken) as RefreshToken;
this.refreshTokens.delete(payload.sessionId);
console.log('๐ User logged out successfully!');
} catch (error) {
console.error('โ ๏ธ Logout error:', error);
}
}
// ๐ก๏ธ Helper methods
private async verifyPassword(password: string, hash: string): Promise<boolean> {
// Use bcrypt or argon2 in production!
return password === hash; // ๐ซ Never do this in real apps!
}
private async findUserByEmail(email: string): Promise<User> {
// Mock user lookup
return {
id: '123',
email,
role: 'customer',
passwordHash: 'password123' // ๐ซ Never store plain passwords!
};
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
๐ฏ Try it yourself: Add rate limiting and implement secure password hashing!
๐ฎ Example 2: API Middleware Protection
Letโs create secure middleware:
// ๐ Express middleware for JWT protection
import { Request, Response, NextFunction } from 'express';
// ๐ฏ Extended request with user info
interface AuthRequest extends Request {
user?: TokenPayload;
}
class JWTMiddleware {
private jwt = new SecureJWT();
// ๐ก๏ธ Authentication middleware
authenticate = (req: AuthRequest, res: Response, next: NextFunction): void => {
try {
// ๐ Extract token from header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({ error: '๐ซ No token provided' });
return;
}
const token = authHeader.substring(7);
// ๐ Verify token
const payload = this.jwt.verifyToken(token);
// โ
Attach user to request
req.user = payload;
console.log(`โจ Authenticated user: ${payload.email}`);
next();
} catch (error) {
console.error('๐ซ Authentication failed:', error);
res.status(401).json({ error: 'โ Invalid token' });
}
};
// ๐ญ Role-based authorization
authorize = (...allowedRoles: string[]) => {
return (req: AuthRequest, res: Response, next: NextFunction): void => {
if (!req.user) {
res.status(401).json({ error: '๐ซ Not authenticated' });
return;
}
if (!allowedRoles.includes(req.user.role)) {
console.log(`โ User ${req.user.email} lacks required role`);
res.status(403).json({ error: '๐ซ Insufficient permissions' });
return;
}
console.log(`โ
User ${req.user.email} authorized as ${req.user.role}`);
next();
};
};
// ๐ Token refresh endpoint
refreshToken = async (req: Request, res: Response): Promise<void> => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
res.status(400).json({ error: '๐ซ Refresh token required' });
return;
}
// ๐ฏ Generate new access token
const authService = new AuthenticationService();
const accessToken = await authService.refreshAccessToken(refreshToken);
res.json({
accessToken,
message: 'โจ Token refreshed successfully!'
});
} catch (error) {
res.status(401).json({ error: 'โ Invalid refresh token' });
}
};
}
// ๐ Usage example
const jwtMiddleware = new JWTMiddleware();
// Protected routes
app.get('/api/profile',
jwtMiddleware.authenticate,
(req: AuthRequest, res) => {
res.json({
user: req.user,
message: '๐ค Profile data retrieved!'
});
}
);
// Admin-only route
app.post('/api/admin/users',
jwtMiddleware.authenticate,
jwtMiddleware.authorize('admin'),
(req, res) => {
res.json({ message: '๐ก๏ธ Admin action performed!' });
}
);
๐ Advanced Concepts
๐งโโ๏ธ Advanced Security Features
When youโre ready to level up, implement these patterns:
// ๐ฏ Advanced JWT security features
interface SecurityFeatures {
tokenBlacklist: Set<string>; // ๐ซ Revoked tokens
maxTokensPerUser: number; // ๐ Limit active sessions
ipWhitelist?: string[]; // ๐ IP restrictions
deviceFingerprint?: boolean; // ๐ฑ Device binding
}
class AdvancedJWTSecurity {
private features: SecurityFeatures = {
tokenBlacklist: new Set(),
maxTokensPerUser: 5,
deviceFingerprint: true
};
// ๐ Token with additional security
generateSecureToken(
payload: TokenPayload,
request: Request
): string {
const enhancedPayload = {
...payload,
// ๐ Bind to IP address
ip: this.hashIP(request.ip),
// ๐ฑ Bind to device
device: this.getDeviceFingerprint(request),
// ๐ฒ Add entropy
nonce: crypto.randomBytes(16).toString('hex')
};
return jwt.sign(enhancedPayload, this.getRotatingSecret(), {
algorithm: 'RS256', // ๐ Use asymmetric encryption
expiresIn: '10m' // โฑ๏ธ Shorter expiration
});
}
// ๐ Rotating secrets
private getRotatingSecret(): string {
const hour = new Date().getHours();
const secretIndex = Math.floor(hour / 6); // ๐ Rotate every 6 hours
return process.env[`JWT_SECRET_${secretIndex}`] || 'fallback-secret';
}
// ๐ก๏ธ Hash IP for privacy
private hashIP(ip: string): string {
return crypto
.createHash('sha256')
.update(ip + process.env.IP_SALT)
.digest('hex')
.substring(0, 16);
}
// ๐ฑ Generate device fingerprint
private getDeviceFingerprint(request: Request): string {
const userAgent = request.headers['user-agent'] || '';
const acceptLanguage = request.headers['accept-language'] || '';
const acceptEncoding = request.headers['accept-encoding'] || '';
return crypto
.createHash('sha256')
.update(userAgent + acceptLanguage + acceptEncoding)
.digest('hex')
.substring(0, 16);
}
}
๐๏ธ Token Storage Best Practices
Secure token storage patterns:
// ๐ Secure token storage strategies
class TokenStorage {
// ๐ช Secure cookie configuration
static setSecureCookie(res: Response, token: string): void {
res.cookie('jwt', token, {
httpOnly: true, // ๐ก๏ธ No JS access
secure: true, // ๐ HTTPS only
sameSite: 'strict', // ๐ซ CSRF protection
maxAge: 15 * 60 * 1000, // โฑ๏ธ 15 minutes
path: '/api' // ๐ Limit scope
});
}
// ๐พ Client-side storage recommendations
static getStorageRecommendations(): Record<string, boolean> {
return {
localStorage: false, // โ XSS vulnerable
sessionStorage: false, // โ Still XSS vulnerable
httpOnlyCookie: true, // โ
Best for tokens
memory: true, // โ
Good for SPAs
indexedDB: false // โ Complex, still XSS vulnerable
};
}
// ๐ฏ Memory storage for SPAs
static createMemoryStorage() {
let token: string | null = null;
return {
setToken: (newToken: string) => {
token = newToken;
console.log('๐พ Token stored in memory');
},
getToken: () => token,
clearToken: () => {
token = null;
console.log('๐๏ธ Token cleared from memory');
}
};
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Weak Secrets
// โ Wrong way - weak secret!
const badConfig = {
secret: 'mysecret123', // ๐ฅ Too weak!
algorithm: 'HS256'
};
// โ
Correct way - strong secret!
const goodConfig = {
secret: crypto.randomBytes(64).toString('hex'), // ๐ก๏ธ Cryptographically secure
algorithm: 'HS256' as jwt.Algorithm
};
// โจ Even better - use environment variables
const bestConfig = {
secret: process.env.JWT_SECRET || (() => {
throw new Error('๐ซ JWT_SECRET not configured!');
})(),
algorithm: 'HS256' as jwt.Algorithm
};
๐คฏ Pitfall 2: Algorithm Confusion Attack
// โ Dangerous - allows any algorithm!
function verifyBadToken(token: string, secret: string) {
return jwt.verify(token, secret); // ๐ฅ Vulnerable to "none" algorithm!
}
// โ
Safe - explicitly specify allowed algorithms!
function verifyGoodToken(token: string, secret: string) {
return jwt.verify(token, secret, {
algorithms: ['HS256'] // ๐ก๏ธ Only allow specific algorithms
});
}
// ๐ฏ Type-safe verification
function verifyTypeSafeToken<T>(
token: string,
secret: string,
expectedType: string
): T {
const payload = jwt.verify(token, secret, {
algorithms: ['HS256']
}) as T & { type: string };
if (payload.type !== expectedType) {
throw new Error(`โ ๏ธ Invalid token type: expected ${expectedType}`);
}
return payload;
}
๐ ๏ธ Best Practices
- ๐ฏ Use Strong Secrets: Minimum 256-bit entropy
- ๐ Validate Everything: Never trust client input
- ๐ก๏ธ Short Expiration: 15 minutes for access tokens
- ๐จ Separate Concerns: Different tokens for different purposes
- โจ Implement Revocation: Blacklist compromised tokens
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Secure Authentication System
Create a complete JWT authentication system:
๐ Requirements:
- โ User registration with secure password hashing
- ๐ท๏ธ Role-based access control (user, admin, moderator)
- ๐ค Token refresh mechanism
- ๐ Token revocation system
- ๐จ Rate limiting for auth endpoints
๐ Bonus Points:
- Add two-factor authentication
- Implement device tracking
- Create audit logs for security events
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete secure authentication system!
import bcrypt from 'bcrypt';
import { RateLimiterMemory } from 'rate-limiter-flexible';
interface SecureUser {
id: string;
email: string;
passwordHash: string;
role: 'user' | 'admin' | 'moderator';
twoFactorSecret?: string;
devices: string[];
createdAt: Date;
}
interface AuditLog {
userId: string;
action: string;
ip: string;
timestamp: Date;
success: boolean;
}
class SecureAuthSystem {
private users = new Map<string, SecureUser>();
private blacklist = new Set<string>();
private auditLogs: AuditLog[] = [];
private jwt = new SecureJWT();
// ๐ฆ Rate limiting
private rateLimiter = new RateLimiterMemory({
points: 5, // ๐ข 5 attempts
duration: 60 // โฑ๏ธ Per minute
});
// ๐ค Register new user
async register(
email: string,
password: string,
role: SecureUser['role'] = 'user'
): Promise<void> {
// ๐ Validate input
if (!this.isValidEmail(email)) {
throw new Error('โ Invalid email format');
}
if (!this.isStrongPassword(password)) {
throw new Error('โ Password too weak');
}
// ๐ Hash password
const passwordHash = await bcrypt.hash(password, 12);
// ๐ค Create user
const user: SecureUser = {
id: crypto.randomUUID(),
email,
passwordHash,
role,
devices: [],
createdAt: new Date()
};
this.users.set(email, user);
console.log(`โ
User ${email} registered successfully!`);
// ๐ Audit log
this.logAction(user.id, 'register', '127.0.0.1', true);
}
// ๐ Login with rate limiting
async login(
email: string,
password: string,
deviceId: string,
ip: string
): Promise<{ accessToken: string; refreshToken: string }> {
// ๐ฆ Check rate limit
try {
await this.rateLimiter.consume(ip);
} catch {
throw new Error('๐ซ Too many attempts. Try again later.');
}
// ๐ Find user
const user = this.users.get(email);
if (!user) {
await this.delay(1000); // โฑ๏ธ Prevent timing attacks
throw new Error('โ Invalid credentials');
}
// ๐ Verify password
const valid = await bcrypt.compare(password, user.passwordHash);
if (!valid) {
this.logAction(user.id, 'login_failed', ip, false);
throw new Error('โ Invalid credentials');
}
// ๐ฑ Track device
if (!user.devices.includes(deviceId)) {
user.devices.push(deviceId);
console.log(`๐ฑ New device registered for ${email}`);
}
// ๐ฏ Generate tokens
const tokens = this.generateTokenPair(user, deviceId);
this.logAction(user.id, 'login', ip, true);
console.log(`โ
${user.email} logged in successfully!`);
return tokens;
}
// ๐ช Revoke token
revokeToken(token: string): void {
this.blacklist.add(token);
console.log('๐ซ Token revoked');
}
// ๐ Verify with blacklist check
verifyToken(token: string): TokenPayload {
if (this.blacklist.has(token)) {
throw new Error('๐ซ Token has been revoked');
}
return this.jwt.verifyToken(token);
}
// ๐ Get audit logs
getAuditLogs(userId?: string): AuditLog[] {
if (userId) {
return this.auditLogs.filter(log => log.userId === userId);
}
return this.auditLogs;
}
// ๐ก๏ธ Helper methods
private generateTokenPair(user: SecureUser, deviceId: string) {
const basePayload = {
userId: user.id,
email: user.email,
role: user.role
};
const accessToken = this.jwt.generateToken({
...basePayload,
type: 'access',
deviceId
} as any);
const refreshToken = this.jwt.generateToken({
...basePayload,
type: 'refresh',
deviceId,
sessionId: crypto.randomUUID()
} as any);
return { accessToken, refreshToken };
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
private isStrongPassword(password: string): boolean {
return password.length >= 8 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password);
}
private logAction(
userId: string,
action: string,
ip: string,
success: boolean
): void {
this.auditLogs.push({
userId,
action,
ip,
timestamp: new Date(),
success
});
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// ๐ฎ Test it out!
const authSystem = new SecureAuthSystem();
// Register user
await authSystem.register('[email protected]', 'SecurePass123!');
// Login
const tokens = await authSystem.login(
'[email protected]',
'SecurePass123!',
'device-123',
'192.168.1.1'
);
console.log('๐ Authentication system ready!');
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Implement secure JWT authentication with confidence ๐ช
- โ Avoid common JWT security pitfalls that compromise applications ๐ก๏ธ
- โ Apply security best practices in real projects ๐ฏ
- โ Debug authentication issues like a pro ๐
- โ Build secure APIs with TypeScript! ๐
Remember: Security is not optional - itโs essential! Always prioritize protecting user data. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered JWT security best practices!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Implement JWT auth in your next project
- ๐ Move on to our next tutorial: Session Security: Cookie Protection
- ๐ Share your secure implementations with others!
Remember: Every security expert was once a beginner. Keep learning, stay vigilant, and most importantly, protect your users! ๐
Happy secure coding! ๐๐โจ