Supabase
Why is my Supabase login not working (400 Bad Request)?
A 400 from signInWithPassword means your app reached Supabase Auth and the sign-in was rejected. Read error.code. invalid_credentials means no matching email and password in this project, and email_not_confirmed means the password is right but the email hasn't been confirmed yet.
Updated October 31, 2026
A login that won’t work is stressful, especially when real users are the ones locked out. The good news is that a “400 Bad Request” means your app reached Supabase Auth and got a specific reason back, and that reason is already in the error object your app receives.
Why it happens
The error object has an HTTP status in error.status and a code that says why, and the ones below come back as 400:
invalid_credentials(“Invalid login credentials”) covers three cases: there’s no user with that email in this project, the password is wrong, or the user has no password because they signed up another way, such as with Google or a magic link. All three get the same code, so your app can’t tell them apart either.email_not_confirmed(“Email not confirmed”) means the password is right, but email confirmation is turned on and the user hasn’t clicked the link in their confirmation email. On hosted Supabase projects, confirmation is on by default.user_bannedmeans the user has an active ban.validation_failedmeans the request was missing the email or phone.
A few other failures use different statuses. Too many requests from one IP address gets a 429 with over_request_rate_limit, and email sign-in turned off for the project gets a 422 with email_provider_disabled.
How to fix it
-
Log the code so you know which case you’re in:
const { data, error } = await supabase.auth.signInWithPassword({ email: email.trim(), password, }); if (error) { console.log(error.status, error.code, error.message); } -
For
invalid_credentials, check the project first. Under Authentication in your Supabase dashboard, open Users and look for the email. Then check that the URL and publishable key in your app come from that same project. If your app points at a different project, such as a test project instead of your live one, every sign-in fails withinvalid_credentials, because the user doesn’t exist there. Also trim the email and setautoCapitalize="none"andkeyboardType="email-address"on the email field, so a stray space or capital letter doesn’t sneak in. -
For
email_not_confirmed, ask the user to open the confirmation email, and give them a way to send it again:await supabase.auth.resend({ type: "signup", email });You can turn confirmation off on the Auth Providers page while you test. We’d keep it on in a live app, since without it anyone can sign up with an email address they don’t own.
-
For users who signed up with Google or a magic link, have them sign in the same way, or let them set a password with a password reset flow.
-
For a 429, show a “try again in a few minutes” message rather than retrying in a loop, which only uses up more of the limit.
How you know it’s fixed
When you sign in with a test user on a phone, data.session has a value and error is null. Close and reopen the app, and the user is still signed in.
If it still fails
If error.status is 0, the request never got a response from Supabase Auth. Check the network and the project URL, and use isAuthApiError(error) from @supabase/supabase-js to tell server errors apart from errors raised in the app itself. On older React Native versions, a missing URL polyfill can also break the client before it signs anyone in, which is covered in what react-native-url-polyfill does.
Ask the agent
If your app is in Draftbit, paste this into the agent, and it can read your code and make the change for you.
Our Supabase login fails with a 400 error. Log error.code and error.message from signInWithPassword, show a clear message for a wrong password, an unconfirmed email, and too many attempts, and check that the app uses the same Supabase project as the dashboard where our users are.