Email and password sign-in

Sign users up and in with an email and password on SupaHost, confirm their address, set the minimum password length, and build a working password reset flow.

Email and password is on by default for every SupaHost project. A user signs up, confirms their address from an email Auth sends, and then signs in to get a session. Passwords are hashed with bcrypt by Auth and never stored in plain text.

Settings that apply

Dashboard → your project → Auth tab → Sign-in & security:

Setting Effect
Email sign-in Turns email and password, magic links and email OTP on or off together. On by default
Disable new sign-ups Existing users can still sign in, and you can still invite people. signUp returns an error
Skip email confirmation Users get a session straight from signUp without confirming. Handy in development, risky in production
Minimum password length 6 to 72 characters. Default 6

Click Save sign-in settings. The change reaches the project within a minute. The minimum length is the only password rule you can set on SupaHost; if you want more (a mix of character types, a blocklist), check it in your app before calling signUp.

Also set the Site URL on the Auth card, because confirmation and reset links fall back to it. See Site URL and redirect URLs.

Sign up

const { data, error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'plum-orchard-42-lantern',
  options: {
    emailRedirectTo: 'https://invoices.example.com/auth/callback',
    data: { company: 'Alex Design Ltd' },
  },
})

With confirmation on (the default), data.user is set but data.session is null. Auth sends the Confirm sign-up email. It contains a button with a link and, below it, a one-time code. Either works:

  • The link goes to /auth/v1/verify on your project, which confirms the address and redirects to emailRedirectTo (or the Site URL) with a session.
  • The code lets the user confirm without leaving your app, which helps on mobile:
const { data, error } = await supabase.auth.verifyOtp({
  email: '[email protected]',
  token: codeFromInput,
  type: 'email',
})

If emailRedirectTo isn't in your Redirect URLs list, Auth ignores it and uses the Site URL instead.

When the email is already registered

With confirmation on, signing up with an address that already has an account doesn't return an error, so attackers can't use the form to find out who has an account. For an already confirmed account, the response has a user object with an empty identities array and no session. If you want to show "you already have an account", check data.user?.identities?.length === 0.

Sign in

const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: passwordFromInput,
})

if (error?.code === 'invalid_credentials') {
  // wrong email or password; don't say which
}
if (error?.code === 'email_not_confirmed') {
  // offer to resend the confirmation email
}

Resend a confirmation email:

await supabase.auth.resend({ type: 'signup', email: '[email protected]' })

Every email counts toward the project's email limit (30 per rate-limit window, fixed on SupaHost), so don't resend automatically in a loop. See Rate limits.

Password reset

A reset has two steps: send the email, then let the user choose a new password once they're signed in through the link.

1. Send the reset email from a "Forgot password" form:

await supabase.auth.resetPasswordForEmail('[email protected]', {
  redirectTo: 'https://invoices.example.com/account/new-password',
})

Auth sends the Reset password template. Add https://invoices.example.com/account/new-password (or a wildcard that covers it) to Redirect URLs first.

2. Set the new password. When the user follows the link, they arrive on that page signed in. In a browser-only app, the client library fires a PASSWORD_RECOVERY event you can listen for. In both browser and server apps, the page can simply call updateUser:

const { error } = await supabase.auth.updateUser({ password: newPassword })

If you use server-side auth with the PKCE flow, the link arrives with a ?code= parameter. Exchange it in your callback route first (see Server-side auth), then redirect to the new-password page. The user can also type the code from the email instead:

await supabase.auth.verifyOtp({ email, token: codeFromEmail, type: 'recovery' })
await supabase.auth.updateUser({ password: newPassword })

Changing a password while signed in

The same updateUser call works for a signed-in user on a settings page. Ask for the current password in your UI and check it by signing in again before you call it, so someone who walks up to an unlocked laptop can't change it.

Signing out

await supabase.auth.signOut()                    // all of this user's sessions (default)
await supabase.auth.signOut({ scope: 'local' })  // only this device

Common problems

For every option on these methods, see the supabase-js reference for signUp.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us