Dùng Authorization Code Flow + PKCE (RFC 6749/7636), không dùng Implicit Flow.
Các bước:
1. Client tạo code_verifier ngẫu nhiên, hash ra code_challenge, và một state chống CSRF. Redirect user tới Google /authorize kèm client_id, redirect_uri, scope, state, code_challenge.
2. User đăng nhập + đồng ý; Google redirect về redirect_uri?code=...&state=....
3. Server kiểm tra state khớp, rồi đổi code + code_verifier lấy token ở endpoint /token (bước server-to-server, có client_secret nếu là confidential client).
4. Lấy hồ sơ user từ id_token (JWT, verify chữ ký + aud/iss) hoặc gọi userinfo.
import { randomBytes, createHash } from "crypto"
const b64url = (b) => b.toString("base64url")
const verifier = b64url(randomBytes(32)) // keep for the /token step
const challenge = b64url(createHash("sha256").update(verifier).digest())
// send challenge + method S256 to /authorize; exchange code + verifier at /tokenTạo/link user: tìm theo email (đã verify) hoặc theo sub (id ổn định của Google). Nếu email đã tồn tại → link provider vào tài khoản cũ thay vì tạo trùng. Sau đó phát session/token của app mình.
Bẫy hay gặp: quên verify state (mở CSRF), tin email chưa email_verified, hoặc dùng sub sai chỗ.