This error occurs when a user with an active ban attempts to authenticate or refresh their session in Supabase. Banned users cannot log in or refresh tokens until their ban expires or is lifted.
The "user_banned" error indicates that the user account has been administratively banned using Supabase's Auth Admin API. When a user is banned, their `banned_until` field in the auth.users table is set to a future timestamp, preventing them from authenticating or refreshing their session tokens. Banning is a moderation feature that allows administrators to temporarily or long-term restrict user access without deleting their account. This is particularly useful for preventing abusive users, enforcing terms of service violations, or implementing temporary suspensions. When a banned user attempts to sign in or their client tries to refresh an authentication token, Supabase Auth returns a 400 status code with the error code "user_banned", immediately terminating the authentication flow. The user's existing session becomes invalid and cannot be renewed until the ban is lifted.
Check the user's ban status in the Supabase dashboard or via SQL query:
-- Query the auth.users table
SELECT id, email, banned_until
FROM auth.users
WHERE email = '[email protected]';If banned_until contains a timestamp in the future, the user is currently banned. If it's NULL or in the past, the ban should have expired.
You can also check via the Admin API:
const { data: user, error } = await supabase.auth.admin.getUserById(userId);
console.log('Banned until:', user.banned_until);If the timestamp is in the future, the ban is still active.
If the user should no longer be banned, remove the ban using the Admin API:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE_KEY // Use service role key
);
// Lift the ban by setting ban_duration to "none"
const { data, error } = await supabase.auth.admin.updateUserById(
userId,
{ ban_duration: 'none' }
);
if (error) {
console.error('Failed to lift ban:', error);
} else {
console.log('Ban lifted successfully for user:', userId);
}After lifting the ban, the user should be able to log in immediately.
Add client-side error handling to detect and display the ban status to users:
const { data, error } = await supabase.auth.signInWithPassword({
email: email,
password: password
});
if (error) {
if (error.message?.includes('user_banned') || error.code === 'user_banned') {
// Display user-friendly ban message
alert('Your account has been suspended. Please contact support.');
// Optionally, redirect to a ban explanation page
window.location.href = '/account-suspended';
} else {
// Handle other authentication errors
console.error('Login failed:', error.message);
}
}This provides a better user experience by clearly communicating the ban status.
When banning users, use appropriate time units for the ban duration:
// Valid time units: "ns", "us" (or "ยตs"), "ms", "s", "m", "h"
// Example: Ban for 24 hours
const { data, error } = await supabase.auth.admin.updateUserById(
userId,
{ ban_duration: '24h' }
);
// Example: Ban for 7 days
const { data, error } = await supabase.auth.admin.updateUserById(
userId,
{ ban_duration: '168h' } // 7 days * 24 hours
);
// Example: Ban for 30 days
const { data, error } = await supabase.auth.admin.updateUserById(
userId,
{ ban_duration: '720h' } // 30 days * 24 hours
);
// Example: Long-term ban (100 years - effectively permanent)
const { data, error } = await supabase.auth.admin.updateUserById(
userId,
{ ban_duration: '876000h' }
);The ban duration is added to the current timestamp to calculate banned_until.
For server-side rendered applications, implement middleware to check ban status:
// Next.js middleware example
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs';
import { NextResponse } from 'next/server';
export async function middleware(req) {
const res = NextResponse.next();
const supabase = createMiddlewareClient({ req, res });
const { data: { session } } = await supabase.auth.getSession();
if (session?.user) {
// Fetch user details to check banned_until
const { data: user } = await supabase.auth.admin.getUserById(session.user.id);
if (user?.banned_until && new Date(user.banned_until) > new Date()) {
// User is banned, sign them out and redirect
await supabase.auth.signOut();
return NextResponse.redirect(new URL('/account-banned', req.url));
}
}
return res;
}This prevents banned users from accessing protected routes even if they have cached credentials.
Ban Duration Format: The ban duration follows Go's time.Duration format with strict parsing. Valid units are "ns" (nanoseconds), "us" or "ยตs" (microseconds), "ms" (milliseconds), "s" (seconds), "m" (minutes), and "h" (hours). The duration is a sequence of decimal numbers, each with optional fraction and unit suffix. Examples: "300ms", "1.5h", "2h45m".
Ban vs. Delete: Banning preserves the user's data and allows for account reinstatement. Deleting a user is permanent and removes all associated data. Use banning for temporary suspensions or when you want to retain data for audit purposes.
Error Code Inconsistencies: There have been reported issues where banned users sometimes receive "Invalid login credentials" instead of the proper "user_banned" error code. This is a known limitation in some Supabase Auth versions. Always check the banned_until field directly when debugging authentication issues.
Automatic Unban: Supabase Auth automatically allows login once the banned_until timestamp has passed. You don't need to manually lift the ban after the duration expires. However, there's no automatic notification system, so consider implementing a scheduled job to notify users when their ban expires.
Service Role Key Required: Only the service role key (not the anon key) can ban and unban users via auth.admin.updateUserById(). Never expose the service role key to client-side code. All ban/unban operations must happen server-side or in secure functions.
Client-Side Validation: The banned_until field can be checked client-side to proactively prevent banned users from attempting actions, but this should not be relied upon for security. Always enforce ban checks server-side, as client-side checks can be bypassed.
Ban During Sign-Up: When creating users with auth.admin.createUser(), there have been reports that the ban_duration field may not persist correctly. If you need to create a user with an immediate ban, consider creating the user first, then updating them with the ban_duration in a second operation.
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists