Email/Password Authentication
Email Auth Flow
Signup → Confirm → Login
┌─────────────────────────────────────────────────────────────┐
│ EMAIL AUTH FLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. SIGNUP │
│ User ──▶ signUp(email, password) │
│ ◀── Confirmation email sent │
│ │
│ 2. CONFIRM EMAIL │
│ User ──▶ Clicks link in email │
│ ◀── Email confirmed, can login │
│ │
│ 3. LOGIN │
│ User ──▶ signInWithPassword(email, password) │
│ ◀── JWT tokens (access + refresh) │
│ │
│ 4. SESSION │
│ Client ──▶ Stores tokens │
│ ──▶ Auto-refresh khi hết hạn │
│ │
└─────────────────────────────────────────────────────────────┘
Sign Up
Basic Signup
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password-123',
});
if (error) {
console.error('Signup error:', error.message);
return;
}
// data.user exists but email not confirmed yet
// data.session is null until confirmed (nếu email confirmation enabled)
console.log('User created:', data.user?.id);
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password-123',
options: {
data: {
full_name: 'John Doe',
avatar_url: 'https://example.com/avatar.jpg',
role: 'member',
},
// Custom redirect sau khi confirm email
emailRedirectTo: 'https://myapp.com/welcome',
},
});
// Metadata được lưu trong auth.users.raw_user_meta_data
Email Confirmation
Enable/Disable trong Dashboard
Supabase Dashboard
└── Authentication
└── Providers
└── Email
├── Enable Email Signup: ON
├── Confirm Email: ON/OFF
└── Double Confirm Email Changes: ON
Custom Email Templates
Dashboard → Authentication → Email Templates
Templates available:
- Confirm signup
- Invite user
- Magic link
- Change email address
- Reset password
Email Template Variables
<!-- Available variables -->
{{ .ConfirmationURL }} - Link xác nhận
{{ .Email }} - Email người dùng
{{ .Token }} - Token (nếu cần)
{{ .TokenHash }} - Hashed token
{{ .SiteURL }} - Your app URL
Sign In
Basic Sign In
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'secure-password-123',
});
if (error) {
// Common errors:
// - Invalid login credentials
// - Email not confirmed
console.error('Login error:', error.message);
return;
}
// Success: data.session contains tokens
console.log('Logged in:', data.user?.email);
console.log('Access token:', data.session?.access_token);
Check Session sau Login
// Get current session
const { data: { session } } = await supabase.auth.getSession();
if (session) {
console.log('User:', session.user.email);
console.log('Expires at:', new Date(session.expires_at! * 1000));
}
Password Reset
Request Reset
const { error } = await supabase.auth.resetPasswordForEmail(
'user@example.com',
{
redirectTo: 'https://myapp.com/reset-password',
}
);
if (error) {
console.error('Error:', error.message);
} else {
console.log('Reset email sent');
}
Handle Reset Callback
// User clicks link → redirected to /reset-password?code=xxx
// In your reset password page
const { error } = await supabase.auth.updateUser({
password: 'new-secure-password',
});
if (error) {
console.error('Error updating password:', error.message);
} else {
console.log('Password updated successfully');
}
'use client';
import { useState } from 'react';
import { createClient } from '@/lib/supabase/client';
export function SignupForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState('');
const supabase = createClient();
const handleSignup = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${location.origin}/auth/callback`,
},
});
if (error) {
setMessage(error.message);
} else {
setMessage('Check your email to confirm your account!');
}
setLoading(false);
};
return (
<form onSubmit={handleSignup}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
minLength={6}
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Sign Up'}
</button>
{message && <p>{message}</p>}
</form>
);
}
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { createClient } from '@/lib/supabase/client';
export function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const router = useRouter();
const supabase = createClient();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
setError(error.message);
setLoading(false);
} else {
router.push('/dashboard');
router.refresh();
}
};
return (
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Login'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
Auth Callback Handler
Next.js Route Handler
// app/auth/callback/route.ts
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get('code');
const next = searchParams.get('next') ?? '/dashboard';
if (code) {
const supabase = await createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) {
return NextResponse.redirect(`${origin}${next}`);
}
}
// Return to error page
return NextResponse.redirect(`${origin}/auth/error`);
}
Password Validation
Client-side Validation
function validatePassword(password: string): string[] {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain a number');
}
return errors;
}
Supabase Password Settings
Dashboard → Authentication → Providers → Email
- Minimum Password Length: 8 (default: 6)
Best Practices
Security Checklist
✅ Enable email confirmation
✅ Set minimum password length (≥8)
✅ Use HTTPS everywhere
✅ Implement rate limiting
✅ Hash/salt on Supabase side (automatic)
✅ Secure password reset flow
✅ Don't leak user existence info
Error Handling
// Don't reveal if email exists
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
// Generic message to user
if (error) {
// Don't show: "User not found"
// Show: "Invalid email or password"
setError('Invalid email or password');
}
Tổng kết
Email Auth Methods
| Method |
Purpose |
signUp |
Create new account |
signInWithPassword |
Login với email/password |
resetPasswordForEmail |
Request password reset |
updateUser |
Update password |
signOut |
Logout |
Flow Summary
Signup → Confirm Email → Login → Use App → Logout
↓
Forgot Password → Reset → Login
Q&A
- Có custom email templates chưa?
- Yêu cầu password strength như thế nào?
- Đã implement password reset chưa?