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 authentication patterns and secure login! ๐ In this guide, weโll explore how to build rock-solid authentication systems with TypeScript that keep your usersโ data safe and your applications secure.
Youโll discover how proper authentication patterns can transform your application from vulnerable to virtually impenetrable. Whether youโre building a social media platform ๐ฑ, e-commerce site ๐, or enterprise application ๐ข, understanding secure authentication is essential for protecting user data and maintaining trust.
By the end of this tutorial, youโll feel confident implementing secure login systems in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding Authentication Patterns
๐ค What is Authentication?
Authentication is like a bouncer at an exclusive club ๐ญ. Think of it as the process that verifies โAre you who you say you are?โ before letting users into your application.
In TypeScript terms, authentication involves verifying user credentials (like username and password) and establishing a secure session. This means you can:
- โจ Verify user identity securely
- ๐ Protect sensitive resources
- ๐ก๏ธ Maintain user sessions safely
๐ก Why Use Secure Authentication Patterns?
Hereโs why developers prioritize secure authentication:
- Data Protection ๐: Keep user information safe from attackers
- Trust Building ๐ป: Users feel secure using your application
- Compliance ๐: Meet security standards and regulations
- Attack Prevention ๐ง: Stop unauthorized access attempts
Real-world example: Imagine building a banking app ๐ฆ. With proper authentication, you ensure only the account owner can access their financial information.
๐ง Basic Syntax and Usage
๐ Simple Authentication Types
Letโs start with defining our authentication types:
// ๐ Hello, secure TypeScript!
interface User {
id: string; // ๐ Unique identifier
email: string; // ๐ง User email
username: string; // ๐ค Display name
}
// ๐ Credentials for login
interface LoginCredentials {
username: string; // ๐ค Username or email
password: string; // ๐ User password
}
// ๐ซ Authentication token
interface AuthToken {
token: string; // ๐๏ธ JWT or session token
expiresAt: Date; // โฐ Token expiration
refreshToken: string; // ๐ Token for renewal
}
// ๐ Authentication result
type AuthResult =
| { success: true; user: User; token: AuthToken }
| { success: false; error: string };
๐ก Explanation: Notice how we use TypeScriptโs union types for the AuthResult
. This ensures we handle both success and failure cases properly!
๐ฏ Common Authentication Patterns
Here are patterns youโll use in secure applications:
// ๐๏ธ Pattern 1: Password hashing
interface PasswordHasher {
hash(password: string): Promise<string>;
verify(password: string, hash: string): Promise<boolean>;
}
// ๐จ Pattern 2: Session management
interface Session {
userId: string;
createdAt: Date;
lastActivity: Date;
ipAddress: string;
userAgent: string;
}
// ๐ Pattern 3: Multi-factor authentication
interface MFAChallenge {
type: "totp" | "sms" | "email";
code?: string;
verified: boolean;
}
๐ก Practical Examples
๐ Example 1: Secure Login Service
Letโs build a complete authentication service:
// ๐ Secure authentication service
class AuthenticationService {
private readonly saltRounds = 10;
private sessions: Map<string, Session> = new Map();
// ๐ Hash password securely
private async hashPassword(password: string): Promise<string> {
// In real app, use bcrypt or argon2
const salt = this.generateSalt();
return `hashed_${salt}_${password}`; // ๐จ Simplified for demo
}
// ๐ง Generate salt for hashing
private generateSalt(): string {
return Math.random().toString(36).substring(2, 15);
}
// ๐ค Register new user
async register(
email: string,
username: string,
password: string
): Promise<AuthResult> {
// โ
Validate input
if (!this.isValidEmail(email)) {
return { success: false, error: "Invalid email format ๐ง" };
}
if (password.length < 8) {
return {
success: false,
error: "Password must be at least 8 characters ๐"
};
}
// ๐ Hash the password
const hashedPassword = await this.hashPassword(password);
// ๐ค Create user (in real app, save to database)
const user: User = {
id: this.generateUserId(),
email,
username
};
// ๐ซ Generate authentication token
const token = this.generateAuthToken(user);
console.log(`โ
User ${username} registered successfully!`);
return { success: true, user, token };
}
// ๐ช Login user
async login(credentials: LoginCredentials): Promise<AuthResult> {
// ๐ Find user (in real app, query database)
const user = this.findUserByUsername(credentials.username);
if (!user) {
return { success: false, error: "Invalid credentials ๐ซ" };
}
// ๐ Verify password (simplified)
const isValidPassword = await this.verifyPassword(
credentials.password,
user.hashedPassword
);
if (!isValidPassword) {
return { success: false, error: "Invalid credentials ๐ซ" };
}
// ๐ Create session
const session = this.createSession(user);
const token = this.generateAuthToken(user);
console.log(`๐ ${user.username} logged in successfully!`);
return { success: true, user, token };
}
// ๐๏ธ Generate secure token
private generateAuthToken(user: User): AuthToken {
return {
token: `jwt_${user.id}_${Date.now()}`, // ๐จ Use real JWT in production
expiresAt: new Date(Date.now() + 3600000), // 1 hour
refreshToken: `refresh_${user.id}_${Math.random()}`
};
}
// ๐ง Validate email format
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// ๐ Generate unique user ID
private generateUserId(): string {
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Simplified helper methods
private findUserByUsername(username: string): any {
// In real app, query database
return null;
}
private async verifyPassword(password: string, hash: string): Promise<boolean> {
// In real app, use bcrypt.compare
return hash === await this.hashPassword(password);
}
private createSession(user: User): Session {
const session: Session = {
userId: user.id,
createdAt: new Date(),
lastActivity: new Date(),
ipAddress: "127.0.0.1", // Get from request
userAgent: "Mozilla/5.0..." // Get from headers
};
this.sessions.set(session.userId, session);
return session;
}
}
// ๐ฎ Let's use it!
const auth = new AuthenticationService();
// Register a new user
const registerResult = await auth.register(
"[email protected]",
"cooluser123",
"superSecurePassword123!"
);
if (registerResult.success) {
console.log(`๐ Welcome, ${registerResult.user.username}!`);
console.log(`๐ซ Your token: ${registerResult.token.token}`);
}
๐ฏ Try it yourself: Add email verification and password reset functionality!
๐ฎ Example 2: Multi-Factor Authentication
Letโs add an extra security layer:
// ๐ก๏ธ Multi-factor authentication system
interface TOTP {
secret: string;
qrCode: string;
backupCodes: string[];
}
class MFAService {
private userSecrets: Map<string, string> = new Map();
private backupCodes: Map<string, Set<string>> = new Map();
// ๐ Enable 2FA for user
async enableTwoFactor(userId: string): Promise<TOTP> {
// ๐ฒ Generate secret
const secret = this.generateSecret();
this.userSecrets.set(userId, secret);
// ๐ข Generate backup codes
const backupCodes = this.generateBackupCodes();
this.backupCodes.set(userId, new Set(backupCodes));
// ๐ฑ Generate QR code (simplified)
const qrCode = `otpauth://totp/MyApp:${userId}?secret=${secret}`;
console.log(`โ
2FA enabled for user ${userId}!`);
return { secret, qrCode, backupCodes };
}
// โ
Verify TOTP code
verifyTOTP(userId: string, code: string): boolean {
const secret = this.userSecrets.get(userId);
if (!secret) {
console.log("โ No 2FA enabled for this user");
return false;
}
// ๐ข In real app, use speakeasy or similar library
const expectedCode = this.generateTOTPCode(secret);
const isValid = code === expectedCode;
if (isValid) {
console.log("โ
2FA code verified successfully!");
} else {
console.log("โ Invalid 2FA code");
}
return isValid;
}
// ๐ Use backup code
useBackupCode(userId: string, code: string): boolean {
const userCodes = this.backupCodes.get(userId);
if (!userCodes || !userCodes.has(code)) {
console.log("โ Invalid backup code");
return false;
}
// ๐๏ธ Remove used code
userCodes.delete(code);
console.log(`โ
Backup code used. ${userCodes.size} codes remaining`);
return true;
}
// ๐ฒ Generate secret key
private generateSecret(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let secret = '';
for (let i = 0; i < 32; i++) {
secret += chars[Math.floor(Math.random() * chars.length)];
}
return secret;
}
// ๐ข Generate backup codes
private generateBackupCodes(): string[] {
const codes: string[] = [];
for (let i = 0; i < 10; i++) {
const code = Math.random().toString(36).substring(2, 10).toUpperCase();
codes.push(code);
}
return codes;
}
// ๐ฑ Generate TOTP code (simplified)
private generateTOTPCode(secret: string): string {
// In real app, use proper TOTP algorithm
const timestamp = Math.floor(Date.now() / 30000);
return String(timestamp % 1000000).padStart(6, '0');
}
}
// ๐ฎ Test MFA
const mfa = new MFAService();
const totpSetup = await mfa.enableTwoFactor("user123");
console.log("๐ฑ Scan this QR code with your authenticator app:");
console.log(totpSetup.qrCode);
console.log("๐ Backup codes:", totpSetup.backupCodes);
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: OAuth2 Integration
When youโre ready to integrate with external providers:
// ๐ฏ OAuth2 provider configuration
interface OAuth2Config {
provider: "google" | "github" | "facebook";
clientId: string;
clientSecret: string;
redirectUri: string;
scopes: string[];
}
// ๐ช OAuth2 service
class OAuth2Service {
private configs: Map<string, OAuth2Config> = new Map();
// ๐ Generate authorization URL
getAuthorizationUrl(provider: string): string {
const config = this.configs.get(provider);
if (!config) {
throw new Error(`Provider ${provider} not configured ๐ฐ`);
}
const params = new URLSearchParams({
client_id: config.clientId,
redirect_uri: config.redirectUri,
scope: config.scopes.join(' '),
response_type: 'code'
});
return `https://${provider}.com/oauth/authorize?${params}`;
}
// ๐ซ Exchange code for token
async exchangeCodeForToken(
provider: string,
code: string
): Promise<AuthToken> {
console.log(`๐ Exchanging ${provider} code for token...`);
// In real app, make HTTP request to provider
return {
token: `oauth_${provider}_${code}`,
expiresAt: new Date(Date.now() + 3600000),
refreshToken: `refresh_oauth_${provider}_${code}`
};
}
}
๐๏ธ Advanced Topic 2: Rate Limiting & Brute Force Protection
Protect against attacks:
// ๐ Rate limiter for login attempts
class LoginRateLimiter {
private attempts: Map<string, number[]> = new Map();
private readonly maxAttempts = 5;
private readonly windowMs = 15 * 60 * 1000; // 15 minutes
// ๐ก๏ธ Check if login allowed
canAttemptLogin(identifier: string): boolean {
const now = Date.now();
const userAttempts = this.attempts.get(identifier) || [];
// ๐งน Clean old attempts
const recentAttempts = userAttempts.filter(
time => now - time < this.windowMs
);
if (recentAttempts.length >= this.maxAttempts) {
console.log(`๐ซ Too many attempts for ${identifier}`);
return false;
}
return true;
}
// ๐ Record login attempt
recordAttempt(identifier: string): void {
const attempts = this.attempts.get(identifier) || [];
attempts.push(Date.now());
this.attempts.set(identifier, attempts);
console.log(`๐ Login attempt recorded for ${identifier}`);
}
// โ
Clear attempts on success
clearAttempts(identifier: string): void {
this.attempts.delete(identifier);
console.log(`๐ Attempts cleared for ${identifier}`);
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Storing Passwords in Plain Text
// โ Wrong way - NEVER do this!
interface InsecureUser {
username: string;
password: string; // ๐ฅ Plain text password!
}
const badUser: InsecureUser = {
username: "john",
password: "password123" // ๐ฑ Visible to anyone!
};
// โ
Correct way - always hash passwords!
interface SecureUser {
username: string;
passwordHash: string; // ๐ Hashed password
}
import bcrypt from 'bcrypt';
async function createSecureUser(username: string, password: string): Promise<SecureUser> {
const passwordHash = await bcrypt.hash(password, 10);
return { username, passwordHash }; // โ
Safe storage!
}
๐คฏ Pitfall 2: Weak Session Management
// โ Dangerous - predictable session IDs!
function badSessionId(userId: string): string {
return `session_${userId}`; // ๐ฅ Attacker can guess this!
}
// โ
Safe - cryptographically secure session IDs!
import crypto from 'crypto';
function secureSessionId(): string {
return crypto.randomBytes(32).toString('hex'); // โ
Unpredictable!
}
// ๐ก๏ธ Complete session management
class SessionManager {
private sessions: Map<string, Session> = new Map();
createSession(userId: string): string {
const sessionId = secureSessionId();
const session: Session = {
userId,
createdAt: new Date(),
lastActivity: new Date(),
ipAddress: "127.0.0.1",
userAgent: "Mozilla/5.0..."
};
this.sessions.set(sessionId, session);
return sessionId;
}
validateSession(sessionId: string): boolean {
const session = this.sessions.get(sessionId);
if (!session) return false;
// โฐ Check expiration
const now = Date.now();
const lastActivity = session.lastActivity.getTime();
const thirtyMinutes = 30 * 60 * 1000;
if (now - lastActivity > thirtyMinutes) {
this.sessions.delete(sessionId);
return false;
}
// โ
Update activity
session.lastActivity = new Date();
return true;
}
}
๐ ๏ธ Best Practices
- ๐ฏ Use Established Libraries: Donโt roll your own crypto - use bcrypt, argon2
- ๐ Implement Proper Validation: Check all inputs thoroughly
- ๐ก๏ธ Use HTTPS Always: Encrypt data in transit
- ๐จ Implement Rate Limiting: Prevent brute force attacks
- โจ Keep Sessions Secure: Use secure, random session IDs
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Complete Auth System
Create a type-safe authentication system with these features:
๐ Requirements:
- โ User registration with email verification
- ๐ท๏ธ Secure password hashing with bcrypt
- ๐ค Login with rate limiting
- ๐ Remember me functionality
- ๐จ Password reset via email
๐ Bonus Points:
- Add social login (Google/GitHub)
- Implement JWT tokens
- Add two-factor authentication
๐ก Solution
๐ Click to see solution
// ๐ฏ Complete authentication system!
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
interface User {
id: string;
email: string;
username: string;
passwordHash: string;
emailVerified: boolean;
emailVerificationToken?: string;
resetPasswordToken?: string;
resetPasswordExpires?: Date;
}
interface JWTPayload {
userId: string;
email: string;
rememberMe: boolean;
}
class CompleteAuthSystem {
private users: Map<string, User> = new Map();
private rateLimiter = new LoginRateLimiter();
private readonly jwtSecret = process.env.JWT_SECRET || 'development-secret';
// ๐ Register with email verification
async register(email: string, username: string, password: string): Promise<AuthResult> {
// โ
Validate inputs
if (!this.isValidEmail(email)) {
return { success: false, error: "Invalid email format ๐ง" };
}
if (password.length < 8) {
return { success: false, error: "Password too short ๐" };
}
// ๐ Hash password
const passwordHash = await bcrypt.hash(password, 10);
// ๐ง Generate verification token
const emailVerificationToken = crypto.randomBytes(32).toString('hex');
// ๐ค Create user
const user: User = {
id: crypto.randomUUID(),
email,
username,
passwordHash,
emailVerified: false,
emailVerificationToken
};
this.users.set(user.id, user);
// ๐ฌ Send verification email (in real app)
console.log(`๐ง Verification link: /verify?token=${emailVerificationToken}`);
// ๐ซ Generate token
const token = this.generateToken(user, false);
return {
success: true,
user: this.sanitizeUser(user),
token: this.createAuthToken(token)
};
}
// ๐ช Login with rate limiting
async login(
username: string,
password: string,
rememberMe: boolean = false
): Promise<AuthResult> {
// ๐ก๏ธ Check rate limit
if (!this.rateLimiter.canAttemptLogin(username)) {
return {
success: false,
error: "Too many login attempts. Try again later ๐ซ"
};
}
this.rateLimiter.recordAttempt(username);
// ๐ Find user
const user = Array.from(this.users.values())
.find(u => u.username === username || u.email === username);
if (!user) {
return { success: false, error: "Invalid credentials ๐ซ" };
}
// ๐ Verify password
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
return { success: false, error: "Invalid credentials ๐ซ" };
}
// โ
Clear rate limit on success
this.rateLimiter.clearAttempts(username);
// ๐ง Check email verification
if (!user.emailVerified) {
return {
success: false,
error: "Please verify your email first ๐ง"
};
}
// ๐ซ Generate token
const token = this.generateToken(user, rememberMe);
console.log(`๐ ${user.username} logged in successfully!`);
return {
success: true,
user: this.sanitizeUser(user),
token: this.createAuthToken(token, rememberMe)
};
}
// ๐ง Verify email
async verifyEmail(token: string): Promise<boolean> {
const user = Array.from(this.users.values())
.find(u => u.emailVerificationToken === token);
if (!user) {
console.log("โ Invalid verification token");
return false;
}
user.emailVerified = true;
user.emailVerificationToken = undefined;
console.log(`โ
Email verified for ${user.email}`);
return true;
}
// ๐ Request password reset
async requestPasswordReset(email: string): Promise<void> {
const user = Array.from(this.users.values())
.find(u => u.email === email);
if (!user) {
// Don't reveal if email exists
console.log("๐ง If email exists, reset link sent");
return;
}
// ๐ฒ Generate reset token
user.resetPasswordToken = crypto.randomBytes(32).toString('hex');
user.resetPasswordExpires = new Date(Date.now() + 3600000); // 1 hour
console.log(`๐ Reset link: /reset-password?token=${user.resetPasswordToken}`);
}
// ๐ Reset password
async resetPassword(token: string, newPassword: string): Promise<boolean> {
const user = Array.from(this.users.values())
.find(u => u.resetPasswordToken === token);
if (!user || !user.resetPasswordExpires) {
console.log("โ Invalid or expired reset token");
return false;
}
if (user.resetPasswordExpires < new Date()) {
console.log("โ Reset token expired");
return false;
}
// ๐ Update password
user.passwordHash = await bcrypt.hash(newPassword, 10);
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
console.log(`โ
Password reset for ${user.email}`);
return true;
}
// ๐ซ Generate JWT token
private generateToken(user: User, rememberMe: boolean): string {
const payload: JWTPayload = {
userId: user.id,
email: user.email,
rememberMe
};
const expiresIn = rememberMe ? '30d' : '1d';
return jwt.sign(payload, this.jwtSecret, { expiresIn });
}
// ๐๏ธ Create auth token response
private createAuthToken(token: string, rememberMe: boolean = false): AuthToken {
const expiresMs = rememberMe ? 30 * 24 * 60 * 60 * 1000 : 24 * 60 * 60 * 1000;
return {
token,
expiresAt: new Date(Date.now() + expiresMs),
refreshToken: crypto.randomBytes(32).toString('hex')
};
}
// ๐งน Remove sensitive data
private sanitizeUser(user: User): Partial<User> {
const { passwordHash, emailVerificationToken, resetPasswordToken, ...safe } = user;
return safe;
}
// ๐ง Email validation
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
// ๐ฎ Test the complete system!
const authSystem = new CompleteAuthSystem();
// Register user
const result = await authSystem.register(
"[email protected]",
"secureuser",
"MyStr0ngP@ssw0rd!"
);
if (result.success) {
console.log(`๐ Welcome! Check your email for verification`);
}
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Implement secure authentication with confidence ๐ช
- โ Hash passwords properly using industry standards ๐ก๏ธ
- โ Manage sessions securely with proper validation ๐ฏ
- โ Add multi-factor authentication for extra security ๐
- โ Protect against common attacks like brute force! ๐
Remember: Security is not optional - itโs essential! Always prioritize your usersโ safety. ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered authentication patterns and secure login!
Hereโs what to do next:
- ๐ป Practice with the complete auth system above
- ๐๏ธ Add OAuth2 integration for social logins
- ๐ Move on to our next tutorial: Authorization and Access Control
- ๐ Implement these patterns in your real projects!
Remember: Every secure application starts with proper authentication. Keep building secure systems! ๐
Happy coding! ๐๐โจ