Social Login (Google & Apple)
Tài liệu này tổng hợp kiến trúc xác thực liên kết (Federated Authentication) qua Google OIDC và Sign in with Apple (iCloud) trong justo-service, ghi nhận Quyết định chốt cho từng Edge Case, và liên kết tới kế hoạch triển khai chi tiết tại memory/plan/plan_social_login_hardening.md.
1. Bảng Tổng Hợp Quyết Định Chốt Cho 8 Edge Cases
Phần tiêu đề “1. Bảng Tổng Hợp Quyết Định Chốt Cho 8 Edge Cases”| # | Edge Case | Quyết định chốt của Dự án (Product / Architecture) | Trạng thái xử lý |
|---|---|---|---|
| 1 | Apple chỉ trả fullName ở lần đầu & không nằm trong JWT |
“Không lấy name của user từ bước này. Sẽ có bước khác để lấy name của user.” | Bỏ lấy FullName ở SocialLogin & Verifier; thu thập Name ở bước Onboarding riêng. |
| 2 | Apple Hide My Email (@privaterelay.appleid.com) gây trùng tài khoản |
“Hiện tại dự án có bước verify số điện thoại nữa rồi mới tạo account.” | Không tạo User ở bước SocialLogin; chốt chặn chống trùng lặp nằm ở bước Verify Số điện thoại (Phone OTP). |
| 3 | Pre-Account Takeover & Recycled Email khi Auto-Link | “Nếu đăng ký trước thì chúng tôi có verify email gửi OTP về email rồi nên cũng không thể làm thế.” | Luồng Email thường đã bắt buộc Verify Email OTP + Phone OTP trước khi tạo User. Chỉ bổ sung kiểm tra chống xung đột provider_sub khi auto-link theo email. |
| 4 | Thời điểm tạo bản ghi User trong hệ thống |
“Đúng rồi phải verify phone thành công thì mới tạo user trong hệ thống.” | Khi SocialLogin chưa tìm thấy User, chỉ lưu trạng thái xác thực Social vào phiên để đi tiếp sang Verify Phone OTP; chỉ tạo User sau khi Verify Phone thành công. |
| 5 | Đa nền tảng Client IDs (aud / azp cho iOS, Android, Web) |
“Ok” | Hỗ trợ mảng AllowedGoogleClientIDs và AllowedAppleClientIDs trong VerifierConfig. |
| 6 | JWKS Cache Busting (Outbound DoS) & singleflight Context |
“Ok” | Bổ sung minRefreshInterval, tách context.WithoutCancel trong singleflight, và giữ cache cũ khi lỗi mạng tạm thời. |
| 7 | Race Condition khi Burn Nonce & DB Transaction | “Ok” | Chuyển sang câu lệnh SQL UPDATE nguyên tử (ConsumeSocialNonce) và dùng DB Transaction khi ghi User + UserIdentity. |
| 8 | Apple Webhook (consent-revoked / account-delete) |
“Ok” | Thu hồi phiên đăng nhập (auth_sessions) và đánh dấu revoked_at thay vì xóa cứng làm mất liên kết tài khoản. |
2. Sơ đồ Luồng Xác Thực Social OIDC & Onboarding Đã Chốt
Phần tiêu đề “2. Sơ đồ Luồng Xác Thực Social OIDC & Onboarding Đã Chốt”CLIENT (iOS / Android / Web) SERVER (justo-service) │ │ ├─ 1. POST /v1/auth/init-session (Attestation + P-256 ECDH) ─────>│ │<─── Trả về jwtToken (chứa sessionId) ───────────────────────────┤ │ │ ├─ 2. POST /v1/auth/social/nonce (sessionId) ────────────────────>│ │ ├─ Sinh 32 bytes CSPRNG │ ├─ Tính hashedNonce = SHA256_hex(raw) │ ├─ Lưu hashedNonce vào auth_sessions (TTL 10m) │<─── Trả về { nonce: hashedNonce, expiresAt } ───────────────────┤ │ │ ├─ 3. Gọi Google Sign-In SDK / Apple ASAuthorization │ │ (truyền trực tiếp hashedNonce vào request) │ │<─── Nhận id_token (JWT do Google/Apple ký RS256, chứa nonce) ───┤ │ │ ├─ 4. POST /v1/auth/social-login ───────────────────────────────>│ │ Body: { sessionId, provider, idToken, nonce } │ │ ├─ Atomic SQL ConsumeSocialNonce (chống Replay) │ ├─ Verify JWT RS256 qua JWKS Cache (Rate-limited) │ ├─ Kiểm tra iss, aud/azp (Multi-Platform), exp, email_verified │ ├─ Tra cứu user_identities(provider, sub) │ │ ├─ ĐÃ CÓ USER: Bind userId vào auth_sessions -> Login hoàn tất │ │ └─ CHƯA CÓ USER: Lưu trạng thái Social (provider, sub, email) │ │ vào phiên -> Chuyển sang luồng Onboarding │ │ (Thu thập Name -> Verify Phone OTP -> Tạo User + Identity)3. Chi Tiết Phân Tích Từng Edge Case & Quyết Định Của Dự Án
Phần tiêu đề “3. Chi Tiết Phân Tích Từng Edge Case & Quyết Định Của Dự Án”Edge Case 1: Thông tin Họ Tên (fullName) từ Google / Apple
Phần tiêu đề “Edge Case 1: Thông tin Họ Tên (fullName) từ Google / Apple”- Vấn đề:
id_tokencủa Apple không chứa claimname(chỉ trả ở Client trong lần đầu tiên), và tên trên Google không đảm bảo chuẩn pháp lý. - Quyết định của dự án:
“Không lấy name của user từ bước này. Sẽ có bước khác để lấy name của user.”
- Hướng xử lý trong code:
- Loại bỏ trường
FullNamekhỏiFederatedClaims(internal/application/auth/ports.go) và bỏ việc đọcclaims.NametrongVerifier(internal/infrastructure/security/federated/verifier.go). - Trong
Service.SocialLogin(internal/application/auth/social_login.go), bỏ logic cắt chuỗi trước dấu@của email để gánDisplayName. Tên người dùng sẽ được cập nhật ở bước Onboarding tiếp theo.
- Loại bỏ trường
Edge Case 2: Apple “Hide My Email” (@privaterelay.appleid.com) & Chống trùng lặp tài khoản
Phần tiêu đề “Edge Case 2: Apple “Hide My Email” (@privaterelay.appleid.com) & Chống trùng lặp tài khoản”- Vấn đề: Người dùng bật tính năng Hide My Email của Apple sẽ nhận email ẩn danh
@privaterelay.appleid.com. Nếu tạo ngay bản ghiusersở bước Social Login chỉ dựa trên email, một người dùng có thể tạo nhiều tài khoản trùng lặp. - Quyết định của dự án:
“Hiện tại dự án có bước verify số điện thoại nữa rồi mới tạo account.”
- Hướng xử lý trong code:
- Không tạo bản ghi
usersở bướcSocialLogin. - Việc chống trùng lặp danh tính người dùng được kiểm soát tại bước Verify Số điện thoại (Phone OTP) trước khi khởi tạo tài khoản chính thức.
- Không tạo bản ghi
Edge Case 3: Pre-Account Takeover & Recycled Corporate Email
Phần tiêu đề “Edge Case 3: Pre-Account Takeover & Recycled Corporate Email”- Vấn đề:
- Kẻ tấn công đăng ký trước bằng email của nạn nhân (Pre-Registration Hijack).
- Công ty dùng Google Workspace tái sử dụng email của nhân viên cũ cho nhân viên mới (cùng
email, khácprovider_sub).
- Quyết định của dự án:
“Nếu đăng ký trước thì chúng tôi có verify email gửi otp về email rồi nên cũng không thể làm thế.”
- Hướng xử lý trong code:
- Do luồng đăng ký thường bắt buộc Verify Email OTP và Verify Phone OTP trước khi tạo
User, kịch bản đăng ký chiếm chỗ trước đã bị triệt tiêu hoàn toàn bởi quy trình nghiệp vụ. - Khi auto-link vào
Userđã tồn tại theoemail, chỉ cần kiểm tra thêm: nếuUserđó đã có liên kết cùngprovidernhưng khácprovider_subthì từ chối auto-link (ErrIdentityExists) để chống Recycled Corporate Email.
- Do luồng đăng ký thường bắt buộc Verify Email OTP và Verify Phone OTP trước khi tạo
Edge Case 4: Thời điểm Tạo User trong Hệ thống (Sau khi Verify Phone)
Phần tiêu đề “Edge Case 4: Thời điểm Tạo User trong Hệ thống (Sau khi Verify Phone)”- Vấn đề: Trong
Service.SocialLoginhiện tại, khi chưa tìm thấyUser, code đang gọiSaveUservàSaveIdentitytạo ngay bản ghiusersvớiStatus: activedù chưa verify số điện thoại. - Quyết định của dự án:
“Đúng rồi phải verify phone thành công thì mới tạo user trong hệ thống.”
- Hướng xử lý trong code:
- Khi
SocialLoginxác thực thành công token Google/Apple nhưng chưa cóUsertrong DB:- Không tạo
UservàUserIdentityngay trongSocialLogin. - Lưu thông tin định danh Social đã xác thực (
social_provider,social_sub,social_email) vào phiên hiện tại (auth_sessions). - Trả về kết quả để Client chuyển sang màn hình thu thập thông tin và Verify Phone OTP.
- Khi hoàn tất Verify Phone OTP và kích hoạt tài khoản, tạo đồng thời
User+UserIdentitytrong cùng một Database Transaction.
- Không tạo
- Khi
Edge Case 5: Đa nền tảng (Multi-Platform Client IDs: iOS, Android, Web)
Phần tiêu đề “Edge Case 5: Đa nền tảng (Multi-Platform Client IDs: iOS, Android, Web)”- Quyết định của dự án: “Ok”
- Hướng xử lý trong code:
- Mở rộng
VerifierConfig(internal/infrastructure/security/federated/verifier.go) vàConfig(internal/configs/config.go) để hỗ trợ danh sáchGoogleClientIDs []stringvàAppleClientIDs []string(cho phép cấu hình nhiều Client ID qua biến môi trường phân tách bằng dấu phẩy cho iOS, Android, Web).
- Mở rộng
Edge Case 6: Chống JWKS Cache Busting (Outbound DoS) & singleflight Context Cancellation
Phần tiêu đề “Edge Case 6: Chống JWKS Cache Busting (Outbound DoS) & singleflight Context Cancellation”- Quyết định của dự án: “Ok”
- Hướng xử lý trong code (
internal/infrastructure/security/federated/jwks_cache.go):- Thêm
minRefreshInterval(mặc định 30 giây): Khi gặpkidkhông có trong cache, nếu mới fetch JWKS trong vòngminRefreshIntervalthì trả vềErrJWKNotFoundngay mà không gọi HTTP ra ngoài tới Google/Apple. - Tách
context.WithoutCancel(ctx)kèm timeout độc lập bên trongsfg.Do(...)để một request bị hủy không làm hỏng các request đang chờ chungsingleflight. - Giữ lại tập
keyscũ (Stale-While-Revalidate) nếu lần fetch làm tươi bị lỗi mạng tạm thời.
- Thêm
Edge Case 7: Atomic Nonce Burn & Database Transaction
Phần tiêu đề “Edge Case 7: Atomic Nonce Burn & Database Transaction”- Quyết định của dự án: “Ok”
- Hướng xử lý trong code:
- Bổ sung phương thức
ConsumeSocialNonce(ctx, sessionID, expectedNonce string, now time.Time) errortrongRepositoryvàPostgresRepositorysử dụng câu lệnhUPDATE ... WHERE id = $1 AND social_nonce = $2 AND social_nonce_expires_at > $3nguyên tử, tránh hoàn toàn Race Condition khi 2 request gửi song song. - Đảm bảo thao tác lưu
User+UserIdentitykhi hoàn tất xác thực được thực thi trong một Database Transaction (pgx.Tx).
- Bổ sung phương thức
Edge Case 8: Apple Webhook (account-delete / consent-revoked)
Phần tiêu đề “Edge Case 8: Apple Webhook (account-delete / consent-revoked)”- Quyết định của dự án: “Ok”
- Hướng xử lý trong code:
- Bổ sung cột
revoked_at TIMESTAMPTZvào bảnguser_identitiesvà phương thức thu hồi phiên theouser_id. - Khi nhận
consent-revokedhoặcaccount-deletetừ Apple (Service.HandleAppleNotification), đánh dấurevoked_attrênuser_identitiesvà xóa/thu hồi các phiênauth_sessionsđang hoạt động củauser_idđó (trong 1 Transaction), bảo toàn bản ghiusersvà lịch sử tài chính.
- Bổ sung cột
4. Liên Kết Kế Hoạch Triển Khai (Implementation Plan)
Phần tiêu đề “4. Liên Kết Kế Hoạch Triển Khai (Implementation Plan)”Chi tiết kế hoạch thực hiện theo 3 Phase tuân thủ Onion Architecture được lưu tại:
- Plan tổng thể:
memory/plan/plan_social_login_hardening.md- Phase 1 (Infrastructure Security Hardening):
memory/plan/plan_P1_social_infra_hardening.md - Phase 2 (Domain & Persistence Atomic Operations):
memory/plan/plan_P2_social_persistence_atomic.md - Phase 3 (Application Use Cases & Onboarding Flow Alignment):
memory/plan/plan_P3_social_app_onboarding_flow.md
- Phase 1 (Infrastructure Security Hardening):
- Bảng theo dõi tiến độ:
memory/plan/CHECKLIST.md