Tổng Quan
Khi xây dựng một nền tảng nội dung hiện đại, một trong những thách thức kiến trúc thú vị nhất là kết nối Next.js frontend với headless CMS backend cùng trải nghiệm xác thực mượt mà. Trong bài viết này, chúng tôi chia sẻ cách triển khai Google OAuth authentication cho visitor — hoàn toàn tách biệt khỏi hệ thống admin CMS — sử dụng luồng JWT tùy chỉnh.
Bài Toán Hai Hệ Thống
Stack của chúng tôi có hai lớp riêng biệt:
- Frontend: Ứng dụng Next.js phục vụ nội dung công khai (blog, bài viết)
- Backend: Ứng dụng ASP.NET Core / Optimizely CMS 12 quản lý nội dung và expose REST API tùy chỉnh
CMS có hệ thống xác thực admin tích hợp sẵn (cookie-based). Chúng tôi cần thêm visitor authentication riêng biệt — dùng Google Sign-In — mà không ảnh hưởng đến phiên admin CMS. Điều này có nghĩa là chạy hai hệ thống auth song song trên cùng một cơ sở dữ liệu ASP.NET Identity.
Kiến Trúc Tổng Quát
┌───────────────────────┐ ┌───────────────────────────┐
│ Next.js Frontend │ │ CMS Backend (ASP.NET) │
│ │ │ │
│ AuthProvider │◄───────►│ /api/auth/* │
│ (React Context) │ JWT │ GoogleAuthService │
│ │ Cookie │ JwtService │
│ GoogleLoginButton │ │ │
│ UserMenu │ │ ASP.NET Identity │
│ AuthGuard │ │ (Users + Roles) │
└───────────────────────┘ └───────────────────────────┘
│ │
└──────── Google OAuth ───────────┘
Ba Authentication Scheme Cùng Tồn Tại
Backend đăng ký ba authentication scheme hoạt động hòa bình:
- Default Cookie Scheme — do CMS framework xử lý cho admin login. Chúng tôi không bao giờ đụng vào đây.
- Google OAuth Scheme — chỉ dùng trong quá trình OAuth callback. Cookie tạm ngắn hạn (
GoogleExternalCookie) chuyển kết quả Google sang callback handler. - JWT Bearer Scheme — đọc JWT từ
httpOnlycookie (auth_token) cho tất cả API endpoint dành cho visitor.
Luồng Đăng Nhập: Từng Bước
1. User Click "Sign in with Google"
Nút này gọi một hàm đơn giản chuyển hướng trình duyệt đến backend login endpoint, kèm URL trang hiện tại làm returnUrl:
GET /api/auth/google-login?returnUrl=https://yoursite.com/blog/some-post
2. Backend Challenge Google
Backend phát ra Challenge(GoogleDefaults.AuthenticationScheme), chuyển hướng user đến màn hình đồng ý của Google. returnUrl được lưu trong authentication properties.
3. Google Callback
Sau khi user chấp nhận, Google redirect về /api/auth/google-callback. Backend:
- Xác thực kết quả qua Google scheme
- Gọi
FindOrCreateUserAsync()để xử lý tài khoản visitor - Tạo JWT token với user claims (tên, avatar, roles)
- Set
httpOnlycookie (auth_token, thời hạn 7 ngày) - Dọn dẹp temporary OAuth cookie
- Redirect về trang frontend ban đầu
4. Frontend Khôi Phục Session
Sau khi redirect, AuthProvider (React Context) chạy lúc mount và gọi GET /api/auth/me với cookie tự động đính kèm. Backend validate JWT, lấy profile và trả về. UI cập nhật hiển thị trạng thái đã đăng nhập.
Xử Lý Tài Khoản: Find or Create
Phương thức FindOrCreateUserAsync xử lý ba kịch bản mỗi lần Google login:
- Returning user (đã link Google) — Tìm bằng Google provider ID trực tiếp. Đường đi nhanh nhất.
- Tài khoản đã có cùng email — Tự động link Google login vào tài khoản hiện có (với non-admin).
- User mới — Tạo visitor account mới, gán role
Visitors, link Google login.
Vòng Đời Session Trên Frontend
React context AuthProvider quản lý toàn bộ session lifecycle:
- Lúc mount: Gọi
/api/auth/međể khôi phục session từ cookie - Mỗi 5 phút: Gọi
/api/auth/status(kiểm tra JWT nhẹ, không query DB) để phát hiện session hết hạn - Khi hết hạn: Xóa user state, hiện toast "Session expired"
- Khi logout: Gọi
POST /api/auth/logout→ backend xóa cookie → frontend xóa state
Thiết Kế JWT Token
JWT là self-contained và bao gồm:
- User ID (
sub), email, unique token ID (jti) - Display name và avatar URL từ Google profile
- User roles (ví dụ:
Visitors) - Ngày tạo tài khoản
Token được giao và đọc qua httpOnly cookie — JavaScript trên frontend không bao giờ chạm đến raw token. JwtBearer middleware đọc trực tiếp từ cookie trong mỗi API request.
Bảo Vệ Route
Trên frontend, component AuthGuard bọc các trang cần xác thực. Nó kiểm tra trạng thái auth từ context và redirect user chưa đăng nhập về trang chủ. Loading state được hiển thị trong khi session đang được xử lý.
Trên backend, bất kỳ endpoint nào cần xác thực được đánh dấu với:
[Authorize(AuthenticationSchemes = "JwtBearer")]
Điều này sử dụng JWT scheme một cách tường minh, hoàn toàn tách biệt khỏi CMS admin scheme.
Những Điểm Rút Ra
- Chạy hai hệ thống auth (CMS admin + visitor Google OAuth) trên cùng backend hoàn toàn khả thi bằng cách sử dụng named authentication schemes trong ASP.NET Core
- Dùng
httpOnlycookie cho JWT là cách tiếp cận sạch hơnlocalStoragecho các setup cross-origin Next.js ↔ CMS - Ưu tiên tìm user bằng Google provider ID (trước email lookup) mạnh mẽ hơn cho returning users và tránh edge case email collision
- Endpoint
/api/auth/statusnhẹ (không query DB) giúp background session check rẻ về chi phí - Lưu profile metadata (avatar, display name) dưới dạng Identity claims tránh phải query DB thêm mỗi request