Thay thế Supabase Auth bằng Clerk: thiết lập đăng nhập bảo mật trong 30 phút
Supabase mạnh. Auth tích hợp nhanh. Nhưng khi sản phẩm lớn hơn, nhu cầu auth đổi: social login tốt hơn, quản lý session chặt hơn, giao diện đăng nhập sẵn có, bảo mật nhiều lớp, tổ chức/team, MFA, webhook rõ ràng. Lúc đó, Clerk thành lựa chọn đáng cân nhắc.
Bài này chỉ cách thay Supabase Auth bằng Clerk trong app dùng Supabase làm database. Mục tiêu: vẫn dùng Supabase Postgres, RLS, API, nhưng bỏ phần xác thực Supabase Auth. Clerk lo đăng nhập, đăng ký, session, user management. Supabase lo dữ liệu.
Kịch bản phù hợp: app Next.js, frontend cần đăng nhập nhanh, backend cần xác minh user chắc, database vẫn nằm trong Supabase.
Vì sao thay Supabase Auth bằng Clerk?
Supabase Auth tốt, nhưng không luôn đủ
Supabase Auth hợp khi cần auth sát database, đơn giản, cùng hệ sinh thái. Nhưng có điểm khiến nhiều team đổi:
– UI đăng nhập phải tự làm nhiều.
– Quản lý session phía frontend cần thêm code.
– Social login, MFA, organization, invitation cần cấu hình kỹ.
– User metadata và lifecycle cần xử lý thêm.
– Trải nghiệm dashboard quản lý user không mạnh bằng Clerk.
Clerk mạnh ở lớp identity
Clerk tập trung vào authentication và user experience. Điểm mạnh:
– Component đăng nhập sẵn: , , .
– Session bảo mật: token rotation, device/session management.
– Social login nhanh: Google, GitHub, Microsoft, Apple.
– MFA, magic link, passkey: bật trong dashboard.
– Organizations: hợp SaaS B2B.
– Webhook user lifecycle: sync user sang database riêng.
– Middleware bảo vệ route: gọn, rõ.
Ý tưởng chính: Clerk xác thực người dùng, Supabase lưu dữ liệu.
Kiến trúc sau khi thay
Luồng mới:
1. User đăng nhập bằng Clerk.
2. App lấy userId từ Clerk session.
3. App gọi Supabase bằng server client.
4. Dữ liệu trong Supabase gắn với clerk_user_id.
5. RLS hoặc backend policy dùng clerk_user_id để kiểm soát quyền.
Bảng ví dụ:
create table profiles (
id uuid primary key default gen_random_uuid(),
clerk_user_id text unique not null,
email text,
full_name text,
created_at timestamptz default now()
);
Supabase không còn là nguồn identity chính. auth.users không cần dùng cho user mới. Nguồn truth: Clerk.
SUPABASE_SERVICE_ROLE_KEY bỏ qua RLS. Chỉ dùng trong server route, server action, cron, webhook. Không prefix bằng NEXT_PUBLIC_.
RLS với Clerk cần thiết kế riêng
Supabase RLS mặc định hay dùng auth.uid(). Khi bỏ Supabase Auth, auth.uid() không còn ý nghĩa với Clerk token nếu chưa cấu hình JWT integration.
Có hai hướng:
– Backend-enforced security: dùng service role, mọi query qua server, luôn filter bằng userId.
– JWT integration: cấu hình Clerk phát JWT cho Supabase, rồi RLS đọc claim.
Cách 1 nhanh trong 30 phút. Cách 2 mạnh hơn nếu client cần query trực tiếp Supabase.
Checklist 30 phút
0-5 phút
– Tạo app Clerk.
– Copy key vào .env.local.
– Cài @clerk/nextjs.
5-10 phút
– Bọc app bằng ClerkProvider.
– Thêm middleware.ts.
– Tạo /sign-in, /sign-up.
10-20 phút
– Tạo profiles.
– Tạo Supabase admin client.
– Dùng auth() lấy userId.
– Query dữ liệu theo clerk_user_id.
20-30 phút
– Thêm ensureProfile() hoặc webhook.
– Chuyển route private sang Clerk.
– Test login, logout, truy cập dashboard, query dữ liệu.
Lỗi thường gặp
Login xong nhưng vẫn bị đá ra
Kiểm tra Clerk Dashboard URL:
/sign-in
/sign-up
/dashboard
Sai URL gây redirect vòng.
API trả Unauthorized
Route handler phải chạy server-side và gọi:
const { userId } = auth();
Nếu gọi từ nơi không có request context, userId rỗng.
Dữ liệu user khác bị lộ
Thiếu .eq("clerk_user_id", userId). Sửa ngay. Nếu dùng service role, lỗi này nghiêm trọng.
Webhook không chạy
Kiểm tra:
– URL public, không phải localhost trừ khi dùng tunnel.
– CLERK_WEBHOOK_SECRET đúng.
– Header svix-* đủ.
– Event đã chọn trong Clerk Dashboard.
Kết luận thực tế
Thay Supabase Auth bằng Clerk không có nghĩa bỏ Supabase. Cách tốt: Clerk làm identity, Supabase làm database. Trong 30 phút, bạn có thể có login bảo mật, UI đẹp, session ổn, social login sẵn, route protection rõ.
Điểm cần cẩn thận: phân quyền dữ liệu. Nếu dùng SUPABASE_SERVICE_ROLE_KEY, mọi query phải đi qua server và luôn filter theo userId từ Clerk. Với app nhỏ và vừa, cách này nhanh, rõ, đủ an toàn nếu code kỷ luật. Với app lớn, nhiều client query trực tiếp, hãy đầu tư JWT integration và RLS dựa trên claim.
Migration tốt nhất làm từng bước: thêm Clerk, sync profile, chuyển route, chuyển bảng sang clerk_user_id, rồi tắt dần Supabase Auth. Không cần đập đi xây lại. Chuyển nhỏ, test chắc, tránh lộ dữ liệu.