345 lines
8.7 KiB
Markdown
345 lines
8.7 KiB
Markdown
# 🔐 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 `username` hoặc `email` và `password`
|
||
- Click button "Đăng nhập"
|
||
|
||
#### **Bước 2: Gửi Request Đến Server**
|
||
```javascript
|
||
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**
|
||
```javascript
|
||
// 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**
|
||
```javascript
|
||
// 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_attempts` lê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
|
||
- Trả về lỗi 401
|
||
|
||
**✅ Nếu Password Đúng:**
|
||
- Reset `failed_login_attempts = 0`
|
||
- Tăng `login_count` lê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_id` mới (UUID)
|
||
|
||
#### **Bước 4: Tạo JWT Token**
|
||
```javascript
|
||
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**
|
||
```json
|
||
{
|
||
"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**
|
||
```javascript
|
||
// 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:**
|
||
```javascript
|
||
fetch('/api/users/profile', {
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token
|
||
}
|
||
})
|
||
```
|
||
|
||
**Server Verify Token:**
|
||
```javascript
|
||
POST /api/users/verify-token
|
||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||
```
|
||
|
||
### 4️⃣ Đăng Xuất
|
||
|
||
```javascript
|
||
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`
|
||
```sql
|
||
- 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
|
||
```bash
|
||
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:**
|
||
```bash
|
||
curl -X POST http://localhost:4000/api/users/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"username":"admin","password":"admin123"}'
|
||
```
|
||
|
||
**Verify Token:**
|
||
```bash
|
||
curl -X POST http://localhost:4000/api/users/verify-token \
|
||
-H "Authorization: Bearer YOUR_TOKEN_HERE"
|
||
```
|
||
|
||
**Logout:**
|
||
```bash
|
||
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 passwords
|
||
- `jsonwebtoken`: Tạo và verify JWT tokens
|
||
- `crypto`: Generate salt và secrets
|
||
- `sequelize`: ORM for MySQL
|
||
- `express`: Web framework
|
||
|
||
---
|
||
|
||
**Created by Sena Team** 🚀
|