Prerequisites
- Basic understanding of JavaScript ๐
- TypeScript installation โก
- VS Code or preferred IDE ๐ป
- Basic Node.js knowledge ๐ข
- HTTP/REST API concepts ๐
What you'll learn
- Understand JWT and session authentication fundamentals ๐ฏ
- Apply JWT authentication in real projects ๐๏ธ
- Debug common authentication issues ๐
- Write type-safe authentication code โจ
๐ฏ Introduction
Welcome to the exciting world of TypeScript authentication! ๐ In this guide, weโll explore JWT (JSON Web Tokens) and session-based authentication - the two most popular ways to keep your applications secure.
Youโll discover how authentication can transform your TypeScript backend development experience. Whether youโre building APIs ๐, web applications ๐ฅ๏ธ, or microservices ๐, understanding authentication is essential for writing secure, maintainable code.
By the end of this tutorial, youโll feel confident implementing both JWT and session authentication in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding Authentication
๐ค What is Authentication?
Authentication is like being a bouncer at a nightclub ๐จ. Think of it as checking IDs at the door - you need to verify someone is who they claim to be before letting them into your exclusive app!
In TypeScript terms, authentication is the process of verifying user credentials and managing their access to protected resources โจ. This means you can:
- โจ Secure your API endpoints
- ๐ Control user access levels
- ๐ก๏ธ Protect sensitive data
๐ก JWT vs Sessions: The Great Debate
Hereโs why developers have passionate discussions about these approaches:
JWT (JSON Web Tokens) ๐ซ:
- Stateless ๐: All info stored in the token
- Scalable ๐ป: No server-side storage needed
- Cross-Domain ๐: Works across different services
- Portable ๐ง: Easy to pass between services
Sessions ๐๏ธ:
- Server-Controlled ๐ฏ: Full control over sessions
- Secure by Default ๐ก๏ธ: Harder to tamper with
- Easy Revocation โก: Can instantly invalidate sessions
- Simpler Logic ๐: Straightforward to implement
Real-world example: Imagine building a banking app ๐ฆ. With JWT, itโs like giving customers a tamper-proof ID card. With sessions, itโs like keeping a guest list at the front desk.
๐ง Basic Syntax and Usage
๐ Setting Up Our Types
Letโs start with friendly type definitions:
// ๐ Hello, Authentication Types!
interface User {
id: string; // ๐ Unique identifier
email: string; // ๐ง User's email
password: string; // ๐ Hashed password
role: "admin" | "user"; // ๐ค User role
createdAt: Date; // ๐
Registration date
emoji: string; // ๐ญ User's favorite emoji!
}
// ๐ซ JWT Payload structure
interface JWTPayload {
userId: string; // ๐ค User identifier
email: string; // ๐ง User email
role: string; // ๐ฏ User role
iat: number; // ๐ Issued at
exp: number; // โฐ Expires at
}
// ๐๏ธ Session data structure
interface SessionData {
userId: string; // ๐ค User identifier
email: string; // ๐ง User email
role: string; // ๐ฏ User role
loginTime: Date; // ๐ When they logged in
lastActivity: Date; // โก Last activity time
}
๐ก Explanation: Notice how we define clear types for our authentication data! The emoji field adds personality to our users. ๐
๐ฏ Common Authentication Patterns
Here are patterns youโll use daily:
// ๐๏ธ Pattern 1: Password hashing
import bcrypt from 'bcrypt';
const hashPassword = async (password: string): Promise<string> => {
const saltRounds = 10; // ๐ง Salt rounds for security
return await bcrypt.hash(password, saltRounds);
};
// ๐ Pattern 2: Password verification
const verifyPassword = async (
password: string,
hashedPassword: string
): Promise<boolean> => {
return await bcrypt.compare(password, hashedPassword);
};
// ๐จ Pattern 3: Login response type
interface LoginResponse {
success: boolean; // โ
Login success status
token?: string; // ๐ซ JWT token (if successful)
user?: User; // ๐ค User data (without password!)
message: string; // ๐ Response message
emoji: string; // ๐ Success/error emoji
}
๐ก Practical Examples
๐ Example 1: JWT Authentication System
Letโs build a complete JWT authentication system:
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import express, { Request, Response, NextFunction } from 'express';
// ๐ฏ JWT Authentication Service
class JWTAuthService {
private readonly JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-key-๐';
private readonly JWT_EXPIRES_IN = '24h';
// ๐ซ Generate JWT token
generateToken(user: User): string {
const payload: JWTPayload = {
userId: user.id,
email: user.email,
role: user.role,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60) // 24 hours
};
return jwt.sign(payload, this.JWT_SECRET, {
expiresIn: this.JWT_EXPIRES_IN
});
}
// ๐ Verify JWT token
verifyToken(token: string): JWTPayload | null {
try {
return jwt.verify(token, this.JWT_SECRET) as JWTPayload;
} catch (error) {
console.log('๐ซ Invalid token:', error);
return null;
}
}
// ๐ก๏ธ Middleware for protecting routes
authenticate = (req: Request, res: Response, next: NextFunction): void => {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1]; // Bearer TOKEN
if (!token) {
res.status(401).json({
message: 'Access denied! No token provided ๐ซ',
emoji: '๐'
});
return;
}
const payload = this.verifyToken(token);
if (!payload) {
res.status(403).json({
message: 'Invalid token! ๐ซ',
emoji: 'โ'
});
return;
}
// โจ Add user info to request
(req as any).user = payload;
next();
};
}
// ๐๏ธ User service for authentication
class UserService {
private users: Map<string, User> = new Map();
private authService = new JWTAuthService();
// ๐ Register new user
async register(email: string, password: string, emoji: string = '๐'): Promise<LoginResponse> {
// ๐ Check if user already exists
const existingUser = Array.from(this.users.values())
.find(user => user.email === email);
if (existingUser) {
return {
success: false,
message: 'User already exists! ๐โโ๏ธ',
emoji: 'โ ๏ธ'
};
}
// ๐ Hash password and create user
const hashedPassword = await bcrypt.hash(password, 10);
const newUser: User = {
id: Date.now().toString(),
email,
password: hashedPassword,
role: 'user',
createdAt: new Date(),
emoji
};
this.users.set(newUser.id, newUser);
const token = this.authService.generateToken(newUser);
return {
success: true,
token,
user: { ...newUser, password: '' }, // ๐ซ Never send password!
message: 'Registration successful! Welcome aboard! ๐',
emoji: '๐'
};
}
// ๐ Login user
async login(email: string, password: string): Promise<LoginResponse> {
// ๐ Find user by email
const user = Array.from(this.users.values())
.find(user => user.email === email);
if (!user) {
return {
success: false,
message: 'User not found! ๐',
emoji: '๐'
};
}
// ๐ Verify password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return {
success: false,
message: 'Invalid password! ๐ซ',
emoji: '๐'
};
}
// โ
Generate token and return success
const token = this.authService.generateToken(user);
return {
success: true,
token,
user: { ...user, password: '' }, // ๐ซ Clean password
message: `Welcome back, ${user.emoji}! ๐`,
emoji: '๐'
};
}
}
๐ฏ Try it yourself: Add a logout functionality that blacklists tokens!
๐๏ธ Example 2: Session-Based Authentication
Letโs implement session authentication:
import session from 'express-session';
import connectRedis from 'connect-redis';
import redis from 'redis';
// ๐๏ธ Session store configuration
const redisClient = redis.createClient({
host: 'localhost',
port: 6379
});
const RedisStore = connectRedis(session);
// ๐ฎ Session authentication service
class SessionAuthService {
private users: Map<string, User> = new Map();
// โ๏ธ Session middleware configuration
getSessionMiddleware() {
return session({
store: new RedisStore({
client: redisClient,
prefix: 'myapp:sess:' // ๐ท๏ธ Session prefix
}),
secret: process.env.SESSION_SECRET || 'your-session-secret-๐๏ธ',
resave: false,
saveUninitialized: false,
rolling: true, // ๐ Reset expiry on activity
cookie: {
secure: process.env.NODE_ENV === 'production', // ๐ HTTPS only in prod
httpOnly: true, // ๐ก๏ธ Prevent XSS
maxAge: 24 * 60 * 60 * 1000, // ๐
24 hours
sameSite: 'strict' // ๐ก๏ธ CSRF protection
}
});
}
// ๐ Login with session
async loginWithSession(
req: express.Request,
email: string,
password: string
): Promise<LoginResponse> {
// ๐ Find and verify user (similar to JWT example)
const user = Array.from(this.users.values())
.find(user => user.email === email);
if (!user || !await bcrypt.compare(password, user.password)) {
return {
success: false,
message: 'Invalid credentials! ๐ซ',
emoji: 'โ'
};
}
// โจ Create session data
const sessionData: SessionData = {
userId: user.id,
email: user.email,
role: user.role,
loginTime: new Date(),
lastActivity: new Date()
};
// ๐๏ธ Store in session
(req.session as any).user = sessionData;
return {
success: true,
user: { ...user, password: '' },
message: `Welcome back! ${user.emoji} ๐`,
emoji: '๐'
};
}
// ๐ก๏ธ Session authentication middleware
requireAuth = (req: express.Request, res: express.Response, next: express.NextFunction): void => {
const sessionUser = (req.session as any).user as SessionData;
if (!sessionUser) {
res.status(401).json({
message: 'Please log in first! ๐',
emoji: '๐ช'
});
return;
}
// ๐ Update last activity
sessionUser.lastActivity = new Date();
(req.session as any).user = sessionUser;
// โจ Add user to request
(req as any).user = sessionUser;
next();
};
// ๐ Logout (destroy session)
logout(req: express.Request): Promise<boolean> {
return new Promise((resolve) => {
req.session.destroy((err) => {
if (err) {
console.log('๐ซ Session destroy error:', err);
resolve(false);
} else {
console.log('๐ Session destroyed successfully!');
resolve(true);
}
});
});
}
}
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Refresh Token System
When youโre ready to level up, implement refresh tokens:
// ๐ฏ Advanced token system with refresh
interface TokenPair {
accessToken: string; // ๐ซ Short-lived (15 mins)
refreshToken: string; // ๐ Long-lived (7 days)
expiresIn: number; // โฐ Access token expiry
tokenType: "Bearer"; // ๐ท๏ธ Token type
}
class AdvancedJWTService {
private readonly ACCESS_TOKEN_EXPIRY = '15m';
private readonly REFRESH_TOKEN_EXPIRY = '7d';
private refreshTokens: Set<string> = new Set(); // ๐๏ธ Store valid refresh tokens
// ๐ซ Generate token pair
generateTokenPair(user: User): TokenPair {
const accessToken = jwt.sign(
{ userId: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: this.ACCESS_TOKEN_EXPIRY }
);
const refreshToken = jwt.sign(
{ userId: user.id, type: 'refresh' },
process.env.REFRESH_SECRET!,
{ expiresIn: this.REFRESH_TOKEN_EXPIRY }
);
// ๐๏ธ Store refresh token
this.refreshTokens.add(refreshToken);
return {
accessToken,
refreshToken,
expiresIn: 15 * 60, // 15 minutes in seconds
tokenType: "Bearer"
};
}
// ๐ Refresh access token
refreshAccessToken(refreshToken: string): TokenPair | null {
try {
// ๐ Verify refresh token
const payload = jwt.verify(refreshToken, process.env.REFRESH_SECRET!) as any;
if (!this.refreshTokens.has(refreshToken) || payload.type !== 'refresh') {
return null;
}
// ๐๏ธ Remove old refresh token
this.refreshTokens.delete(refreshToken);
// ๐ซ Generate new token pair
// (You'd fetch user from database using payload.userId)
const user = this.getUserById(payload.userId);
return user ? this.generateTokenPair(user) : null;
} catch (error) {
console.log('๐ซ Refresh token error:', error);
return null;
}
}
// ๐๏ธ Revoke refresh token
revokeRefreshToken(refreshToken: string): boolean {
return this.refreshTokens.delete(refreshToken);
}
}
๐๏ธ Advanced Topic 2: Role-Based Access Control (RBAC)
For enterprise-level security:
// ๐ Role-based access control system
type Permission = 'read' | 'write' | 'delete' | 'admin';
type Resource = 'users' | 'posts' | 'comments' | 'settings';
interface Role {
name: string;
permissions: Map<Resource, Permission[]>;
emoji: string; // ๐ญ Role emoji!
}
class RBACService {
private roles: Map<string, Role> = new Map([
['admin', {
name: 'admin',
permissions: new Map([
['users', ['read', 'write', 'delete', 'admin']],
['posts', ['read', 'write', 'delete', 'admin']],
['comments', ['read', 'write', 'delete', 'admin']],
['settings', ['read', 'write', 'delete', 'admin']]
]),
emoji: '๐'
}],
['user', {
name: 'user',
permissions: new Map([
['posts', ['read', 'write']],
['comments', ['read', 'write']]
]),
emoji: '๐ค'
}]
]);
// ๐ Check if user has permission
hasPermission(userRole: string, resource: Resource, permission: Permission): boolean {
const role = this.roles.get(userRole);
if (!role) return false;
const resourcePermissions = role.permissions.get(resource);
return resourcePermissions?.includes(permission) || false;
}
// ๐ก๏ธ Permission middleware factory
requirePermission = (resource: Resource, permission: Permission) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const user = (req as any).user;
if (!user || !this.hasPermission(user.role, resource, permission)) {
res.status(403).json({
message: `Access denied! Need ${permission} permission for ${resource} ๐ซ`,
emoji: '๐ก๏ธ'
});
return;
}
next();
};
};
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Storing Passwords in Plain Text
// โ Wrong way - NEVER do this!
const badUser = {
email: "[email protected]",
password: "mysecretpassword123" // ๐ฅ Huge security risk!
};
// โ
Correct way - always hash passwords!
import bcrypt from 'bcrypt';
const goodUser = {
email: "[email protected]",
password: await bcrypt.hash("mysecretpassword123", 10) // ๐ก๏ธ Safe and secure!
};
๐คฏ Pitfall 2: Sending Passwords in Responses
// โ Dangerous - exposing password!
const loginResponse = {
success: true,
user: {
id: "123",
email: "[email protected]",
password: "hashedpassword" // ๐ฅ Never send this!
}
};
// โ
Safe - clean user data!
const safeLoginResponse = {
success: true,
user: {
id: "123",
email: "[email protected]"
// ๐ซ Password intentionally omitted!
},
message: "Login successful! ๐"
};
๐ Pitfall 3: Weak JWT Secrets
// โ Terrible - easily guessable!
const weakSecret = "secret"; // ๐ฅ Hackers will break this instantly!
// โ
Strong - cryptographically secure!
const strongSecret = process.env.JWT_SECRET || crypto.randomBytes(64).toString('hex');
// ๐ก๏ธ Use environment variables for production!
๐ ๏ธ Best Practices
- ๐ฏ Use Strong Secrets: Generate cryptographically strong secrets for JWT and sessions
- ๐ Hash Passwords: Always use bcrypt or similar for password hashing
- ๐ก๏ธ Validate Input: Sanitize and validate all user inputs
- ๐จ Clean Responses: Never send passwords or sensitive data in API responses
- โจ Use HTTPS: Always use HTTPS in production for secure token transmission
- โฐ Short-lived Tokens: Keep access tokens short-lived (15-30 minutes)
- ๐ Implement Refresh: Use refresh tokens for seamless user experience
- ๐ซ Logout Properly: Always provide secure logout functionality
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Complete Authentication System
Create a type-safe authentication system with both JWT and session support:
๐ Requirements:
- โ User registration and login endpoints
- ๐ท๏ธ Both JWT and session authentication
- ๐ค Role-based access control
- ๐ Password reset functionality
- ๐จ Each user needs a favorite emoji!
- ๐ Refresh token implementation
๐ Bonus Points:
- Add password strength validation
- Implement account lockout after failed attempts
- Create audit logging for security events
- Add two-factor authentication (2FA)
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete authentication system!
import express from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import rateLimit from 'express-rate-limit';
interface CompleteUser {
id: string;
email: string;
password: string;
role: 'admin' | 'user';
emoji: string;
isLocked: boolean;
failedLoginAttempts: number;
lastLoginAttempt?: Date;
createdAt: Date;
lastLogin?: Date;
}
class CompleteAuthSystem {
private users: Map<string, CompleteUser> = new Map();
private refreshTokens: Set<string> = new Set();
private readonly MAX_FAILED_ATTEMPTS = 5;
private readonly LOCKOUT_DURATION = 15 * 60 * 1000; // 15 minutes
// ๐ก๏ธ Rate limiting middleware
private loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Max 5 attempts per window
message: {
error: 'Too many login attempts! Try again later ๐ซ',
emoji: 'โฐ'
}
});
// ๐ Register with validation
async register(
email: string,
password: string,
emoji: string = '๐'
): Promise<{ success: boolean; message: string; user?: any; token?: string }> {
// ๐ Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return {
success: false,
message: 'Invalid email format! ๐ง'
};
}
// ๐ Password strength validation
if (password.length < 8) {
return {
success: false,
message: 'Password must be at least 8 characters long! ๐'
};
}
// ๐ Check if user exists
const existingUser = Array.from(this.users.values())
.find(user => user.email === email);
if (existingUser) {
return {
success: false,
message: 'Email already registered! ๐โโ๏ธ'
};
}
// ๐๏ธ Create user
const hashedPassword = await bcrypt.hash(password, 12);
const newUser: CompleteUser = {
id: Date.now().toString(),
email,
password: hashedPassword,
role: 'user',
emoji,
isLocked: false,
failedLoginAttempts: 0,
createdAt: new Date()
};
this.users.set(newUser.id, newUser);
// ๐ซ Generate tokens
const tokens = this.generateTokenPair(newUser);
return {
success: true,
message: `Welcome aboard! ${emoji} ๐`,
user: this.sanitizeUser(newUser),
token: tokens.accessToken
};
}
// ๐ Enhanced login with security features
async login(
email: string,
password: string
): Promise<{ success: boolean; message: string; user?: any; tokens?: any }> {
const user = Array.from(this.users.values())
.find(u => u.email === email);
if (!user) {
return {
success: false,
message: 'Invalid credentials! ๐ซ'
};
}
// ๐ Check if account is locked
if (this.isAccountLocked(user)) {
return {
success: false,
message: 'Account temporarily locked! Try again later ๐'
};
}
// ๐ Verify password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
// ๐ Increment failed attempts
user.failedLoginAttempts++;
user.lastLoginAttempt = new Date();
if (user.failedLoginAttempts >= this.MAX_FAILED_ATTEMPTS) {
user.isLocked = true;
}
return {
success: false,
message: `Invalid password! ${this.MAX_FAILED_ATTEMPTS - user.failedLoginAttempts} attempts remaining ๐ซ`
};
}
// โ
Successful login - reset counters
user.failedLoginAttempts = 0;
user.isLocked = false;
user.lastLogin = new Date();
const tokens = this.generateTokenPair(user);
return {
success: true,
message: `Welcome back! ${user.emoji} ๐`,
user: this.sanitizeUser(user),
tokens
};
}
// ๐ Token pair generation
private generateTokenPair(user: CompleteUser) {
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role,
emoji: user.emoji
},
process.env.JWT_SECRET || 'fallback-secret',
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id, type: 'refresh' },
process.env.REFRESH_SECRET || 'refresh-secret',
{ expiresIn: '7d' }
);
this.refreshTokens.add(refreshToken);
return {
accessToken,
refreshToken,
expiresIn: 15 * 60, // 15 minutes
tokenType: 'Bearer'
};
}
// ๐ Check if account is locked
private isAccountLocked(user: CompleteUser): boolean {
if (!user.isLocked || !user.lastLoginAttempt) return false;
const timeSinceLock = Date.now() - user.lastLoginAttempt.getTime();
if (timeSinceLock > this.LOCKOUT_DURATION) {
// ๐ Auto-unlock after lockout duration
user.isLocked = false;
user.failedLoginAttempts = 0;
return false;
}
return true;
}
// ๐งน Remove sensitive data
private sanitizeUser(user: CompleteUser) {
const { password, failedLoginAttempts, isLocked, ...safeUser } = user;
return safeUser;
}
// ๐ก๏ธ Authentication middleware
authenticate = (req: express.Request, res: express.Response, next: express.NextFunction) => {
const authHeader = req.headers.authorization;
const token = authHeader?.split(' ')[1];
if (!token) {
res.status(401).json({
message: 'Access token required! ๐ซ',
emoji: '๐ช'
});
return;
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret') as any;
(req as any).user = payload;
next();
} catch (error) {
res.status(403).json({
message: 'Invalid or expired token! ๐ซ',
emoji: 'โฐ'
});
}
};
}
// ๐ฎ Usage example
const authSystem = new CompleteAuthSystem();
const app = express();
app.use(express.json());
// ๐ Registration endpoint
app.post('/register', async (req, res) => {
const { email, password, emoji } = req.body;
const result = await authSystem.register(email, password, emoji);
res.status(result.success ? 201 : 400).json(result);
});
// ๐ Login endpoint
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const result = await authSystem.login(email, password);
res.status(result.success ? 200 : 401).json(result);
});
// ๐ก๏ธ Protected route example
app.get('/profile', authSystem.authenticate, (req, res) => {
const user = (req as any).user;
res.json({
message: `Hello ${user.emoji}! Here's your profile ๐`,
user: user
});
});
console.log('๐ Authentication server ready on port 3000!');
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Implement JWT authentication with confidence ๐ช
- โ Create session-based auth that scales ๐
- โ Avoid security pitfalls that trip up beginners ๐ก๏ธ
- โ Apply RBAC patterns for enterprise apps ๐ฏ
- โ Debug authentication issues like a pro ๐
- โ Build secure, type-safe backends with TypeScript! โจ
Remember: Security is not a feature, itโs a foundation! Build it right from the start. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered authentication fundamentals!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Build a complete app with authentication
- ๐ Move on to our next tutorial: Authorization & Middleware
- ๐ Share your secure apps with the world!
- ๐ Explore OAuth2 and social login integrations
Remember: Every security expert was once a beginner. Keep coding, keep learning, and most importantly, keep your users safe! ๐
Happy coding! ๐๐โจ