Supabase Authentication — Email, OAuth, SSO, MFA
Tổng quan
Supabase Auth cung cấp xác thực và phân quyền tích hợp sẵn. Dùng JSON Web Tokens (JWT) để xác thực, và tích hợp với Row Level Security (RLS) để phân quyền ở tầng database.
Kiến trúc Auth — 4 tầng
[Client SDK] --> [Kong API Gateway] --> [GoTrue Auth Service] --> [PostgreSQL]
| | | |
| Rate limiting, Xác thực user, Lưu user data
| routing, API key sinh JWT, gửi email trong auth schema
| validation
- Client layer — Supabase client SDKs (
@supabase/supabase-js, Flutter, Swift, Python, C#, Kotlin) hoặc HTTP request thủ công - Kong API Gateway — Shared gateway cho tất cả Supabase products, xử lý rate limiting và routing
- Auth service (GoTrue) — Server xác thực, fork từ dự án GoTrue của Netlify, sinh JWT và quản lý session
- Postgres database — Lưu trữ user data trong
authschema (auth.users,auth.identities,auth.sessions, ...)
public.profiles với id UUID REFERENCES auth.users(id).Cấu trúc JWT Token
Supabase Auth sử dụng JSON Web Token (JWT) làm cơ chế xác thực chính. Mỗi access token chứa các claims sau:
{
"sub": "d0d4e701-5a5c-4c8d-b8e7-a1b2c3d4e5f6",
"email": "user@example.com",
"phone": "",
"app_metadata": {
"provider": "email",
"providers": ["email", "google"]
},
"user_metadata": {
"full_name": "Nguyen Van A",
"avatar_url": "https://..."
},
"role": "authenticated",
"aal": "aal1",
"session_id": "abc123-def456-...",
"iat": 1715702400,
"exp": 1715706000
}
Giải thích từng claim trong JWT:
| Claim | Ý nghĩa | Ví dụ |
|---|---|---|
sub | User UUID — định danh duy nhất của user | d0d4e701-... |
email | Email của user (nếu dùng email auth) | user@example.com |
phone | Số điện thoại (nếu dùng phone auth) | +84123456789 |
app_metadata | Metadata từ provider, user không thể thay đổi | {"provider": "email"} |
user_metadata | Metadata do app hoặc user set, có thể cập nhật | {"full_name": "Vinh"} |
role | Phân quyền cơ bản: authenticated hoặc anon | authenticated |
aal | Authenticator Assurance Level: aal1 (chưa MFA) hoặc aal2 (đã MFA) | aal2 |
session_id | UUID của session hiện tại | abc123-... |
iat | Issued At — thời điểm token được tạo (Unix timestamp) | 1715702400 |
exp | Expiration — thời điểm token hết hạn (Unix timestamp) | iat + 3600 |
Authorization: Bearer <token>. RLS policies dùng auth.uid() (lấy từ sub claim) và auth.role() (lấy từ role claim) để phân quyền. Custom claims có thể được thêm qua Custom Access Token Hook.Token Lifetime — Thời gian sống của token
| Token | Thời gian mặc định | Có thể cấu hình? | Mục đích |
|---|---|---|---|
| Access Token (JWT) | 3,600 giây (1 giờ) | Có — Dashboard > Authentication > Settings | Xác thực mỗi API request |
| Refresh Token | 604,800 giây (7 ngày) | Có — Dashboard > Authentication > Settings | Lấy access token mới mà không cần login lại |
Refresh Flow — Cơ chế tự động refresh token
User Login thành công
--> Server trả về access_token (JWT, hết hạn sau 1h)
+ refresh_token (opaque string, hết hạn sau 7d)
--> Client SDK lưu cả hai token trong localStorage / cookie
Mỗi API request:
--> Client gửi access_token trong header Authorization: Bearer <access_token>
Khi access_token hết hạn (sau ~1h):
--> Client SDK TỰ ĐỘNG gọi POST /auth/v1/token?grant_type=refresh_token
--> Gửi refresh_token trong body
--> Server kiểm tra refresh_token còn hợp lệ
--> Server trả về access_token MỚI (JWT mới, 1h mới)
+ có thể refresh_token mới (tùy cấu hình)
--> Client cập nhật token mới, TIẾP TỤC gọi API
--> USER KHÔNG HỀ BIẾT quá trình này xảy ra
Khi refresh_token hết hạn (sau 7d):
--> Server từ chối refresh
--> Client SDK phát hiện lỗi, throw error
--> User phải login lại từ đầu
@supabase/supabase-js) tự động xử lý refresh — developer không cần viết logic refresh thủ công. Khi refresh token cũng hết hạn, onAuthStateChange fire event SIGNED_OUT.Các phương thức xác thực
1. Email / Password
Phương thức cơ bản nhất — user đăng ký với email và mật khẩu:
// Sign up
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password',
options: {
data: {
full_name: 'Nguyen Van A', // user metadata
}
}
})
// Email verification được gửi tự động nếu enable trong Dashboard
// Sau khi verify, user có thể sign in
// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'secure-password'
})
// data.session chứa access_token (JWT) và refresh_token
// data.user chứa user info (id, email, metadata)
2. Magic Link (không cần password)
User nhận link đăng nhập qua email — không cần nhớ mật khẩu:
const { data, error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: 'https://myapp.com/dashboard',
shouldCreateUser: true, // Tự động tạo user nếu chưa tồn tại
}
})
// User nhận email với magic link
// Click link sẽ redirect về app kèm session
Magic link an toàn hơn password vì:
- Không cần user nhớ mật khẩu
- Link chỉ dùng một lần và có thời hạn ngắn
- Không có risk bị brute-force password
3. Phone Login (OTP qua SMS)
const { data, error } = await supabase.auth.signInWithOtp({
phone: '+84123456789',
options: {
channel: 'sms',
}
})
// User nhận SMS chứa OTP 6 chữ số
// Verify OTP:
const { data, session, error } = await supabase.auth.verifyOtp({
phone: '+84123456789',
token: '123456',
type: 'sms'
})
Phone Auth Providers — chi tiết:
| Provider | Đặc điểm | Khu vực hỗ trợ |
|---|---|---|
| MessageBird | Global coverage tốt, API đơn giản | Toàn cầu (190+ quốc gia) |
| Twilio | Provider phổ biến nhất, nhiều tính năng | Toàn cầu (180+ quốc gia) |
| Vonage (Nexmo) | Giá cạnh tranh, chất lượng ổn định | Toàn cầu |
Có thể cấu hình provider trong Dashboard > Authentication > Providers > Phone. Mỗi provider yêu cầu API key/secret riêng. Bạn cũng có thể tạo Send SMS Hook để dùng provider tùy chỉnh (như AWS SNS, Infobip).
WhatsApp OTP cũng được hỗ trợ thông qua Twilio — cấu hình channel là whatsapp.
4. Social Login (OAuth) — 50+ providers
// Google
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://myapp.com/auth/callback',
queryParams: {
access_type: 'offline',
prompt: 'consent',
}
}
})
// GitHub
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'https://myapp.com/auth/callback'
}
})
Danh sách đầy đủ các OAuth provider được hỗ trợ:
| Provider | Loại | Ghi chú |
|---|---|---|
| Apple | Social | Hỗ trợ Sign in with Apple, iOS/macOS native |
| Azure (Microsoft) | Enterprise | Azure AD / Microsoft Entra ID, hỗ trợ multi-tenant |
| Bitbucket | Developer | Dành cho team dùng Atlassian ecosystem |
| Discord | Social | Phổ biến cho game, community apps |
| Social | Cần Facebook App ID + App Secret | |
| Figma | Developer | Dành cho design/developer tools |
| GitHub | Developer | Phổ biến cho developer tools, SaaS |
| GitLab | Developer | Self-managed GitLab instances cũng được hỗ trợ |
| Social | Provider phổ biến nhất, hỗ trợ Google Workspace domains | |
| Kakao | Social | Phổ biến ở Hàn Quốc (KakaoTalk) |
| Keycloak | Enterprise | Open-source identity provider, tự host |
| Social | Dành cho professional/recruitment apps | |
| Notion | Productivity | Dành cho apps tích hợp với Notion |
| Slack | Productivity | Dành cho workspace/business apps |
| Spotify | Social | Dành cho music/audio apps |
| Twitter (X) | Social | Cần Twitter API v2 credentials |
| Twitch | Social | Dành cho gaming/streaming apps |
| WorkOS | Enterprise | Hỗ trợ nhiều enterprise IdPs qua một integration |
| Zoom | Productivity | Dành cho video conferencing apps |
Các provider khác: Auth0, AWS Cognito, Battle.net, Coinbase, Deezer, DigitalOcean, Discord, Dropbox, eBay, Etsy, Facebook (custom), Fitbit, Heroku, HubSpot, Instagram, Intercom, Kakao, Keycloak, Line, Linear, Mailchimp, Meetup, Naver, Notion, Okta, Patreon, Paypal, Pipedrive, Reddit, Salesforce, Shopify, Slack, SoundCloud, Spotify, Strava, Stripe, Tableau, TikTok, Todoist, Typeform, Uber, VK, X (Twitter), Yahoo, Yandex, YouTube, Zendesk, Zoom.
https://<project>.supabase.co/auth/v1/callback. Khi user login bằng OAuth, Supabase tự động tạo record trong auth.identities liên kết với auth.users. Một user có thể có nhiều identities (ví dụ: vừa Google vừa GitHub).Bạn cũng có thể thêm Custom OAuth/OIDC Provider — cấu hình Client ID, Client Secret, Authorization URL, Token URL, và User Info URL.
5. Enterprise SSO (SAML 2.0)
Hỗ trợ Single Sign-On cho doanh nghiệp với SAML 2.0. Dùng cho các ứng dụng enterprise với identity provider như Okta, Azure AD, OneLogin.
// SSO sign in
const { data, error } = await supabase.auth.signInWithSSO({
domain: 'company.com', // Email domain hoặc organization slug
options: {
redirectTo: 'https://myapp.com/dashboard'
}
})
6. Passkey (WebAuthn) — Sinh trắc học
// Register passkey (Face ID, Touch ID, Windows Hello)
const { data } = await supabase.auth.registerPasskey()
// Sign in với passkey
const { data, error } = await supabase.auth.signInWithPasskey()
7. Anonymous Sign-Ins
Cho phép người dùng dùng app mà không cần đăng ký, sau đó có thể liên kết tài khoản:
// Anonymous sign in
const { data, error } = await supabase.auth.signInAnonymously()
// Sau đó, user có thể link với email/OAuth để trở thành permanent user
const { data, error } = await supabase.auth.linkIdentity({
provider: 'google'
})
Identity Linking — quản lý nhiều phương thức đăng nhập cho cùng một user:
// Link Google identity vào tài khoản hiện tại
// (user đã login bằng email, muốn thêm Google login)
const { data, error } = await supabase.auth.linkIdentity({
provider: 'google'
})
// Link GitHub identity
const { data, error } = await supabase.auth.linkIdentity({
provider: 'github'
})
// Unlink — gỡ bỏ một identity khỏi tài khoản
// (chỉ hoạt động khi user còn ít nhất 1 identity khác)
const { data, error } = await supabase.auth.unlinkIdentity({
identity_id: 'd0d4e701-5a5c-4c8d-b8e7-a1b2c3d4e5f6'
})
// Lấy danh sách tất cả identities của user hiện tại
const { data, error } = await supabase.auth.getUserIdentities()
// data.identities = [
// { id: '...', provider: 'email', identity_data: { email: '...' } },
// { id: '...', provider: 'google', identity_data: { email: '...' } },
// ]
getUserIdentities() trả về tất cả identities giúp UI hiển thị "Connected accounts". Khi unlink, user phải còn ít nhất 1 identity — không thể xóa identity cuối cùng.8. Web3 Wallet (Ethereum / Solana)
const { data } = await supabase.auth.signInWithWeb3({
provider: 'ethereum'
})
Hỗ trợ MetaMask, WalletConnect và các wallet Ethereum/Solana.
Session Management
Session được quản lý qua access token (JWT, thời hạn 1 giờ) và refresh token (dùng để lấy access token mới):
// Lấy session hiện tại
const { data: { session } } = await supabase.auth.getSession()
// Lấy user hiện tại
const { data: { user } } = await supabase.auth.getUser()
// Refresh session (access token hết hạn -> dùng refresh token lấy mới)
const { data, error } = await supabase.auth.refreshSession()
// Sign out
const { error } = await supabase.auth.signOut()
Theo dõi thay đổi Auth State
// Listen auth state changes
supabase.auth.onAuthStateChange((event, session) => {
switch (event) {
case 'INITIAL_SESSION':
console.log('Initial session loaded:', session)
break
case 'SIGNED_IN':
console.log('User signed in:', session.user)
break
case 'SIGNED_OUT':
console.log('User signed out')
break
case 'TOKEN_REFRESHED':
console.log('Token refreshed')
break
case 'USER_UPDATED':
console.log('User updated:', session.user)
break
case 'USER_DELETED':
console.log('User deleted')
break
case 'PASSWORD_RECOVERY':
console.log('Password recovery event')
break
case 'MFA_CHALLENGE_VERIFIED':
console.log('MFA verified')
break
}
})
Giải thích từng onAuthStateChange event:
| Event | Khi nào fire | Hành động điển hình |
|---|---|---|
INITIAL_SESSION | App vừa load, session được khôi phục từ localStorage | Khởi tạo UI dựa trên session có sẵn |
SIGNED_IN | User vừa sign in thành công (email, OAuth, magic link, OTP) | Redirect về dashboard, fetch user data |
SIGNED_OUT | User sign out hoặc refresh token hết hạn | Xóa local state, redirect về login |
TOKEN_REFRESHED | Access token được refresh tự động | Cập nhật token trong app state (thường tự động) |
USER_UPDATED | User metadata thay đổi (updateUser, email change) | Cập nhật UI với thông tin mới |
USER_DELETED | User bị xóa (từ admin API hoặc tự xóa) | Xóa local data, redirect về trang chủ |
PASSWORD_RECOVERY | User click link reset password từ email | Hiển thị form đặt mật khẩu mới |
MFA_CHALLENGE_VERIFIED | User vượt qua MFA challenge thành công | Nâng session lên aal2, mở khóa tính năng nhạy cảm |
onAuthStateChange là listener quan trọng nhất trong Supabase Auth. Dùng nó để:- Sync auth state với UI framework (Vue reactivity, React context)
- Bảo vệ routes:
SIGNED_OUT-> redirect/login,SIGNED_IN-> redirect/dashboard - Xử lý MFA flow:
MFA_CHALLENGE_VERIFIED-> cập nhật UI - Cleanup:
USER_DELETED-> xóa local data INITIAL_SESSIONgiúp phân biệt "đang loading session" và "đã biết user chưa login"
Server-Side Auth — Xác thực trong API Routes
Khi xây dựng API với Next.js, Nuxt, SvelteKit, bạn cần xác thực user ở server side:
Next.js SSR Pattern
// app/api/protected/route.ts (Next.js App Router)
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export async function GET() {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{
cookies: {
get(name) { return cookieStore.get(name)?.value },
set(name, value, options) { cookieStore.set({ name, value, ...options }) },
remove(name, options) { cookieStore.set({ name, value: '', ...options }) },
},
}
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
return NextResponse.json({ user })
}
Nuxt SSR Pattern
// server/api/protected.get.ts (Nuxt 3)
import { serverSupabaseUser } from '#supabase/server'
export default defineEventHandler(async (event) => {
const user = await serverSupabaseUser(event)
if (!user) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}
return { user }
})
SvelteKit SSR Pattern
// src/routes/api/protected/+server.ts (SvelteKit)
import { createServerClient } from '@supabase/ssr'
import type { RequestHandler } from './$types'
export const GET: RequestHandler = async ({ cookies }) => {
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ cookies: { /* ... */ } }
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
return new Response(JSON.stringify({ user }))
}
Admin Operations trên Server
// Dùng service role key cho admin operations (backend only)
const supabaseAdmin = createClient(url, serviceRoleKey)
// Verify user từ JWT gửi từ client
const { data: user, error } = await supabaseAdmin.auth.admin.getUser(jwt)
// Decode JWT không verify (chỉ đọc claims, không kiểm tra chữ ký)
const claims = supabaseAdmin.auth.getClaims(jwt)
// { sub: '...', email: '...', role: 'authenticated', ... }
// List tất cả users với pagination
const { data: { users, total } } = await supabaseAdmin.auth.admin.listUsers({
page: 1,
perPage: 100
})
Authorization header và verify JWT trước khi thực hiện admin operations.Multi-Factor Authentication (MFA)
Hỗ trợ MFA với TOTP (Time-based One-Time Password) và Phone:
// Bắt đầu enroll MFA (TOTP)
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp',
issuer: 'My App',
friendlyName: 'user@example.com'
})
// Trả về QR code URL để user scan với Authenticator app
const { qr_code } = data.totp
// Challenge sau khi user đã enroll
const { data, error } = await supabase.auth.mfa.challenge({
factorId: factorId
})
// Verify TOTP code
const { data, error } = await supabase.auth.mfa.verify({
factorId: factorId,
code: '123456', // 6-digit code từ Authenticator app
challengeId: challengeId
})
// Unenroll
const { data, error } = await supabase.auth.mfa.unenroll({
factorId: factorId
})
// Lấy danh sách các factor đã enroll
const { data } = await supabase.auth.mfa.listFactors()
MFA Flow
- User login với email/password
- Server trả về
aal1session (chưa MFA) - Client gọi
mfa.challenge()vớifactorId - User nhập TOTP code từ Authenticator app
- Client gọi
mfa.verify()để xác nhận - Server trả về
aal2session (đã MFA, full access)
Auth Hooks
Customize hành vi Auth tại các điểm lifecycle bằng Postgres functions:
| Hook | Mục đích | Khi nào dùng |
|---|---|---|
| Custom Access Token | Thêm claims vào JWT access token | Thêm role, permissions, metadata vào JWT |
| Send SMS | Tùy chỉnh gửi SMS | Dùng provider SMS tùy chỉnh thay vì MessageBird/Twilio |
| Send Email | Tùy chỉnh gửi email | Dùng Resend, SendGrid, custom SMTP thay vì Supabase default |
| MFA Verification Attempt | Custom logic khi verify MFA | Rate limiting, logging, custom validation |
-- Custom Access Token Hook: thêm custom claims vào JWT
-- Hook này được gọi mỗi khi JWT được tạo hoặc refresh
CREATE OR REPLACE FUNCTION public.custom_access_token_hook(event jsonb)
RETURNS jsonb AS $$
DECLARE
claims jsonb := event->'claims';
user_data jsonb;
BEGIN
-- Lấy thông tin từ bảng profiles
SELECT jsonb_build_object(
'plan', plan,
'role', role,
'tenant_id', tenant_id
)
INTO user_data
FROM public.profiles
WHERE user_id = (event->>'user_id')::uuid;
-- Merge thông tin vào claims
-- user_metadata sẽ xuất hiện trong JWT và có thể dùng trong RLS policy
claims := claims || jsonb_build_object('user_metadata', user_data);
-- Set lại claims trong event
RETURN jsonb_set(event, '{claims}', claims);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Sau đó enable hook trong Dashboard > Authentication > Hooks
-- Chọn "Custom Access Token" và chọn function public.custom_access_token_hook
-- Send Email Hook: tùy chỉnh gửi email qua Resend/SendGrid
CREATE OR REPLACE FUNCTION public.custom_send_email_hook(event jsonb)
RETURNS jsonb AS $$
DECLARE
response jsonb;
BEGIN
-- Gọi external email API (Resend, SendGrid, v.v.)
SELECT content::jsonb INTO response
FROM http((
'POST',
'https://api.resend.com/emails',
ARRAY[http_header('Authorization', 'Bearer re_xxxxx')],
'application/json',
jsonb_build_object(
'from', 'My App <noreply@myapp.com>',
'to', ARRAY[event->>'email'],
'subject', event->>'email_action_type',
'html', event->>'redirect_to'
)::text
));
RETURN event;
END;
$$ LANGUAGE plpgsql;
plan, role, tenant_id) vào JWT mà không cần tự tạo JWT server riêng. Claims được thêm vào sẽ có sẵn trong RLS policies qua auth.jwt(). Hook chạy trong Postgres với SECURITY DEFINER để có quyền đọc bảng profiles.Auth UI Components
Supabase cung cấp pre-built UI components:
npm install @supabase/supabase-js @supabase/ssr
# Auth UI (React)
npm install @supabase/auth-ui-react @supabase/auth-ui-shared
# Auth UI (Vue)
npm install @supabase/auth-ui-vue
<!-- Vue example -->
<script setup>
import { Auth } from '@supabase/auth-ui-vue'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(url, anonKey)
</script>
<template>
<Auth :supabase-client="supabase" />
</template>
Component <Auth /> tự động bao gồm: Sign Up, Sign In, Magic Link, OAuth buttons, password reset, update password.
User Management (Admin)
Dùng service role key để quản lý users từ backend:
// Khởi tạo admin client (backend only)
const supabaseAdmin = createClient(url, serviceRoleKey)
// Lấy user theo ID
const { data, error } = await supabaseAdmin.auth.admin.getUserById(userId)
// Liệt kê tất cả users
const { data, error } = await supabaseAdmin.auth.admin.listUsers({
page: 1,
perPage: 100
})
// Tạo user (không gửi email xác nhận)
const { data, error } = await supabaseAdmin.auth.admin.createUser({
email: 'user@example.com',
password: 'password123',
email_confirm: true, // Bypass email verification
user_metadata: { full_name: 'Nguyen Van A' }
})
// Cập nhật user
const { data, error } = await supabaseAdmin.auth.admin.updateUserById(userId, {
email: 'newemail@example.com',
user_metadata: { role: 'admin' }
})
// Xóa user
const { data, error } = await supabaseAdmin.auth.admin.deleteUser(userId)
Pricing Auth
Tính phí theo Monthly Active Users (MAU):
| Plan | MAU | Giá vượt |
|---|---|---|
| Free | 50,000 MAU | — |
| Pro | 100,000 MAU | $0.00325/MAU |
- Third-Party MAU (OAuth, phone auth): tính phí riêng
- SSO MAU (SAML): tính phí riêng
- Advanced MFA (Phone): add-on tính phí
Password Security — Chi tiết
Rate Limits
Supabase áp dụng rate limiting để chống brute-force attacks:
| Hành động | Giới hạn | Time window |
|---|---|---|
| Login attempts (email/password) | 5 lần | Mỗi phút, mỗi email |
| Signup attempts | 5 lần | Mỗi giờ, mỗi email |
| OTP requests (email/phone) | 5 lần | Mỗi phút, mỗi email/phone |
| Password reset requests | 5 lần | Mỗi giờ, mỗi email |
| Token refresh | Không giới hạn cứng | Rate limit nhẹ |
| Overall auth requests | 30 req/s | Mỗi IP |
429 Too Many Requests. Đối với Pro/Team plan, có thể liên hệ support để điều chỉnh.Password Requirements
- Độ dài tối thiểu: 6 ký tự (mặc định)
- Không có yêu cầu về uppercase, lowercase, số, hoặc ký tự đặc biệt theo mặc định
- Có thể tăng requirements qua Custom Auth Hook hoặc validate ở client trước khi gửi
- Mật khẩu được hash bằng bcrypt/scrypt (không lưu plaintext)
- Có thể cấu hình password policy chi tiết hơn trong Dashboard > Authentication > Settings
Bot Detection (CAPTCHA)
| CAPTCHA Provider | Đặc điểm | Cấu hình trong Dashboard |
|---|---|---|
| hCaptcha | Cần site key + secret key, hoạt động như reCAPTCHA | Authentication > Settings > CAPTCHA |
| Cloudflare Turnstile | Miễn phí, bảo vệ privacy, không tracking user | Authentication > Settings > CAPTCHA |
// Khi enable CAPTCHA, client SDK tự động lấy token
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'password123',
options: {
captchaToken: token // Từ Turnstile/hCaptcha widget
}
})
Audit Logs
-- Xem audit logs (Team plan trở lên)
SELECT
created_at,
ip_address,
payload->>'action' as action,
payload->>'actor_username' as user_email,
payload->>'traits' as details
FROM auth.audit_log_entries
WHERE created_at > now() - interval '7 days'
ORDER BY created_at DESC
LIMIT 100;
-- Các action được log:
-- 'user_signedup', 'user_confirmed', 'user_repeated_signup',
-- 'login', 'token_refreshed', 'user_modified',
-- 'user_deleted', 'user_recovery_requested'
⭐ Interview Callouts
auth.uid() để lấy user ID từ JWT — đây là cách Auth và Database tích hợp với nhau. Luôn bật RLS trước khi expose dữ liệu.sub (user ID), role (authenticated/anon), và email. Bạn có thể thêm custom claims qua Custom Access Token Hook. Khi token hết hạn, refresh token được dùng để lấy access token mới. Service role key bypass RLS — chỉ dùng trong Edge Functions hoặc backend.linkIdentity() để thêm provider, unlinkIdentity() để gỡ, getUserIdentities() để xem danh sách. Khi login bằng bất kỳ identity nào đã link, user vào cùng một account. Không thể unlink identity cuối cùng.createServerClient từ @supabase/ssr để verify user ở server. Service role key cho admin operations — tuyệt đối không expose ra client. Pattern phổ biến: client gửi JWT trong Authorization header -> server dùng supabase.auth.admin.getUser(jwt) để verify.Bài tập
- Setup Supabase project với email verification bắt buộc
- Tạo Nuxt/Vue app với trang Sign Up (
signUp), Sign In (signInWithPassword), Dashboard - Implement flow:
- User đăng ký -> nhận email verification
- Verify email -> redirect về app
- User đăng nhập -> nhận JWT session
- Dashboard hiển thị user info từ
getUser() - Auto-refresh session khi access token hết hạn
- Sign out và redirect về trang login
- Theo dõi
onAuthStateChangeđể sync UI với auth state - Middleware bảo vệ route: redirect về login nếu chưa đăng nhập
- Cấu hình Google OAuth provider trong Supabase Dashboard
- Tạo nút "Sign in with Google" trong app
- Implement Magic Link như alternative login method
- Sau khi OAuth login, tạo record trong bảng
public.profiles(dùng Database Trigger) - Xử lý case: user dùng Google OAuth và Magic Link với cùng một email
- Bonus: Thêm GitHub OAuth provider
- Enable MFA trong Supabase project
- Tạo flow enroll MFA: user scan QR code với Google Authenticator
- Tạo flow challenge: sau khi login password, yêu cầu TOTP code
- Verify TOTP code và lấy
aal2session - Xử lý recovery codes khi user mất Authenticator app
- Bonus: Gửi SMS OTP như MFA factor thứ hai
- Tạo bảng
public.rolesvớiuser_idvàrole_name(admin, editor, viewer) - Viết
custom_access_token_hookPostgres function để thêmrolevào JWT claims - Enable hook trong Dashboard
- Viết RLS policy dùng claim từ JWT: admin được edit tất cả posts, editor được edit posts của họ, viewer chỉ được read
- Test: login với user có role khác nhau, kiểm tra quyền truy cập