Đăng nhập bằng Google (OAuth 2.0)
Status
shipped
Priority
P0
Owner
team-identity
Platforms
fe · be · mobile
AC progress
11 / 11
Last reviewed
2026-05-22
Mục tiêu
Cho phép user đăng nhập / đăng ký nhanh bằng tài khoản Google của họ, không cần nhập số điện thoại hay tạo mật khẩu riêng. Phù hợp với user không thoải mái đặt mật khẩu mới hoặc muốn vào nhanh qua tài khoản đã có sẵn.
Phạm vi
Trong phạm vi (In scope):
- Đăng nhập bằng tài khoản Google đã verify email
- Nếu chưa có tài khoản: tự động tạo user mới với thông tin từ Google (email, tên, avatar)
- Nếu đã có tài khoản (đăng ký LOCAL với cùng email): tự động liên kết Google profile vào tài khoản hiện có
- Cấp access token + refresh token sau khi OAuth flow thành công
- Hỗ trợ 2 chế độ trả response: JSON trực tiếp hoặc redirect về FE callback URL kèm token trong query string
Ngoài phạm vi (Out of scope):
- OAuth với provider khác (Facebook, Apple, Microsoft, …) — chỉ Google ở phase này
- Email Google chưa verify — bị từ chối trực tiếp
- Hỗ trợ chọn account khi user đã có nhiều account Google trên trình duyệt (Google tự xử)
- Unlink Google khỏi tài khoản đã liên kết (cần feature riêng nếu cần)
User Stories
- Là user mới, tôi muốn click “Sign in with Google” để vào ngay không cần điền form đăng ký.
- Là user đã đăng ký bằng số điện thoại với email Y, tôi muốn click Google của email Y và được liên kết tự động (không tạo tài khoản trùng).
- Là user có email Google chưa verify, tôi muốn nhận thông báo rõ “email chưa được Google xác minh” để biết cần verify trước.
Luồng chức năng
sequenceDiagram
actor User
participant App as FE/Mobile
participant BE
participant Google as Google OAuth
User->>App: Bấm "Sign in with Google"
App->>BE: Redirect /oauth2/authorization/google
BE->>Google: Redirect tới Google consent screen
User->>Google: Chọn account + cho phép
Google->>BE: Callback /login/oauth2/code/google + code
BE->>Google: Đổi code lấy access token + profile
Google-->>BE: {sub, email, email_verified, name, picture}
BE->>BE: Validate email_verified = true
alt email không verified
BE-->>App: 401 + lý do "email_not_verified"
else đã có user với sub Google
BE->>BE: Reuse user hiện có
else chưa có sub nhưng có email
BE->>BE: Liên kết Google vào user LOCAL
else email chưa có
BE->>BE: Tạo user mới (auth_provider=GOOGLE)
end
BE->>BE: Cấp access + refresh token
alt Mode = json
BE-->>App: 200 JSON {accessToken, refreshToken, ...}
else Mode = redirect
BE-->>App: 302 → FE callback URL?accessToken=...&refreshToken=...
end
App->>User: Vào màn hình chínhAcceptance Criteria
- AC-1: Endpoint khởi đầu
/oauth2/authorization/googleredirect user tới Google consent screen. - AC-2: Callback
/login/oauth2/code/googlexử lý response từ Google, đổi code lấy profile. - AC-3: Chỉ chấp nhận Google profile có
email_verified: true. Nếu false → từ chối với mã lỗiemail_not_verified. - AC-4: Provider phải là
google(registration id) — provider khác bị từ chối vớiunsupported_provider. - AC-5: Nếu user đã tồn tại với cùng
auth_provider=GOOGLEvàprovider_id={sub}→ reuse user, không tạo mới. - AC-6: Nếu chưa có Google link nhưng email đã có user khác (đăng ký LOCAL) → link Google profile vào user đó (set auth_provider=GOOGLE, provider_id=sub).
- AC-7: Nếu chưa có user nào với email này → tạo user mới với: status
ACTIVE, roleUSER, auth_providerGOOGLE, password = null, fullName từ Google (fallback email), avatar từ Googlepicture(fallback sinh từ tên). - AC-8: User bị disabled / locked không được phép link Google → trả lỗi
account_not_active. - AC-9: Response mode
json(mặc định): trả 200 JSON vớiaccessToken,refreshToken,userId,tokenType,expiresIn. - AC-10: Response mode
redirect: redirect 302 về URL configapp.security.oauth2.success-redirect-urlkèm query paramsuserId,accessToken,refreshToken,tokenType,expiresIn(URL-encoded). - AC-11: Access token TTL 15 phút, refresh token TTL 7 ngày (giống flow login phone).
Quy tắc nghiệp vụ
- Chỉ tin cậy email từ Google khi
email_verified=true— Google đảm bảo email đó thuộc về user. - Một user chỉ có một auth provider tại một thời điểm (LOCAL hoặc GOOGLE). Khi link Google vào user LOCAL, provider được đổi sang GOOGLE.
- Sau khi link: user có thể tiếp tục đăng nhập qua Google. Đăng nhập bằng phone+password vẫn dùng được vì password vẫn còn (chỉ trường hợp tạo user mới qua Google thì password = null).
- Mỗi lần đăng nhập qua Google đều tạo refresh token mới (không reuse).
- Mode response (
jsonvsredirect) được quyết định bởi configapp.security.oauth2.success-response-modeở phía BE — FE và Mobile có thể chọn mode khác nhau.
Dữ liệu & Trạng thái
Entity nghiệp vụ:
User: thêm fieldsauth_provider(LOCAL | GOOGLE),provider_id(Googlesub),email_verified.GoogleProfile(transient, không lưu DB): email, sub, name, picture, email_verified.
Trạng thái user theo provider:
LOCAL— đăng ký bằng phone+password, có thể không có email.GOOGLE— đăng ký hoặc đã link Google, không bắt buộc có phone (chưa quyết định behavior này — xem câu hỏi mở).
Phân loại lỗi:
email_not_verified— Google trả email chưa verifyunsupported_provider— provider id không phảigoogle(cấu hình sai)account_not_active— user có status không phải ACTIVEmissing_sub/missing_email— Google profile thiếu field bắt buộc
Câu hỏi mở
- ❓ User tạo mới qua Google không có số điện thoại — flow nào để họ thêm phone sau (mua package cần phone, OTP confirmation cho giao dịch quan trọng, …)?
- ❓ Khi user LOCAL có phone X và Google email Y được liên kết, nếu sau này có user mới đăng ký Google với email Y → behavior thế nào?
- ❓ Có nên cho phép user “unlink” Google để chuyển về LOCAL only?
- ❓ Avatar Google có thể stale (user đổi avatar ở Google sau đó). Có cần refresh định kỳ?
Liên quan
- Phụ thuộc: Chưa có
- Ảnh hưởng: auth-logout