Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Log Auth Errors in Supabase

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.

What you'll learn

  • How to capture and store client-side auth errors in a logging table
  • How to create a server-side trigger to log auth events automatically
  • How to monitor failed login attempts for security
  • How to query auth error logs for debugging and analysis
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1-- Create the auth error logs table
2CREATE 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);
14
15-- Enable RLS
16ALTER TABLE public.auth_error_logs ENABLE ROW LEVEL SECURITY;
17
18-- Allow authenticated admins to read logs
19CREATE POLICY "Admins can read auth error logs"
20ON public.auth_error_logs FOR SELECT
21TO authenticated
22USING (
23 (SELECT auth.jwt() ->> 'role') = 'admin'
24);
25
26-- Allow inserts via Edge Function (service role bypasses RLS)
27-- No INSERT policy needed when using service_role key

Expected result: The auth_error_logs table is created with RLS enabled. Only admin users can read logs, and inserts happen through server-side code.

2

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.

typescript
1// supabase/functions/log-auth-error/index.ts
2import { corsHeaders } from '../_shared/cors.ts';
3import { createClient } from 'npm:@supabase/supabase-js@2';
4
5Deno.serve(async (req) => {
6 if (req.method === 'OPTIONS') {
7 return new Response('ok', { headers: corsHeaders });
8 }
9
10 try {
11 const { error_type, error_message, error_code, user_email, metadata } =
12 await req.json();
13
14 // Create client with service_role to bypass RLS
15 const supabase = createClient(
16 Deno.env.get('SUPABASE_URL')!,
17 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
18 );
19
20 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 });
28
29 if (error) throw error;
30
31 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.

3

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.

typescript
1import { createClient } from '@supabase/supabase-js';
2
3const supabase = createClient(
4 process.env.NEXT_PUBLIC_SUPABASE_URL!,
5 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
6);
7
8async function logAuthError(
9 errorType: string,
10 errorMessage: string,
11 errorCode?: string,
12 email?: string
13) {
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}
28
29// Wrap sign-in with error logging
30async function signIn(email: string, password: string) {
31 const { data, error } = await supabase.auth.signInWithPassword({
32 email,
33 password,
34 });
35
36 if (error) {
37 await logAuthError('sign_in_failed', error.message, error.status?.toString(), email);
38 throw error;
39 }
40
41 return data;
42}

Expected result: Failed auth attempts are automatically logged to the database via the Edge Function.

4

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.

typescript
1// Set up global auth event monitoring
2supabase.auth.onAuthStateChange(async (event, session) => {
3 // Log specific events that indicate problems
4 if (event === 'TOKEN_REFRESHED' && !session) {
5 await logAuthError(
6 'token_refresh_failed',
7 'Session token refresh returned no session',
8 undefined,
9 undefined
10 );
11 }
12
13 if (event === 'SIGNED_OUT') {
14 // Could be intentional or forced — log for audit trail
15 console.log('User signed out');
16 }
17
18 // Log all events for audit purposes
19 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.

5

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.

typescript
1-- Find the most common auth errors in the last 24 hours
2SELECT error_type, error_message, COUNT(*) as occurrences
3FROM auth_error_logs
4WHERE created_at > now() - INTERVAL '24 hours'
5GROUP BY error_type, error_message
6ORDER BY occurrences DESC;
7
8-- 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_attempt
12FROM auth_error_logs
13WHERE error_type = 'sign_in_failed'
14 AND created_at > now() - INTERVAL '1 hour'
15GROUP BY user_email
16HAVING COUNT(*) >= 5
17ORDER BY failed_attempts DESC;
18
19-- Recent errors with full details
20SELECT error_type, error_message, user_email, user_agent, created_at
21FROM auth_error_logs
22ORDER BY created_at DESC
23LIMIT 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

auth-error-logger.ts
1// Auth error logging utility for Supabase
2// Captures and stores auth errors for debugging and security monitoring
3
4import { createClient, AuthError } from '@supabase/supabase-js';
5
6const supabase = createClient(
7 process.env.NEXT_PUBLIC_SUPABASE_URL!,
8 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
9);
10
11interface AuthErrorLog {
12 error_type: string;
13 error_message: string;
14 error_code?: string;
15 user_email?: string;
16 metadata?: Record<string, unknown>;
17}
18
19async 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}
35
36// Wrapped auth methods with automatic error logging
37export 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}
50
51export 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}
64
65export 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}
75
76// Initialize global auth event monitoring
77export 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.

ChatGPT Prompt

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.

Supabase Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.