To log auth errors in Supabase, capture error events from the client-side auth methods (signIn, signUp, signOut) and the onAuthStateChange listener, then store them in a custom auth_error_logs table. For server-side logging, create a database trigger on the auth.users table or use an Edge Function that monitors auth events. This gives you visibility into failed logins, expired tokens, and signup failures for debugging and security monitoring.
Capturing and Storing Auth Error Events in Supabase
Supabase Auth errors like failed logins, expired tokens, and signup failures are returned to the client but not logged anywhere by default. Without persistent logging, these errors disappear when the user closes their browser. This tutorial shows you how to build an auth error logging system that captures errors from both client-side and server-side, stores them in a database table, and lets you query patterns for debugging and security monitoring.
Prerequisites
- A Supabase project with authentication configured
- The Supabase JS client installed in your frontend
- Access to the Supabase Dashboard SQL Editor
- Basic understanding of Supabase Auth methods and RLS
Step-by-step guide
Create an auth error logging table
Create an auth error logging table
Start by creating a table to store auth errors. The table should capture the error type, message, user email (if available), the timestamp, and any additional context. Use the service_role key for inserts so that errors can be logged even when the user is not authenticated. Enable RLS but create a policy that allows the service role to bypass it.
1-- Create the auth error logs table2CREATE TABLE public.auth_error_logs (3 id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,4 error_type TEXT NOT NULL,5 error_message TEXT NOT NULL,6 error_code TEXT,7 user_email TEXT,8 user_id UUID,9 ip_address TEXT,10 user_agent TEXT,11 metadata JSONB DEFAULT '{}',12 created_at TIMESTAMPTZ DEFAULT now()13);1415-- Enable RLS16ALTER TABLE public.auth_error_logs ENABLE ROW LEVEL SECURITY;1718-- Allow authenticated admins to read logs19CREATE POLICY "Admins can read auth error logs"20ON public.auth_error_logs FOR SELECT21TO authenticated22USING (23 (SELECT auth.jwt() ->> 'role') = 'admin'24);2526-- Allow inserts via Edge Function (service role bypasses RLS)27-- No INSERT policy needed when using service_role keyExpected result: The auth_error_logs table is created with RLS enabled. Only admin users can read logs, and inserts happen through server-side code.
Create an Edge Function for secure error logging
Create an Edge Function for secure error logging
Build an Edge Function that receives auth error details and inserts them into the logging table using the service_role key. This is more secure than inserting directly from the client because it prevents users from spoofing log entries. The function validates the input, adds server-side metadata like timestamps, and stores the record.
1// supabase/functions/log-auth-error/index.ts2import { corsHeaders } from '../_shared/cors.ts';3import { createClient } from 'npm:@supabase/supabase-js@2';45Deno.serve(async (req) => {6 if (req.method === 'OPTIONS') {7 return new Response('ok', { headers: corsHeaders });8 }910 try {11 const { error_type, error_message, error_code, user_email, metadata } =12 await req.json();1314 // Create client with service_role to bypass RLS15 const supabase = createClient(16 Deno.env.get('SUPABASE_URL')!,17 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!18 );1920 const { error } = await supabase.from('auth_error_logs').insert({21 error_type,22 error_message,23 error_code: error_code || null,24 user_email: user_email || null,25 user_agent: req.headers.get('user-agent'),26 metadata: metadata || {},27 });2829 if (error) throw error;3031 return new Response(32 JSON.stringify({ logged: true }),33 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }34 );35 } catch (err) {36 return new Response(37 JSON.stringify({ error: err.message }),38 { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 500 }39 );40 }41});Expected result: The Edge Function accepts error details via POST and stores them securely in the auth_error_logs table.
Capture client-side auth errors and send them to the logging function
Capture client-side auth errors and send them to the logging function
Wrap your auth calls in error-handling logic that sends failures to the logging Edge Function. Capture errors from signInWithPassword, signUp, signOut, and the onAuthStateChange listener. Include as much context as possible — the error message, code, attempted email, and the action that failed.
1import { createClient } from '@supabase/supabase-js';23const supabase = createClient(4 process.env.NEXT_PUBLIC_SUPABASE_URL!,5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!6);78async function logAuthError(9 errorType: string,10 errorMessage: string,11 errorCode?: string,12 email?: string13) {14 try {15 await supabase.functions.invoke('log-auth-error', {16 body: {17 error_type: errorType,18 error_message: errorMessage,19 error_code: errorCode,20 user_email: email,21 metadata: { page: window.location.pathname },22 },23 });24 } catch (e) {25 console.error('Failed to log auth error:', e);26 }27}2829// Wrap sign-in with error logging30async function signIn(email: string, password: string) {31 const { data, error } = await supabase.auth.signInWithPassword({32 email,33 password,34 });3536 if (error) {37 await logAuthError('sign_in_failed', error.message, error.status?.toString(), email);38 throw error;39 }4041 return data;42}Expected result: Failed auth attempts are automatically logged to the database via the Edge Function.
Monitor auth events with onAuthStateChange
Monitor auth events with onAuthStateChange
The onAuthStateChange listener fires for all auth events including TOKEN_REFRESHED and PASSWORD_RECOVERY. Listen for error events and log them. This catches errors that happen outside of explicit sign-in calls, such as token refresh failures when a session expires.
1// Set up global auth event monitoring2supabase.auth.onAuthStateChange(async (event, session) => {3 // Log specific events that indicate problems4 if (event === 'TOKEN_REFRESHED' && !session) {5 await logAuthError(6 'token_refresh_failed',7 'Session token refresh returned no session',8 undefined,9 undefined10 );11 }1213 if (event === 'SIGNED_OUT') {14 // Could be intentional or forced — log for audit trail15 console.log('User signed out');16 }1718 // Log all events for audit purposes19 console.log('Auth event:', event, session?.user?.email);20});Expected result: Auth events are monitored in real time, and errors like token refresh failures are logged automatically.
Query auth error logs for debugging and security analysis
Query auth error logs for debugging and security analysis
Use SQL queries to analyze your auth error logs. Look for patterns like repeated failed logins from the same email (potential brute force), spikes in signup failures (potential configuration issues), and token refresh errors (potential session management problems). Create a simple admin view to monitor these patterns.
1-- Find the most common auth errors in the last 24 hours2SELECT error_type, error_message, COUNT(*) as occurrences3FROM auth_error_logs4WHERE created_at > now() - INTERVAL '24 hours'5GROUP BY error_type, error_message6ORDER BY occurrences DESC;78-- Detect potential brute force attempts (5+ failures per email)9SELECT user_email, COUNT(*) as failed_attempts,10 MIN(created_at) as first_attempt,11 MAX(created_at) as last_attempt12FROM auth_error_logs13WHERE error_type = 'sign_in_failed'14 AND created_at > now() - INTERVAL '1 hour'15GROUP BY user_email16HAVING COUNT(*) >= 517ORDER BY failed_attempts DESC;1819-- Recent errors with full details20SELECT error_type, error_message, user_email, user_agent, created_at21FROM auth_error_logs22ORDER BY created_at DESC23LIMIT 50;Expected result: You can identify patterns in auth failures, detect potential security threats, and debug specific user issues from the logs.
Complete working example
1// Auth error logging utility for Supabase2// Captures and stores auth errors for debugging and security monitoring34import { createClient, AuthError } from '@supabase/supabase-js';56const supabase = createClient(7 process.env.NEXT_PUBLIC_SUPABASE_URL!,8 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!9);1011interface AuthErrorLog {12 error_type: string;13 error_message: string;14 error_code?: string;15 user_email?: string;16 metadata?: Record<string, unknown>;17}1819async function logAuthError(log: AuthErrorLog): Promise<void> {20 try {21 await supabase.functions.invoke('log-auth-error', {22 body: {23 ...log,24 metadata: {25 ...log.metadata,26 page: typeof window !== 'undefined' ? window.location.pathname : 'server',27 timestamp: new Date().toISOString(),28 },29 },30 });31 } catch (e) {32 console.error('Failed to log auth error:', e);33 }34}3536// Wrapped auth methods with automatic error logging37export async function signIn(email: string, password: string) {38 const { data, error } = await supabase.auth.signInWithPassword({ email, password });39 if (error) {40 await logAuthError({41 error_type: 'sign_in_failed',42 error_message: error.message,43 error_code: String(error.status || ''),44 user_email: email,45 });46 throw error;47 }48 return data;49}5051export async function signUp(email: string, password: string) {52 const { data, error } = await supabase.auth.signUp({ email, password });53 if (error) {54 await logAuthError({55 error_type: 'sign_up_failed',56 error_message: error.message,57 error_code: String(error.status || ''),58 user_email: email,59 });60 throw error;61 }62 return data;63}6465export async function signOut() {66 const { error } = await supabase.auth.signOut();67 if (error) {68 await logAuthError({69 error_type: 'sign_out_failed',70 error_message: error.message,71 });72 throw error;73 }74}7576// Initialize global auth event monitoring77export function initAuthMonitoring() {78 supabase.auth.onAuthStateChange(async (event, session) => {79 if (event === 'TOKEN_REFRESHED' && !session) {80 await logAuthError({81 error_type: 'token_refresh_failed',82 error_message: 'Token refresh returned no session',83 });84 }85 });86}Common mistakes when logging Auth Errors in Supabase
Why it's a problem: Creating a client-side INSERT policy on the auth_error_logs table, allowing users to write fake log entries
How to avoid: Route all log inserts through an Edge Function using the service_role key. Deploy the function with --no-verify-jwt so unauthenticated users' errors can be logged.
Why it's a problem: Logging passwords or sensitive session tokens in the error metadata
How to avoid: Only log error messages, error codes, email addresses, and non-sensitive context. Never include passwords, tokens, or API keys in log entries.
Why it's a problem: Not cleaning up old auth error logs, causing the table to grow indefinitely
How to avoid: Set up a pg_cron job to delete entries older than 90 days, or create a retention policy based on your compliance requirements.
Best practices
- Route all auth error log inserts through a server-side Edge Function to prevent spoofed entries
- Never log passwords or authentication tokens — only log error messages, codes, and email addresses
- Monitor for brute force patterns by querying failed login counts per email address per hour
- Set up automated cleanup of old log entries using pg_cron to manage table size
- Include page context in error logs to identify which part of your app generates the most auth errors
- Use the onAuthStateChange listener to catch errors that happen outside explicit auth calls
- Create a simple admin dashboard to review auth error patterns in real time
- Deploy the logging Edge Function with --no-verify-jwt to capture errors from unauthenticated users
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build an auth error logging system in Supabase that captures failed logins, signup errors, and token refresh failures. Show me how to create a logging table, an Edge Function for secure inserts, and client-side wrappers that automatically log auth errors.
Create a complete auth error monitoring system in Supabase with a logging table, an Edge Function for secure writes, and TypeScript client helpers that wrap signIn, signUp, and signOut with automatic error logging. Include SQL queries for detecting brute force attempts.
Frequently asked questions
Does Supabase log auth errors by default?
Supabase logs API requests in the Dashboard under Logs, but these logs are not persistent or easily queryable for auth-specific errors. Building a custom logging table gives you full control over retention, querying, and alerting.
Should I log auth errors on the client or the server?
Both. Client-side logging captures errors with user context like the page they were on. Server-side logging via Edge Functions is more secure and ensures errors are captured even if the client fails to send them.
How do I detect brute force login attempts from the logs?
Query the auth_error_logs table for emails with 5+ failed sign_in attempts within a short time window (e.g., 1 hour). Use this data to trigger account lockouts or CAPTCHA challenges.
Will logging auth errors slow down my application?
No. The logging call is asynchronous and non-blocking. Even if the Edge Function is slow or fails, the user's auth experience is not affected because the log call happens in the background.
How long should I retain auth error logs?
This depends on your compliance requirements. A typical retention period is 90 days for debugging purposes. Security-sensitive applications may require 1+ years. Use pg_cron to automate cleanup.
Can I get alerts when auth errors spike?
Yes. Create a pg_cron job that checks for error spikes periodically and sends a notification via a database webhook or Edge Function to your Slack or email when thresholds are exceeded.
Can RapidDev help build a security monitoring system for my Supabase project?
Yes. RapidDev can build comprehensive auth monitoring solutions including error logging, brute force detection, real-time alerting, and admin dashboards for your Supabase application.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation