supabase auth
Authentication and authorization for web applications
$ npx docs2skills add supabase-authSupabase Auth
Authentication and authorization for web applications
What this skill does
Supabase Auth provides a complete authentication system built on PostgreSQL with JWT-based sessions. It handles user registration, login, password management, social OAuth, magic links, OTP verification, and multi-factor authentication. The system integrates deeply with Supabase's database layer, automatically generating REST APIs with Row Level Security (RLS) policies that scope data access based on the authenticated user's JWT claims.
Unlike standalone auth services, Supabase Auth stores user data directly in your PostgreSQL database in a special auth schema, allowing you to reference user records with foreign keys and trigger functions. The JWT tokens contain user metadata and custom claims that your database RLS policies can inspect, enabling sophisticated authorization patterns without round trips to external services.
Prerequisites
- Supabase project with database and Auth enabled
- Node.js 14+ for client SDKs
- Email provider (built-in or custom SMTP) for email-based auth
- SMS provider (Twilio, MessageBird, or Vonage) for phone auth
- OAuth app credentials for social providers
- HTTPS domain for production redirects
Quick start
npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
)
// Sign up with email/password
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'password123',
options: {
data: {
full_name: 'John Doe'
}
}
})
// Sign in
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password123'
})
// Get current user
const { data: { user } } = await supabase.auth.getUser()
// Listen to auth state changes
supabase.auth.onAuthStateChange((event, session) => {
console.log(event, session)
})
Core concepts
Sessions and JWTs: Supabase Auth uses JSON Web Tokens for stateless authentication. Each user session contains an access token (JWT) that's automatically included in database requests. The JWT contains user ID, email, metadata, and custom claims that RLS policies can access.
User Identity vs Authentication: A user can have multiple identities (email, phone, OAuth providers) but represents a single account. Identity linking allows users to sign in with different methods while maintaining a unified profile.
Row Level Security Integration: The auth JWT is automatically passed to PostgreSQL as auth.jwt() and auth.uid() functions. RLS policies use these to filter data on a per-row basis, ensuring users only access authorized records.
Email Confirmation Flow: New users receive confirmation emails before activation. This can be disabled for development but should remain enabled in production to prevent abuse and ensure deliverability.
Redirect URLs: After authentication actions (login, signup, password reset), users are redirected to configurable URLs. These must be added to your allowed redirect list in the Supabase dashboard.
Key API surface
| Method | Description |
|---|---|
signUp(credentials) | Register new user with email/password or phone |
signInWithPassword(credentials) | Sign in with email/password |
signInWithOtp({ email/phone }) | Send magic link or SMS OTP |
signInWithOAuth({ provider }) | Initiate OAuth flow (Google, GitHub, etc.) |
verifyOtp({ email/phone, token }) | Verify OTP code |
getUser() | Get current authenticated user |
getSession() | Get current session with JWT tokens |
refreshSession() | Refresh expired access token |
updateUser(attributes) | Update user metadata or password |
resetPasswordForEmail(email) | Send password reset email |
signOut() | End current session |
onAuthStateChange(callback) | Listen for auth events |
mfa.enroll({ type }) | Enroll in multi-factor authentication |
mfa.verify({ factorId, code }) | Verify MFA challenge |
Common patterns
Protected routes with session check
// Check auth state on page load
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
})
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
}
)
return () => subscription.unsubscribe()
}, [])
Magic link authentication
// Send magic link
const { error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: 'https://yoursite.com/dashboard'
}
})
// Handle the callback (in your redirect page)
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_IN') {
router.push('/dashboard')
}
}
)
return () => subscription.unsubscribe()
}, [])
Social OAuth login
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://yoursite.com/auth/callback',
queryParams: {
access_type: 'offline',
prompt: 'consent',
}
}
})
Row Level Security policy
-- Enable RLS on your table
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- Policy: users can only see their own profile
CREATE POLICY "Users can view own profile"
ON profiles FOR SELECT
USING (auth.uid() = id);
-- Policy: users can update their own profile
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
USING (auth.uid() = id);
Server-side auth (Next.js)
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
export async function getServerSideProps({ req, res }) {
const supabase = createServerSupabaseClient({ req, res })
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
return { redirect: { destination: '/login', permanent: false } }
}
return { props: { session } }
}
Configuration
Site URL: Base URL for redirects (set in Supabase dashboard under Auth > URL Configuration)
Redirect URLs: Allowed callback URLs after authentication (comma-separated list)
JWT Settings: Access token lifetime (default 1 hour), refresh token lifetime (default 30 days)
Email Templates: Customize confirmation, reset, and magic link emails in Auth > Email Templates
Rate Limiting: API calls per hour limits (default varies by plan)
Providers: OAuth app credentials and scopes in Auth > Providers
SMTP: Custom email provider in Auth > Settings (optional, uses built-in by default)
Best practices
Always handle auth state changes: Use onAuthStateChange to update UI when users sign in/out, don't rely on local state alone.
Validate sessions server-side: For sensitive operations, verify JWTs on your backend using the project's JWT secret.
Use RLS policies: Don't rely solely on client-side filtering - implement proper database-level authorization with Row Level Security.
Store sensitive data in user_metadata: Use signUp({ options: { data: {} }}) for additional user fields that should be in JWT claims.
Implement proper error handling: Auth operations can fail due to network issues, rate limiting, or invalid credentials - always check the error object.
Set appropriate redirect URLs: Use different redirects for different flows (signup confirmation vs password reset).
Enable email confirmation in production: Prevents spam signups and ensures users own their email addresses.
Use refresh tokens properly: Access tokens expire after 1 hour - let the SDK handle refresh automatically or call refreshSession() manually.
Gotchas and common mistakes
- User not immediately available after signUp: User object is null until email confirmation. Check
data.userand handle the confirmation flow. - Session persists after signOut: Clear local storage manually if needed:
localStorage.removeItem('supabase.auth.token') - RLS policies block everything by default: Create explicit SELECT/INSERT/UPDATE/DELETE policies or nothing works.
- Auth state changes fire multiple times: Use proper cleanup in
onAuthStateChangelisteners to prevent memory leaks. - OAuth redirects fail locally: Use
http://localhost:3000not127.0.0.1in redirect URLs for local development. - Custom claims not in JWT: Use Auth Hooks or triggers to add claims - they don't appear automatically from user metadata.
- Rate limiting on auth endpoints: Implement proper error handling and retry logic for production apps.
- Session cookies not set: Use
@supabase/auth-helpersfor proper SSR cookie management in Next.js/SvelteKit. - Password reset links expire: Default 1 hour expiry - handle expired token errors gracefully.
- Phone auth requires verification: SMS OTP must be verified with
verifyOtp()- users aren't signed in after receiving the code. - MFA enrollment requires existing session: Users must be signed in before enrolling in multi-factor authentication.
- Identity linking overwrites metadata: When linking OAuth accounts, user_metadata from the OAuth provider may overwrite existing values.