
Trả lời nhanh: Supabase Auth (GoTrue) cho app của bạn hệ đăng nhập hoàn chỉnh không phải tự viết: email + mật khẩu, magic link, OAuth Google/GitHub. Bản self-host dùng y hệt bản cloud qua SDK supabase-js: khác ở chỗ bạn phải tự cấu hình SMTP (không có ai gửi email hộ) và tự khai redirect URL đúng domain. Sức mạnh thật nằm ở Row Level Security: luật phân quyền viết ngay trong Postgres.
Tự viết hệ đăng nhập là bài toán ai cũng tưởng dễ cho đến khi đụng reset mật khẩu, chống dò, phiên hết hạn, OAuth, Supabase Auth gói sẵn tất cả, và bản self-host cho bạn nguyên gói đó trên VPS của mình. Bài này đi từ luồng email/password đến OAuth Google, tập trung vào phần tài liệu hay bỏ lửng: những thứ bản cloud làm hộ mà bản tự host phải tự lo.
- SDK phía app giống hệt bản cloud, chỉ URL và anon key là của server bạn
- SMTP là việc bắt buộc của bản tự host: không cấu hình là mọi luồng email chết im lặng
- OAuth Google: tạo credential phía Google, khai vào .env, redirect URL phải khớp từng ký tự
- Row Level Security: luật ai-thấy-gì nằm trong Postgres, anon key lộ cũng không đọc được bậy
- SERVICE_ROLE_KEY bỏ qua mọi luật, chỉ sống ở backend, lộ là mất trắng
Luồng cơ bản: email + mật khẩu trong 20 dòng
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://api.ten-mien.vn', // Kong cua ban, khong phai *.supabase.co
'ANON_KEY-cua-ban')
// dang ky / dang nhap / dang xuat:
await supabase.auth.signUp({ email, password })
await supabase.auth.signInWithPassword({ email, password })
await supabase.auth.signOut()
// phien hien tai (tu refresh token):
const { data: { session } } = await supabase.auth.getSession()SDK tự quản token và refresh, app của bạn chỉ hỏi session còn sống không. Mọi thứ chạy y hệt tài liệu chính chủ; khác biệt duy nhất là hai tham số đầu trỏ về hạ tầng của bạn (dựng theo bài dựng stack).
SMTP: mảnh bản cloud làm hộ mà tự host phải tự lo
Xác nhận đăng ký, magic link, reset mật khẩu, tất cả đi qua email, và bản self-host không có ai gửi hộ: chưa khai SMTP là các luồng đó chết không một lời báo lỗi phía người dùng. Khai trong .env:
SMTP_HOST=smtp.dich-vu-email.com
SMTP_PORT=587
SMTP_USER=...
SMTP_PASS=...
SMTP_SENDER_NAME="Ten App"
[email protected]Dùng dịch vụ email giao dịch tử tế (SMTP của nhà cung cấp email doanh nghiệp bạn đang có cũng được), tự bắn từ IP VPS trần dễ vào spam. Chỉnh nội dung email tiếng Việt qua các biến template MAILER_TEMPLATES trong .env. Thử ngay sau khi khai: đăng ký một tài khoản thật, email xác nhận phải đến trong vài giây.
OAuth Google: một lần khai, khớp từng ký tự
Trình tự: tạo OAuth Client trong Google Cloud Console (loại Web) → Authorized redirect URI điền chính xác https://api.ten-mien.vn/auth/v1/callback → lấy Client ID/Secret khai vào .env (bộ biến GOTRUE_EXTERNAL_GOOGLE_*) → restart auth. Phía app: supabase.auth.signInWithOAuth({ provider: 'google' }). Chín phần mười lỗi OAuth bản tự host nằm ở URL không khớp từng ký tự, http/https, www hay không, dấu gạch cuối; và SITE_URL trong .env phải là domain thật của app để redirect sau đăng nhập về đúng chỗ. GitHub, Facebook cùng công thức với bộ biến tương ứng.
Row Level Security: phân quyền nằm trong database
Điểm ăn tiền nhất của hệ Supabase: RLS, luật ai-đọc-ghi-gì khai ngay trên bảng Postgres, thi hành ở tầng database nên anon key có lộ cũng không vượt được luật:
alter table todos enable row level security;
create policy "user thay viec cua minh" on todos
for select using ( auth.uid() = user_id );
create policy "user them viec cho minh" on todos
for insert with check ( auth.uid() = user_id );auth.uid() là ID người dùng rút từ JWT của phiên, sợi chỉ nối Auth với database. Kỷ luật sống còn: bảng nào phơi qua API đều phải bật RLS (quên là cả internet đọc được qua anon key), và SERVICE_ROLE_KEY bỏ qua toàn bộ RLS, chỉ dùng ở backend/cron, không bao giờ nhúng vào app phía người dùng.
Các bẫy tự host còn lại, gom một chỗ
Đăng ký mở toang, mặc định ai cũng signUp được: app nội bộ thì tắt (GOTRUE_DISABLE_SIGNUP=true) rồi mời người dùng bằng API admin. Token hết hạn khó hiểu sau khi đổi JWT_SECRET, đổi secret là mọi phiên cũ chết: chủ đích khi ứng phó sự cố, tai nạn khi quên (đã cảnh báo ở bài dựng). Giờ máy lệch, JWT sống bằng timestamp, VPS lệch giờ là token lúc được lúc không: bật NTP (bệnh quen của cả series lỗi Tailscale). Quản user bằng gì, Studio có tab Authentication đủ dùng: xem, khóa, xóa, mời user, nhớ Studio phải nằm sau tailnet như bài dựng đã khóa.
Kiểm sau khi dựng, năm lệnh
# 1. Service xac thuc song chua docker compose ps auth docker compose logs --tail=60 auth # 2. Gui thu thu that qua may chu thu cua ban docker compose logs auth | grep -i "smtp\|mail" # 3. Kiem chinh sach phan quyen da bat tren tung bang # chay trong psql: bang nao chua bat la bang lo docker compose exec db psql -U postgres -c \ "SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname='public';" # 4. Kiem khoa ky khong con la khoa mau docker compose exec auth printenv | grep -i jwt | cut -c1-40 # 5. Cong nao dang mo ra internet ss -ltnp
Lệnh thứ ba là lệnh quan trọng nhất. Một bảng chưa bật chính sách phân quyền là một bảng ai cầm khóa công khai cũng đọc được toàn bộ, và đây là lỗi rò rỉ dữ liệu phổ biến nhất của các bản tự dựng.
Bốn bẫy hay gặp và cách nhận ra
| Triệu chứng | Nguyên nhân | Cách xử |
|---|---|---|
| Đăng ký được nhưng không nhận thư xác nhận | Chưa cấu hình máy chủ thư, hoặc thư vào hộp rác | Dùng dịch vụ gửi thư có xác thực tên miền, đừng gửi thẳng từ máy chủ |
| Đăng nhập bằng tài khoản Google báo lỗi địa chỉ trả về | Địa chỉ khai ở phía nhà cung cấp lệch một ký tự | Chép nguyên văn, chú ý dấu gạch chéo cuối và giao thức bảo mật |
| Người dùng đọc được dữ liệu của người khác | Bảng chưa bật chính sách phân quyền | Bật trên mọi bảng, rồi viết chính sách cho từng bảng |
| Phiên đăng nhập rơi liên tục | Thời hạn thẻ quá ngắn, hoặc đồng hồ máy chủ lệch | Chỉnh thời hạn, và bật đồng bộ giờ trên máy chủ |
Việc phải làm trước khi mở cho người thật dùng
- Bật chính sách phân quyền trên mọi bảng có dữ liệu người dùng, rồi tự thử bằng hai tài khoản khác nhau xem có đọc chéo được không.
- Đặt giới hạn tần suất đăng nhập để chặn dò mật khẩu tự động.
- Bật sao lưu cơ sở dữ liệu hằng ngày trước ngày mở, không phải sau. Chi tiết ở bài về sao lưu và cập nhật.
- Đóng bảng điều khiển quản trị khỏi internet, chỉ truy cập qua mạng riêng.
Câu hỏi thường gặp
Supabase Auth tự host có an toàn bằng dịch vụ auth thương mại không?
GoTrue là engine đã được dùng ở quy mô lớn qua bản cloud, lỗ hổng thường nằm ở cấu hình phía người dựng: secret yếu, quên RLS, lộ service key. Làm đúng checklist của cụm bài này là đạt mặt bằng an toàn tốt cho đa số ứng dụng.
Đang dùng NextAuth/tự viết JWT, có nên chuyển sang Supabase Auth?
Nếu đã dùng Supabase làm database thì Auth tích hợp RLS là lợi thế lớn, phân quyền tận tầng dữ liệu. App không dùng Supabase database thì lợi ích mỏng hơn, cân nhắc chi phí chuyển.
Magic link có hoạt động trên bản tự host không?
Có, signInWithOtp gửi link qua email; điều kiện là SMTP đã cấu hình chuẩn và SITE_URL đúng. Không nhận được link: kiểm log container auth và hộp spam trước tiên.
Giới hạn được tốc độ đăng nhập chống dò mật khẩu không?
GoTrue có rate limit tích hợp cho các endpoint nhạy cảm (điều chỉnh qua biến GOTRUE_RATE_LIMIT_*); thêm một lớp nữa ở reverse proxy (Caddy/Nginx rate limit theo IP) là đủ chững chạc.
Bài viết liên quan
- Self-host Supabase trên VPS bằng Docker Compose từng bước
- Backup và update Supabase self-host an toàn
- Cài PostgreSQL trên VPS Ubuntu: chuẩn production 15 phút
- Anthropic tặng $100-$250 credit Claude Code cloud sessions cho gói Pro/Max, nhận trước 7/10
- VPS free dùng thử 2026: 5 cách get VPS miễn phí + caveat thật
- Vite là gì? Vì sao dev server của nó nhanh như vậy
- Top 7 VPS Việt Nam giá rẻ 2026: so sánh thực tế từ user dev
- Ceph SSD Enterprise là gì? Tại sao TND chọn công nghệ này cho VPS
- Cài Proxmox build cloud nhỏ tại nhà: tự host vs thuê VPS, khi nào đáng?



