8.7 KiB
8.7 KiB
🔐 Hướng Dẫn Hệ Thống Đăng Nhập
📋 Mô Tả Quy Trình Đăng Nhập
1️⃣ Kiến Trúc Hệ Thống
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │────▶│ API Server │────▶│ Database │
│ (login.html)│◀────│ (Express) │◀────│ (MySQL) │
└─────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐
│ Redis │
│ (Cache) │
└──────────────┘
2️⃣ Quy Trình Đăng Nhập Chi Tiết
Bước 1: User Nhập Thông Tin
- Frontend (login.html) hiển thị form đăng nhập
- User nhập
usernamehoặcemailvàpassword - Click button "Đăng nhập"
Bước 2: Gửi Request Đến Server
POST /api/users/login
Content-Type: application/json
{
"username": "admin", // hoặc email
"password": "admin123"
}
Bước 3: Server Xác Thực
3.1. Tìm User trong Database
// Tìm user theo username HOẶC email
UsersAuth.findOne({
where: {
OR: [
{ username: username },
{ email: username }
]
}
})
3.2. Kiểm Tra Các Điều Kiện
- ❌ User không tồn tại → Trả về lỗi 401
- ❌ Tài khoản bị khóa (
is_locked = true) → Trả về lỗi 403 - ❌ Tài khoản không active (
is_active = false) → Trả về lỗi 403
3.3. Verify Password
// Password được hash theo công thức:
// hash = bcrypt(password + salt)
const passwordMatch = await bcrypt.compare(
password + user.salt,
user.password_hash
);
3.4. Xử Lý Kết Quả
❌ Nếu Password Sai:
- Tăng
failed_login_attemptslên 1 - Nếu thất bại ≥ 5 lần:
- Khóa tài khoản (
is_locked = true) - Set
locked_until= hiện tại + 30 phút
- Khóa tài khoản (
- Trả về lỗi 401
✅ Nếu Password Đúng:
- Reset
failed_login_attempts = 0 - Tăng
login_countlên 1 - Cập nhật
last_login= thời gian hiện tại - Cập nhật
last_login_ip= IP của client - Tạo
session_idmới (UUID)
Bước 4: Tạo JWT Token
const token = jwt.sign(
{
userId: user.id,
username: user.username,
email: user.email,
sessionId: sessionId
},
JWT_SECRET,
{ expiresIn: '24h' }
);
Bước 5: Trả Response Về Client
{
"success": true,
"message": "Đăng nhập thành công",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "uuid-here",
"username": "admin",
"email": "admin@senaai.tech",
"profile": { ... },
"last_login": "2026-01-19T...",
"login_count": 42
}
}
}
Bước 6: Client Lưu Token
// Lưu token vào localStorage
localStorage.setItem('token', data.data.token);
3️⃣ Sử Dụng Token Để Xác Thực
Các Request Tiếp Theo:
fetch('/api/users/profile', {
headers: {
'Authorization': 'Bearer ' + token
}
})
Server Verify Token:
POST /api/users/verify-token
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
4️⃣ Đăng Xuất
POST /api/users/logout
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// Server sẽ:
// 1. Xóa current_session_id trong database
// 2. Token sẽ không còn valid nữa
🔒 Bảo Mật
Password Security
- Salt: Mỗi user có salt riêng (16 bytes random)
- Hash Algorithm: bcrypt với cost factor = 10
- Formula:
hash = bcrypt(password + salt, 10)
Token Security
- Algorithm: JWT với HS256
- Expiration: 24 giờ
- Secret Key: Lưu trong environment variable
- Session Tracking: Mỗi lần login tạo session_id mới
Brute Force Protection
- Khóa tài khoản sau 5 lần đăng nhập sai
- Thời gian khóa: 30 phút
- Tự động mở khóa sau khi hết thời gian
📊 Database Schema
Table: users_auth
- id (UUID, PK)
- username (VARCHAR, UNIQUE)
- email (VARCHAR, UNIQUE)
- password_hash (VARCHAR)
- salt (VARCHAR)
- current_session_id (VARCHAR)
- last_login (DATETIME)
- last_login_ip (VARCHAR)
- login_count (INT)
- failed_login_attempts (INT)
- is_active (BOOLEAN)
- is_locked (BOOLEAN)
- locked_until (DATETIME)
🚀 API Endpoints
Authentication
| Method | Endpoint | Mô Tả |
|---|---|---|
| POST | /api/users/login |
Đăng nhập |
| POST | /api/users/register |
Đăng ký tài khoản mới |
| POST | /api/users/verify-token |
Xác thực token |
| POST | /api/users/logout |
Đăng xuất |
User Management
| Method | Endpoint | Mô Tả |
|---|---|---|
| GET | /api/users |
Lấy danh sách users |
| GET | /api/users/:id |
Lấy thông tin user theo ID |
| POST | /api/users |
Tạo user mới |
| PUT | /api/users/:id |
Cập nhật user |
| DELETE | /api/users/:id |
Xóa user (soft delete) |
🧪 Testing
1. Tạo Test Users
node scripts/create-test-user.js
2. Mở Login Page
http://localhost:4000/login.html
3. Test Login
Test Accounts:
- Admin:
admin/admin123 - Teacher:
teacher1/teacher123 - Student:
student1/student123
4. Test Với cURL
Login:
curl -X POST http://localhost:4000/api/users/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
Verify Token:
curl -X POST http://localhost:4000/api/users/verify-token \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
Logout:
curl -X POST http://localhost:4000/api/users/logout \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
🎯 Luồng Xử Lý (Flow Diagram)
User Input
│
▼
[Frontend] login.html
│ POST /api/users/login
│ { username, password }
▼
[Backend] userController.login()
│
├──▶ Tìm user trong DB
│ ├─ Không tìm thấy → 401 Unauthorized
│ └─ Tìm thấy ✓
│
├──▶ Kiểm tra is_locked
│ ├─ Bị khóa → 403 Forbidden
│ └─ Không khóa ✓
│
├──▶ Kiểm tra is_active
│ ├─ Không active → 403 Forbidden
│ └─ Active ✓
│
├──▶ Verify password với bcrypt
│ ├─ Sai → Tăng failed_attempts
│ │ ├─ ≥5 lần → Khóa tài khoản
│ │ └─ 401 Unauthorized
│ └─ Đúng ✓
│
├──▶ Cập nhật thông tin login
│ ├─ Reset failed_attempts = 0
│ ├─ Tăng login_count
│ ├─ Cập nhật last_login
│ ├─ Cập nhật last_login_ip
│ └─ Tạo session_id mới
│
├──▶ Tạo JWT token
│ └─ Expire sau 24h
│
└──▶ Return response
└─ { token, user }
│
▼
[Frontend] Nhận response
│
├──▶ Lưu token vào localStorage
├──▶ Hiển thị thông báo thành công
└──▶ Sử dụng token cho các request sau
💡 Best Practices
Frontend
- ✅ Lưu token trong localStorage
- ✅ Gửi token trong header:
Authorization: Bearer TOKEN - ✅ Xử lý token expired (redirect về login)
- ✅ Clear token khi logout
Backend
- ✅ Validate input (username, password)
- ✅ Rate limiting để chống brute force
- ✅ Log các lần đăng nhập thất bại
- ✅ Use HTTPS trong production
- ✅ Rotate JWT secret định kỳ
Database
- ✅ Index trên username, email
- ✅ Không bao giờ lưu plain password
- ✅ Use UUID cho ID
- ✅ Soft delete (is_active) thay vì DELETE
🔍 Troubleshooting
"401 Unauthorized"
- Kiểm tra username/password có đúng không
- Kiểm tra user có tồn tại trong DB không
"403 Forbidden - Account Locked"
- Tài khoản bị khóa do nhập sai password ≥5 lần
- Đợi 30 phút hoặc admin mở khóa
"Token Invalid"
- Token đã expire (>24h)
- Session đã bị logout
- Đăng nhập lại để lấy token mới
📚 Dependencies
bcrypt: Hash passwordsjsonwebtoken: Tạo và verify JWT tokenscrypto: Generate salt và secretssequelize: ORM for MySQLexpress: Web framework
Created by Sena Team 🚀