Bỏ qua nội dung

Đă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

  • user mới, tôi muốn click “Sign in with Google” để vào ngay không cần điền form đăng ký.
  • 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).
  • 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ính

Acceptance Criteria

  • AC-1: Endpoint khởi đầu /oauth2/authorization/google redirect user tới Google consent screen.
  • AC-2: Callback /login/oauth2/code/google xử 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ỗi email_not_verified.
  • AC-4: Provider phải là google (registration id) — provider khác bị từ chối với unsupported_provider.
  • AC-5: Nếu user đã tồn tại với cùng auth_provider=GOOGLEprovider_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, role USER, auth_provider GOOGLE, password = null, fullName từ Google (fallback email), avatar từ Google picture (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ới accessToken, refreshToken, userId, tokenType, expiresIn.
  • AC-10: Response mode redirect: redirect 302 về URL config app.security.oauth2.success-redirect-url kèm query params userId, 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 (json vs redirect) được quyết định bởi config app.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 fields auth_provider (LOCAL | GOOGLE), provider_id (Google sub), 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 verify
  • unsupported_provider — provider id không phải google (cấu hình sai)
  • account_not_active — user có status không phải ACTIVE
  • missing_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