Gensics

Back to Home

Express Middleware และ Authentication

Express Developer19 ธันวาคม 2567Authentication
ExpressJWTAuthenticationMiddlewareSecurity

Middleware เป็นหัวใจสำคัญของ Express.js ที่ช่วยให้เราสามารถจัดการ request/response pipeline ได้อย่างมีประสิทธิภาพ

Express Middleware คืออะไร

Middleware เป็น functions ที่ทำงานระหว่าง request และ response โดยสามารถ:

  • แก้ไข request และ response objects
  • จบ request-response cycle
  • เรียก middleware ตัวถัดไป

Built-in Middleware

const express = require('express') const app = express() // Body parsing middleware app.use(express.json()) // สำหรับ JSON app.use(express.urlencoded({ extended: true })) // สำหรับ form data // Static files middleware app.use(express.static('public')) // Cookie parsing const cookieParser = require('cookie-parser') app.use(cookieParser())

Custom Middleware

// Logging middleware const logger = (req, res, next) => { const timestamp = new Date().toISOString() console.log(`${timestamp} - ${req.method} ${req.path}`) next() // เรียก middleware ตัวถัดไป } // CORS middleware const cors = (req, res, next) => { res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS') res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization') if (req.method === 'OPTIONS') { res.sendStatus(200) } else { next() } } // Rate limiting middleware const rateLimit = { windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs requests: new Map() } const rateLimiter = (req, res, next) => { const ip = req.ip const now = Date.now() const windowStart = now - rateLimit.windowMs // Clean old requests const requests = rateLimit.requests.get(ip) || [] const validRequests = requests.filter(time => time > windowStart) if (validRequests.length >= rateLimit.max) { return res.status(429).json({ success: false, message: 'Too many requests, please try again later' }) } validRequests.push(now) rateLimit.requests.set(ip, validRequests) next() } // ใช้งาน middleware app.use(logger) app.use(cors) app.use(rateLimiter)

JWT Authentication

npm install jsonwebtoken bcryptjs
// auth.js const jwt = require('jsonwebtoken') const bcrypt = require('bcryptjs') const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key' const JWT_EXPIRES_IN = '7d' // Generate JWT token const generateToken = (payload) => { return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }) } // Verify JWT token const verifyToken = (token) => { try { return jwt.verify(token, JWT_SECRET) } catch (error) { throw new Error('Invalid token') } } // Hash password const hashPassword = async (password) => { const salt = await bcrypt.genSalt(10) return bcrypt.hash(password, salt) } // Compare password const comparePassword = async (password, hashedPassword) => { return bcrypt.compare(password, hashedPassword) } module.exports = { generateToken, verifyToken, hashPassword, comparePassword }

Authentication Middleware

// middleware/auth.js const { verifyToken } = require('../auth') const authenticate = (req, res, next) => { try { const authHeader = req.headers.authorization if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ success: false, message: 'Access token required' }) } const token = authHeader.substring(7) // Remove 'Bearer ' prefix const decoded = verifyToken(token) req.user = decoded // เก็บข้อมูล user ใน request object next() } catch (error) { res.status(401).json({ success: false, message: 'Invalid or expired token' }) } } // Role-based authorization const authorize = (...roles) => { return (req, res, next) => { if (!req.user) { return res.status(401).json({ success: false, message: 'Authentication required' }) } if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, message: 'Insufficient permissions' }) } next() } } module.exports = { authenticate, authorize }

Auth Routes

// routes/auth.js const express = require('express') const { generateToken, hashPassword, comparePassword } = require('../auth') const { authenticate } = require('../middleware/auth') const router = express.Router() // Mock user database let users = [ { id: 1, email: 'admin@example.com', password: '$2a$10$hash...', // hashed password role: 'admin' } ] // Register router.post('/register', async (req, res) => { try { const { email, password, name } = req.body // Validation if (!email || !password || !name) { return res.status(400).json({ success: false, message: 'Email, password, and name are required' }) } // Check if user exists const existingUser = users.find(u => u.email === email) if (existingUser) { return res.status(400).json({ success: false, message: 'User already exists' }) } // Hash password const hashedPassword = await hashPassword(password) // Create user const newUser = { id: users.length + 1, email, password: hashedPassword, name, role: 'user' } users.push(newUser) // Generate token const token = generateToken({ id: newUser.id, email: newUser.email, role: newUser.role }) res.status(201).json({ success: true, message: 'User registered successfully', token, user: { id: newUser.id, email: newUser.email, name: newUser.name, role: newUser.role } }) } catch (error) { res.status(500).json({ success: false, message: 'Registration failed' }) } }) // Login router.post('/login', async (req, res) => { try { const { email, password } = req.body // Find user const user = users.find(u => u.email === email) if (!user) { return res.status(401).json({ success: false, message: 'Invalid credentials' }) } // Check password const isValidPassword = await comparePassword(password, user.password) if (!isValidPassword) { return res.status(401).json({ success: false, message: 'Invalid credentials' }) } // Generate token const token = generateToken({ id: user.id, email: user.email, role: user.role }) res.json({ success: true, message: 'Login successful', token, user: { id: user.id, email: user.email, name: user.name, role: user.role } }) } catch (error) { res.status(500).json({ success: false, message: 'Login failed' }) } }) // Get current user profile router.get('/profile', authenticate, (req, res) => { const user = users.find(u => u.id === req.user.id) res.json({ success: true, data: { id: user.id, email: user.email, name: user.name, role: user.role } }) }) module.exports = router

Protected Routes

// routes/protected.js const express = require('express') const { authenticate, authorize } = require('../middleware/auth') const router = express.Router() // Protected route (requires authentication) router.get('/dashboard', authenticate, (req, res) => { res.json({ success: true, message: `Welcome to dashboard, ${req.user.email}!`, user: req.user }) }) // Admin only route router.get('/admin', authenticate, authorize('admin'), (req, res) => { res.json({ success: true, message: 'Admin panel access granted', data: { totalUsers: users.length, serverInfo: { uptime: process.uptime(), memory: process.memoryUsage() } } }) }) module.exports = router

Error Handling Middleware

// middleware/errorHandler.js const errorHandler = (err, req, res, next) => { console.error(err.stack) // JWT errors if (err.name === 'JsonWebTokenError') { return res.status(401).json({ success: false, message: 'Invalid token' }) } if (err.name === 'TokenExpiredError') { return res.status(401).json({ success: false, message: 'Token expired' }) } // Default error res.status(500).json({ success: false, message: 'Internal server error' }) } module.exports = errorHandler

การใช้งาน Client-side

// Frontend example const API_BASE = 'http://localhost:3000/api' // Login function const login = async (email, password) => { try { const response = await fetch(`${API_BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }) const data = await response.json() if (data.success) { localStorage.setItem('token', data.token) return data } else { throw new Error(data.message) } } catch (error) { console.error('Login failed:', error) throw error } } // Authenticated request const getProfile = async () => { try { const token = localStorage.getItem('token') const response = await fetch(`${API_BASE}/auth/profile`, { headers: { 'Authorization': `Bearer ${token}` } }) return response.json() } catch (error) { console.error('Failed to get profile:', error) throw error } }

Best Practices

  1. ใช้ HTTPS - สำหรับ production environment
  2. Strong JWT Secret - ใช้ secret key ที่ซับซ้อน
  3. Token Expiration - ตั้งเวลาหมดอายุที่เหมาะสม
  4. Rate Limiting - ป้องกัน brute force attacks
  5. Input Validation - validate ข้อมูล input เสมอ
  6. Error Handling - จัดการ errors อย่างปลอดภัย

สรุป

Middleware และ Authentication เป็นพื้นฐานสำคัญในการสร้าง secure APIs ด้วย Express.js การเข้าใจหลักการเหล่านี้จะช่วยให้สร้าง backend applications ที่มีความปลอดภัยและมีประสิทธิภาพ