Supabase returns 'unexpected_failure: Unexpected service failure' (HTTP 500) when the Auth service encounters a problem during user operations, usually caused by broken database triggers, foreign key constraint violations, permission errors on custom tables, or insufficient project resources. The fix involves identifying the database issue through logs, fixing triggers and constraints, and ensuring proper security permissions.
The "unexpected_failure" error indicates that the Supabase Auth service encountered an internal error while performing an operation on your database. Unlike user-facing errors (like invalid credentials), this 500-level error means the Auth API could not complete the operation because something went wrong in your database layer. Common scenarios include: - A custom trigger on the `auth.users` table or related tables failing silently - Foreign key constraints preventing user creation, deletion, or updates - Permission errors when a trigger tries to access tables outside the auth schema - Malformed database functions or views that the auth system depends on - Connection pool exhaustion or resource limitations on your project Since Supabase Auth relies on your database to store users, any issue in that layer surfaces as an "unexpected_failure" to the client. The error message rarely provides specifics, so diagnosis requires checking database logs and reviewing your custom schema modifications.
Open the Supabase Dashboard, navigate to Auth → Logs (or Monitoring → Logs), and set the time range to capture the error:
1. In the Supabase console, go to Logs or Log Explorer
2. Set the time range to include when the error occurred
3. Search for entries related to the failed auth operation (signUp, signIn, resetPassword, etc.)
4. Look for underlying database errors such as:
- "permission denied for table X"
- "violates foreign key constraint"
- "function "X" does not exist"
- "relation "X" does not exist"
These logs contain the actual database error that triggered the "unexpected_failure" response.
In the Supabase Dashboard, navigate to Database → Triggers and review any custom triggers on the auth schema or tables that interact with auth.users:
SELECT event_object_table, trigger_name, action_statement
FROM information_schema.triggers
WHERE trigger_schema = 'auth'
ORDER BY event_object_table;Common trigger issues:
- Trigger function references a table that does not exist
- Trigger tries to insert into a table without the supabase_auth_admin role having permission
- Trigger has uncaught error handling
For each problematic trigger:
-- Temporarily disable the trigger to confirm it's the cause
ALTER TABLE <table_name> DISABLE TRIGGER <trigger_name>;
-- Test the auth operation (signUp, signIn, etc.)
-- If it works, the trigger is the issue. Recreate it with SECURITY DEFINER:
CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
-- Your function logic here
RETURN NEW;
END;
$$ LANGUAGE plpgsql;The SECURITY DEFINER clause ensures the function runs with the privileges of the user who created it (usually postgres), not the calling role.
If logs show "violates foreign key constraint", you likely have a foreign key between auth.users and a custom table:
SELECT constraint_name, table_name, column_name
FROM information_schema.key_column_usage
WHERE table_name = 'users' AND table_schema = 'auth';The issue arises when:
- A user tries to delete their profile but a foreign key prevents it
- Supabase tries to clean up user data and the custom table blocks it
To fix, either:
1. Use CASCADE DELETE (automatic cleanup):
ALTER TABLE public.profiles
DROP CONSTRAINT <fk_constraint_name>;
ALTER TABLE public.profiles
ADD CONSTRAINT <fk_constraint_name>
FOREIGN KEY (user_id)
REFERENCES auth.users(id) ON DELETE CASCADE;2. Use SET NULL (soft delete):
ALTER TABLE public.profiles
DROP CONSTRAINT <fk_constraint_name>;
ALTER TABLE public.profiles
ADD CONSTRAINT <fk_constraint_name>
FOREIGN KEY (user_id)
REFERENCES auth.users(id) ON DELETE SET NULL;Choose CASCADE if users should be completely removed, or SET NULL if you want to keep the profile record but orphan it.
The supabase_auth_admin role (used by Auth internally) has limited permissions by design. If your trigger or function tries to access a table outside the auth schema, it will fail:
-- Check what supabase_auth_admin can access
SELECT grantee, privilege_type, table_name
FROM information_schema.role_table_grants
WHERE grantee = 'supabase_auth_admin'
ORDER BY table_name;If the auth role lacks permission to a table your trigger needs:
-- Grant SELECT, INSERT, UPDATE, DELETE on public.profiles to auth admin
GRANT SELECT, INSERT, UPDATE, DELETE ON public.profiles TO supabase_auth_admin;
-- Or use a SECURITY DEFINER function (postgres runs it with full privileges)
CREATE OR REPLACE FUNCTION my_trigger_function()
RETURNS TRIGGER
SECURITY DEFINER
AS $$
BEGIN
-- This runs as postgres, so it has access to all tables
UPDATE public.profiles SET updated_at = now() WHERE user_id = NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;Prefer SECURITY DEFINER for auth-related triggers so they always have the necessary permissions.
Invalid email templates can also cause "unexpected_failure" errors during password resets or magic link flows.
In the Supabase Dashboard:
1. Go to Auth → Email Templates
2. Review each template for:
- Unclosed HTML tags (e.g., missing </a>, </div>)
- Invalid variable syntax (should be {{ variable }}, not {variable})
- Forbidden characters (single quotes, unescaped backslashes)
Test a simplified template first:
<h1>Confirm your email</h1>
<p><a href="{{ .ConfirmationURL }}">Confirm Email</a></p>If the simplified version works, gradually add back template logic and test each change.
If logs don't show database errors and triggers/templates are correct, the issue may be resource exhaustion:
1. In the Supabase Dashboard, go to Database → Monitoring or Project Settings → Compute
2. Check for:
- High connection count (approaching the connection pool limit)
- CPU or memory usage spikes during auth operations
- "too many connections" errors in the database logs
If you see resource constraints, upgrade:
- Connection pooler settings: Increase pool size in Project Settings → Database → Connection Pooling
- Compute add-on: Add more RAM/CPU to your project from Billing → Compute
Restart the Auth service after resource upgrades to clear any stuck connections.
### Why "unexpected_failure" is vague
Supabase Auth wraps database errors in a generic 500 response to avoid leaking implementation details to clients. This security measure means error messages are intentionally opaque on the frontend. Always check server-side logs to diagnose the real issue.
### SECURITY DEFINER vs SECURITY INVOKER
Functions created with SECURITY INVOKER run with the privileges of the role calling them (often supabase_auth_admin, which has limited permissions). Functions with SECURITY DEFINER run with the privileges of the function's owner (usually postgres, with full permissions).
For auth-related triggers and functions, SECURITY DEFINER is recommended because it avoids permission issues when Auth tries to modify your custom tables.
### Transaction isolation and prepared statements
Some auth operations involve multiple database statements in a single transaction. If one statement fails or the transaction is aborted, subsequent operations in that transaction also fail. This can surface as "unexpected_failure" if the connection is not properly cleaned up.
Review transaction logs to see if operations are being rolled back due to constraint violations or isolation conflicts.
### RLS policies and auth
Supabase Row-Level Security (RLS) policies can also trigger errors if they're too restrictive on the auth schema. Ensure that auth-related operations bypass RLS by using the service role or by crafting policies that allow the auth system to modify user rows.
email_address_not_authorized: Email sending to this address is not authorized
Email address not authorized for sending in Supabase Auth
reauthentication_needed: Reauthentication required for security-sensitive actions
Reauthentication required for security-sensitive actions
no_authorization: No authorization header was provided
How to fix "no authorization header was provided" in Supabase
otp_expired: OTP has expired
How to fix 'otp_expired: OTP has expired' in Supabase
bad_oauth_state: OAuth state parameter is missing or invalid
How to fix 'bad_oauth_state: OAuth state parameter missing' in Supabase