Bài 11 · Nâng cao · 26 phút
Đăng nhập với Better-auth
Biên soạn bởi Nguyễn Anh Tuấn
Trọn vòng tài khoản khách bằng Better-auth: đăng ký, đăng nhập username/mật khẩu, OAuth qua Google và Zalo, và luồng quên mật khẩu (gửi email đặt lại). Khái niệm session, băm mật khẩu (password hashing), vì sao TUYỆT ĐỐI đừng tự chế phần xác thực. Cho Subagent bảo mật rà soát trước khi mở cho người dùng.
Web bán hàng giờ đã nhớ được dữ liệu, nhưng còn thiếu một thứ căn bản: biết khách là ai. Phải có đăng ký và đăng nhập thì mới gắn được ví, đơn hàng và số dư cho đúng người. Việc chứng minh "bạn đúng là chủ tài khoản này" gọi là authentication (xác thực).
Đây là chỗ nhạy cảm bậc nhất: làm sai là lộ tài khoản, lộ mật khẩu, mất niềm tin. Nên bài này không khuyến khích mèo con "sáng tạo" - mà dựa vào công cụ đã được kiểm chứng.
Tự viết đăng nhập nghe đơn giản nhưng đầy cạm bẫy: lưu mật khẩu sai cách, session đoán được, link đặt lại bị lợi dụng... Một lỗ hổng nhỏ ở đây là toang cả sản phẩm. Quy tắc: đừng phát minh lại phần xác thực - dùng thư viện đã được nhiều người dùng và rà soát.
Khoá dùng Better-auth - một thư viện xác thực cho TypeScript/Next.js. Nó lo sẵn các phần khó: băm mật khẩu, session, đăng nhập Google/Zalo, quên mật khẩu. Mèo con mô tả, Claude gắn nó vào app.
- ▸Xác thực là chỗ nhạy cảm - lỗ hổng nhỏ cũng nguy hiểm.
- ▸Đừng tự chế: dùng thư viện đã kiểm chứng (Better-auth) an toàn hơn đồ tự viết.
- ▸Better-auth lo: hashing, session, OAuth (Google/Zalo), quên mật khẩu.
Nguyên tắc số một: không bao giờ lưu mật khẩu nguyên văn. Thay vào đó, mật khẩu được băm (hash) - biến thành một chuỗi rối một chiều - rồi mới lưu. Lúc đăng nhập, hệ thống băm lại mật khẩu vừa nhập và so khớp hai hash:
Vì băm là một chiều, kể cả khi database bị lộ, kẻ tấn công cũng không suy ngược ra được mật khẩu gốc. Better-auth làm phần này theo chuẩn (argon2/bcrypt) - mèo con không phải tự lo.
Sau khi đăng nhập đúng, chẳng lẽ mỗi lần bấm gì cũng bắt gõ lại mật khẩu? Không. Hệ thống cấp một session - như một tấm vé lưu trong cookie của trình duyệt. Các request sau kèm tấm vé đó, server biết ngay "à, người này đã đăng nhập" mà không hỏi lại mật khẩu.
Trung thực
Nhiều khách ngại tạo thêm một mật khẩu mới. Cho họ đăng nhập bằng Google hoặc Zalo tiện hơn nhiều. Cơ chế là OAuth: khách bấm "đăng nhập bằng Google", Google tự hỏi mật khẩu ở trang của Google rồi chỉ báo lại cho site "người này hợp lệ, đây là email".
Điểm cần nhớ
Nút "đăng nhập bằng Google/Zalo" không tự chạy. Với MỖI nhà cung cấp, mèo con tạo một ứng dụng OAuth để lấy Client ID + Client Secret, và khai báo redirect URI - địa chỉ Better-auth nhận kết quả trả về. Làm một lần cho mỗi nhà cung cấp.
Google (Google Cloud Console):
- ▸Tạo một project ở Google Cloud Console.
- ▸Cấu hình "OAuth consent screen": tên app, email liên hệ, phạm vi cơ bản (email, profile).
- ▸Tạo "OAuth client ID" loại Web application.
- ▸Thêm Authorized redirect URI trỏ tới callback Better-auth (vd https://ten-mien/api/auth/callback/google).
- ▸Copy Client ID + Client Secret, đặt vào .env.
Zalo (Zalo Developers):
- ▸Tạo một ứng dụng ở Zalo Developers (developers.zalo.me).
- ▸Bật Zalo Login và khai báo redirect URL trỏ về Better-auth.
- ▸Lấy App ID + Secret Key, đặt vào .env.
- ▸Better-auth chưa có provider Zalo sẵn nên nối qua "generic OAuth" - đưa endpoint authorize/token Zalo cung cấp.
.env - khoá OAuth (đổi ten-mien theo của bạn); KHÔNG đẩy lên GitHub
# Redirect URI: khai báo ở Google/Zalo đúng đường dẫn callback Better-auth in ra.
# vd Google: https://ten-mien/api/auth/callback/google
# (khi dev: http://localhost:3000/api/auth/callback/google)
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."
ZALO_APP_ID="..."
ZALO_APP_SECRET="..." Tài liệu chính hãng
Mẹo
Mật khẩu lộ là mất tài khoản. Thêm lớp thứ hai (2FA - two-factor authentication): ngoài mật khẩu, cần thêm một bằng chứng nữa. Với tài khoản dính tới tiền, khoá bắt buộc khách bật một trong hai:
- ▸Passkey (WebAuthn): khoá gắn thiết bị + sinh trắc (vân tay/Face ID); không có mã để lộ, chống lừa đảo (phishing) tốt nhất - khuyến nghị.
- ▸TOTP: mã 6 số đổi mỗi 30 giây trong app authenticator (Google Authenticator/Authy); phương án dự phòng khi không dùng được passkey.
- ▸Better-auth có sẵn plugin passkey và two-factor (TOTP) - bật là dùng, không tự chế.
Bắt buộc thiết lập ngay sau khi đăng nhập
Quan trọng không kém: mọi thao tác nhạy cảm phải xác nhận lại ngay lúc đó (step-up), không chỉ dựa vào việc đã đăng nhập từ trước - vì máy có thể bị bỏ quên lúc đang mở:
- ▸Step-up áp cho: nạp/rút/chuyển tiền, đổi mật khẩu, đổi email, và mọi thao tác admin.
- ▸Mỗi lần xác nhận ghi log (ai, làm gì, khi nào) để truy vết khi cần.
- ▸Cho subagent bảo mật rà: đúng các key action nhạy cảm đều có step-up, không sót cái nào.
Khách quên mật khẩu là chuyện thường. Cách đúng không phải gửi lại mật khẩu cũ - database chỉ giữ hash nên cũng không đọc ra được, mà gửi mật khẩu qua email cũng không an toàn. Thay vào đó: gửi một link đặt lại có hạn (vd 30 phút), khách bấm vào và tạo mật khẩu mới.
Quan trọng: reset luôn 2FA
Trung thực
Better-auth lưu user và session vào PostgreSQL qua Drizzle - ăn khớp với mô hình dữ liệu mèo con đã dựng. Đại ý khi gắn vào, mô tả cho Claude rất gọn:
Mô tả cho Claude (không phải code mèo con tự viết) - Better-auth + Drizzle
Gắn Better-auth vào app, lưu user/session vào PostgreSQL qua Drizzle.
Bật:
- đăng ký / đăng nhập bằng email + mật khẩu (băm argon2)
- đăng nhập Google và Zalo (OAuth)
- 2FA: passkey (WebAuthn) + TOTP; BẮT BUỘC thiết lập ngay sau đăng nhập
- xác nhận lại (step-up) cho thao tác nhạy cảm: nạp/rút tiền, đổi mật khẩu, admin
- quên mật khẩu: gửi link đặt lại có hạn 30 phút; đặt lại mật khẩu thì RESET 2FA
Sau đó cho subagent bảo mật rà phần xác thực này. Bước tiếp theo
Câu hỏi thường gặp
Là một thư viện xác thực cho TypeScript/Next.js, lo phần khó và nhạy cảm của đăng nhập: lưu session, băm mật khẩu, đăng nhập qua Google/Zalo (OAuth), quên mật khẩu. Mèo con mô tả việc, Claude gắn Better-auth vào app thay vì tự viết phần này từ đầu.
Vì xác thực có rất nhiều cạm bẫy: lưu mật khẩu sai cách, session đoán được, lộ token đặt lại... Người viết thiếu kinh nghiệm rất dễ để lại lỗ hổng. Thư viện như Better-auth đã được nhiều người dùng và rà soát, nên an toàn hơn hẳn đồ tự chế. Đây là chỗ "đừng phát minh lại bánh xe".
Mã hoá hai chiều: có khoá thì giải ra lại nội dung gốc. Băm một chiều: từ mật khẩu ra hash thì được, nhưng từ hash KHÔNG suy ngược lại ra mật khẩu. Mật khẩu nên băm (không cần đọc lại bao giờ), chỉ cần băm lại lúc đăng nhập rồi so khớp hai hash.
Không. Mèo con bấm "đăng nhập bằng Google", Google tự hỏi mật khẩu ở trang của Google rồi chỉ báo lại cho site "người này hợp lệ, đây là email". Site không bao giờ thấy mật khẩu Google. Đó là OAuth - cùng kiểu uỷ quyền như khi kết nối Claude với GitHub ở bài trước.
Ở Google Cloud Console: tạo một project, cấu hình "OAuth consent screen", rồi tạo "OAuth client ID" loại Web application. Khai báo redirect URI trỏ về callback của Better-auth (vd https://ten-mien/api/auth/callback/google), copy Client ID + Client Secret đặt vào .env. Link chính hãng có trong callout của bài.
Tạo một ứng dụng ở Zalo Developers (developers.zalo.me), bật Zalo Login và khai báo redirect URL, rồi lấy App ID + Secret Key. Better-auth chưa có sẵn provider Zalo nên cấu hình qua "generic OAuth" - đưa các endpoint authorize/token mà Zalo cung cấp. Nhờ Claude làm phần nối; mèo con chỉ cần tạo app và dán khoá vào .env.
Passkey (WebAuthn) là khoá gắn với thiết bị, mở bằng sinh trắc (vân tay/Face ID) - không có mã nào để lộ hay bị lừa nhập, nên chống lừa đảo (phishing) tốt nhất. TOTP là mã 6 số đổi mỗi 30 giây trong app authenticator; tiện và phổ biến nhưng vẫn có thể bị dụ đọc mã. Khoá khuyến nghị passkey, để TOTP làm dự phòng.
Là yêu cầu xác nhận LẠI danh tính (Passkey/TOTP) ngay tại thời điểm làm một việc nhạy cảm - nạp/rút tiền, đổi mật khẩu, thao tác admin - thay vì chỉ tin vào lần đăng nhập trước. Phòng khi máy bị bỏ quên đang mở phiên: kẻ khác cũng không rút được tiền vì không qua được bước xác nhận lại.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Authentication (xác thực) là gì?
- 1
Gắn Better-auth
Bật plan mode, nhờ Claude thêm Better-auth vào app và lưu user/session vào PostgreSQL qua Drizzle; duyệt kế hoạch rồi cho dựng.
✅ Hoàn thành khi: Database có bảng user/session của Better-auth; đăng ký được một tài khoản email/mật khẩu để test.
- 2
Đăng ký rồi đăng nhập lại
Tạo một tài khoản bằng email + mật khẩu, đăng xuất, rồi đăng nhập lại.
✅ Hoàn thành khi: Đăng nhập lại vào được, trang hiển thị trạng thái đã đăng nhập (vd tên/email khách).
- 3
Kiểm mật khẩu đã được băm
Xem bảng user trong database, tìm cột mật khẩu của tài khoản vừa tạo.
✅ Hoàn thành khi: Cột mật khẩu là một chuỗi hash (vd bắt đầu $argon2 hoặc $2b$...), KHÔNG phải "meo123" đọc được.
- 4
Tạo Google OAuth app
Ở Google Cloud Console: tạo project, cấu hình OAuth consent screen, tạo OAuth client ID (Web application), thêm redirect URI của Better-auth; dán Client ID/Secret vào .env rồi nhờ Claude bật đăng nhập Google.
✅ Hoàn thành khi: Đăng nhập được bằng một tài khoản Google mà không phải tạo mật khẩu mới ở site của mèo con.
- 5
Tạo Zalo OAuth app
Ở Zalo Developers: tạo ứng dụng, bật Zalo Login, khai báo redirect URL; lấy App ID + Secret Key đặt vào .env; nhờ Claude nối Zalo qua generic OAuth của Better-auth.
✅ Hoàn thành khi: Đăng nhập được bằng tài khoản Zalo (hoặc tới được màn hình cấp quyền của Zalo nếu app chưa được duyệt).
- 6
Luồng quên mật khẩu
Nhờ Claude bật chức năng quên mật khẩu; bấm "quên mật khẩu", nhận link đặt lại, tạo mật khẩu mới.
✅ Hoàn thành khi: Đặt được mật khẩu mới qua link và đăng nhập lại bằng mật khẩu mới đó.
- 7
Bật 2FA & bắt buộc thiết lập
Nhờ Claude bật plugin passkey + two-factor (TOTP) của Better-auth, và cấu hình bắt buộc thiết lập 2FA ngay sau khi đăng nhập.
✅ Hoàn thành khi: Đăng nhập xong bị bắt thiết lập Passkey hoặc TOTP; chưa thiết lập thì chưa vào được phần chính.
- 8
Step-up cho thao tác nhạy cảm
Nhờ Claude bắt buộc xác nhận lại Passkey/TOTP khi đổi mật khẩu (và sau này là nạp/rút tiền).
✅ Hoàn thành khi: Thử đổi mật khẩu → bị yêu cầu xác nhận Passkey/TOTP trước khi cho đổi.
- 9
Forgot-password reset 2FA
Đặt lại mật khẩu qua email rồi thử đăng nhập lại.
✅ Hoàn thành khi: 2FA cũ không còn dùng được; hệ thống bắt thiết lập Passkey/TOTP mới (và backup codes nếu có).
- 10
Cho subagent bảo mật rà
Gọi subagent rà bảo mật (OWASP) kiểm phần xác thực trước khi mở cho người dùng.
✅ Hoàn thành khi: Có danh sách phát hiện; mỗi mục được sửa hoặc mèo con xác nhận lý do an toàn.