Advanced Authentication Patterns
Supabase Auth supports far more than basic email/password login. For production applications, you need multi-factor authentication, custom roles via JWT claims, enterprise SSO, and secure server-side auth flows. This guide covers advanced patterns used in real SaaS products.
🚀 Advanced Auth Features
- MFA/TOTP: Multi-factor authentication with authenticator apps
- Custom Claims: Add roles and permissions to JWTs
- PKCE Flow: Secure auth for server-rendered applications
- SAML SSO: Enterprise single sign-on
Custom Claims & Roles via JWT
Add custom data to JWTs so RLS policies can check roles without extra database queries.
-- Create a function to add custom claims to the JWT
CREATE OR REPLACE FUNCTION custom_access_token_hook(event jsonb)
RETURNS jsonb
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
claims jsonb;
user_role text;
BEGIN
-- Get the user's role from your custom table
SELECT role INTO user_role
FROM user_roles
WHERE user_id = (event->>'user_id')::uuid;
claims := event->'claims';
IF user_role IS NOT NULL THEN
-- Add custom claims to the JWT
claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
ELSE
claims := jsonb_set(claims, '{user_role}', '"user"');
END IF;
-- Update the claims in the event
event := jsonb_set(event, '{claims}', claims);
RETURN event;
END;
$$;
-- Grant necessary permissions
GRANT USAGE ON SCHEMA public TO supabase_auth_admin;
GRANT EXECUTE ON FUNCTION custom_access_token_hook TO supabase_auth_admin;
-- Use the custom claim in RLS policies
CREATE POLICY "Admins can do everything"
ON posts FOR ALL
USING (
(auth.jwt()->>'user_role') = 'admin'
);
Multi-Factor Authentication (MFA)
// Step 1: Enroll a TOTP factor
async function enrollMFA() {
const { data, error } = await supabase.auth.mfa.enroll({
factorType: 'totp',
friendlyName: 'Authenticator App',
})
if (data) {
// Show QR code to user
console.log('Scan this QR code:', data.totp.qr_code)
// data.totp.uri — for manual entry
// data.id — factor ID needed for verification
return data
}
}
// Step 2: Verify and activate the factor
async function verifyMFA(factorId: string, code: string) {
const { data: challenge } = await supabase.auth.mfa.challenge({
factorId,
})
const { data, error } = await supabase.auth.mfa.verify({
factorId,
challengeId: challenge.id,
code, // 6-digit TOTP code from authenticator app
})
return data
}
// Step 3: Check MFA status on sign in
async function checkMFAStatus() {
const { data } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
if (data.currentLevel === 'aal1' && data.nextLevel === 'aal2') {
// User has MFA enrolled but hasn't verified yet
// Redirect to MFA verification page
return 'needs_verification'
}
return data.currentLevel // 'aal1' or 'aal2'
}
PKCE Flow for Server-Side Auth
// The PKCE flow is automatically used with @supabase/ssr
// It's more secure than the implicit flow for server-rendered apps
// In your OAuth sign-in handler:
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'http://localhost:3000/auth/callback',
// PKCE is used automatically when flowType is 'pkce'
},
})
}
// Auth callback 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 } = new URL(request.url)
const code = searchParams.get('code')
if (code) {
const supabase = await createClient()
// Exchange the code for a session
await supabase.auth.exchangeCodeForSession(code)
}
return NextResponse.redirect(new URL('/dashboard', request.url))
}
Enterprise SAML SSO
// Sign in with SAML SSO (for enterprise customers)
async function signInWithSSO(domain: string) {
const { data, error } = await supabase.auth.signInWithSSO({
domain, // e.g., 'company.com'
options: {
redirectTo: 'https://app.example.com/auth/callback',
},
})
if (data?.url) {
// Redirect to the IdP login page
window.location.href = data.url
}
}
// Account linking — connect multiple auth methods
// Users can sign in with email AND Google to the same account
// This is configured in Supabase Dashboard > Auth > Providers
⚠️ Security Best Practices
Always use the PKCE flow for server-rendered applications. Enable MFA for admin accounts.
Store refresh tokens securely using HTTP-only cookies (handled by @supabase/ssr).
Regularly rotate your JWT secret and API keys.
💡 Key Takeaways
- • Custom JWT claims eliminate extra queries in RLS policies
- • MFA with TOTP adds a critical security layer for sensitive apps
- • PKCE flow is the standard for server-rendered applications
- • SAML SSO enables enterprise authentication workflows
- • Use auth hooks to customize the JWT without modifying Supabase internals
📚 Learn More
-
Multi-Factor Authentication →
Official guide to implementing MFA with Supabase.
-
Enterprise SSO →
Set up SAML 2.0 single sign-on for enterprise customers.