Đăng nhập bằng số điện thoại
Status
shipped
Priority
P0
Owner
team-identity
Platforms
fe · be · mobile
AC progress
8 / 8
Last reviewed
2026-05-22
Mục tiêu
Cho phép user đã có tài khoản đăng nhập bằng số điện thoại + mật khẩu để vào hệ thống. Đây là phương thức đăng nhập chính cho user đăng ký qua flow OTP (auth-register-otp).
Phạm vi
Trong phạm vi (In scope):
- Đăng nhập bằng số điện thoại VN + mật khẩu đã đặt
- Trả về access token (JWT, TTL 15 phút) + refresh token (TTL 7 ngày)
- Cập nhật thời điểm đăng nhập gần nhất (
last_login_at) của user - Từ chối user có tài khoản bị disabled / locked / banned với thông báo rõ ràng
- Phân biệt lỗi credentials sai vs lỗi account inactive
Ngoài phạm vi (Out of scope):
- Đăng nhập bằng OTP (chưa expose endpoint công khai dù backend có method
loginByOtp) - Đăng nhập bằng email + password
- Đăng nhập bằng Google OAuth (xem feature
auth-login-google) - “Remember me” mở rộng refresh token TTL
- Multi-factor authentication
User Stories
- Là user đã đăng ký, tôi muốn nhập số điện thoại + mật khẩu để vào app nhanh chóng.
- Là user nhập sai mật khẩu, tôi muốn nhận thông báo lỗi rõ để biết là sai credentials chứ không phải lỗi hệ thống.
- Là user bị khoá tài khoản, tôi muốn nhận thông báo “tài khoản không hoạt động” thay vì lỗi generic, để liên hệ admin.
Luồng chức năng
sequenceDiagram
actor User
participant App as FE/Mobile
participant BE
User->>App: Nhập số điện thoại + mật khẩu
App->>BE: POST /auth/login {phone, password}
BE->>BE: Validate format VN + password length
BE->>BE: Tìm user theo phone
alt Phone không tồn tại hoặc password sai
BE-->>App: 401 InvalidCredentialsException
App->>User: "Số điện thoại hoặc mật khẩu sai"
else Account disabled / banned / locked
BE-->>App: 403 AccountNotActiveException
App->>User: "Tài khoản không hoạt động"
else Thành công
BE->>BE: Update last_login_at
BE->>BE: Sinh access token + refresh token
BE-->>App: 200 {accessToken, refreshToken, userId, tokenType, expiresIn}
App->>User: Vào màn hình chính
endAcceptance Criteria
- AC-1: Endpoint
POST /auth/loginnhận body{phone, password}. - AC-2: Phone phải đúng định dạng VN
^0\d{9,10}$; password tối thiểu 6 ký tự, tối đa 64 ký tự. - AC-3: Credentials đúng → trả 200 + JSON gồm:
accessToken,refreshToken,userId,tokenType(Bearer),expiresIn(giây). - AC-4: Credentials sai (phone không tồn tại hoặc password không khớp) → trả 401 với code
4011(InvalidCredentials), thông điệp generic không tiết lộ là số nào sai hay password sai. - AC-5: User có status không phải
ACTIVE→ từ chối đăng nhập với mã lỗi phân loại (disabled / locked / banned). - AC-6: Sau đăng nhập thành công, trường
last_login_atcủa user được cập nhật về thời điểm đăng nhập (UTC). - AC-7: Access token là JWT có TTL 15 phút; refresh token có TTL 7 ngày, lưu trong DB và có thể bị revoke.
- AC-8: Password được hash bằng BCrypt; client phải gửi qua HTTPS — không log password ra cho dù DEBUG.
Quy tắc nghiệp vụ
- Mật khẩu so khớp qua BCrypt — không bao giờ so plaintext.
- Lỗi credentials sai phải dùng thông điệp generic (không tiết lộ “số chưa đăng ký” để tránh user enumeration).
- Lỗi account inactive dùng thông điệp riêng nhưng vẫn không tiết lộ trạng thái chi tiết (disabled vs locked vs banned).
- Access token và refresh token là 2 entity tách biệt — refresh có thể revoke độc lập (xem feature
auth-logout). - Mỗi lần đăng nhập tạo refresh token mới (không reuse token cũ).
- Chỉ tài khoản đăng ký bằng
LOCALmới có password để login flow này dùng. Tài khoản chỉ có Google OAuth không có password → flow này từ chối với credentials sai.
Dữ liệu & Trạng thái
Entity nghiệp vụ:
User: phone (định danh để lookup), password hash, status, last_login_at.AccessToken(JWT, không lưu DB): chứa userId, roles, issuer, expiry. Stateless.RefreshToken: token gốc (hash), userId, expiry, revoked flag, rotation chain.
Trạng thái sau đăng nhập:
authenticated— có cặp token hợp lệ, có thể gọi API protected.expired-access— access token hết hạn, dùng refresh để rotate (xem feature liên quan refresh — chưa map).revoked— refresh đã bị logout, phải đăng nhập lại.
Phân loại lỗi (HTTP code → mã domain):
401code4011— credentials sai403— tài khoản không hoạt động (chi tiết qua message i18n key)400— body sai format (phone không phải VN, password rỗng)
Câu hỏi mở
- ❓ Có nên giới hạn số lần đăng nhập sai trước khi auto-lock tài khoản? (Hiện tại chưa có rate limit theo phone cho login)
- ❓ Có cần “session expiry warning” thông báo trước khi access token hết hạn?
- ❓ Có cần multi-device session management (xem danh sách thiết bị đang đăng nhập)?
Liên quan
- Phụ thuộc: auth-register-otp
- Ảnh hưởng: auth-logout