← All notes

Field note

Supabase auto-confirm and open signups

Confirm Email off means Supabase treats the address as verified. Combined with public signup, a new account gets a confirmed session without proving the inbox.

23 Oct 20267 min read1480 words

Two switches in Supabase Auth decide whether a stranger can become a confirmed user. One allows new signups. The other decides whether that signup has to prove it owns the email. Hosted projects expose both in the dashboard, and the public settings document exposes the matching booleans to anyone who has the project URL and the publishable key.

Supabase's general configuration page states the dashboard behavior. "Allow new users to sign up" means users can sign up. If that control is off, only existing users can sign in. "Confirm email" means users must confirm the address before signing in for the first time. The same page says that having Confirm Email disabled assumes the address does not need to be verified, and that Auth implicitly confirms the email in the database. The control lives on the email provider.

This note is those two controls together. The auth posture post is the wider tour: anonymous sign-in, profile tables, and the admin users endpoint. The free scan reports the pair on a verified full scan, after the domain is claimed, because the read is part of the auth posture check.

What the public settings document shows

GET /auth/v1/settings is an unauthenticated read in the sense that the secret key is not required. The publishable key is the credential the client already ships. A response that parses as settings can include:

  • disable_signup — false means new users can register. That is the dashboard's "Allow new users to sign up" left on.
  • mailer_autoconfirm — true means email confirmation is not required. That matches Confirm Email disabled, which the configuration page says implicitly confirms the address.
  • phone_autoconfirm or sms_autoconfirm — true means the phone path confirms without a completed OTP check. The scan treats either flag as auto-confirm. If the key is absent, it does not guess.

bash

curl -sS \
  "$SUPABASE_URL/auth/v1/settings" \
  -H "apikey: $ANON_KEY" \
  -H "Authorization: Bearer $ANON_KEY"

Use your own project URL and your own publishable key. Read the JSON. Do not create a user to prove the flags. A signup against a project you do not operate is not a test of this setting.

The scan stores two findings when the booleans say so, and it stores them only on a verified full scan. A preview of an unverified URL does not call this endpoint.

"Open signup is enabled on public Auth settings" is disable_signup: false. Open signup is how a consumer app is supposed to work. The finding is there so an invite-only tool, or an internal tool that copied a starter, can see that the front door is public. Severity is medium because the flag alone is not a data leak. It becomes one when policies treat every authenticated JWT as a member.

"Auth auto-confirm is enabled" is mailer_autoconfirm or the phone flags. The location line names which flag was true. The meaning is the configuration page's sentence: the user can sign in without proving they own the address, and the email is confirmed in the database anyway.

The two findings are independent. You can have open signup with Confirm Email on, which is a normal consumer app that still rejects a typo'd inbox until the link is clicked. You can have signup disabled and auto-confirm left on from a local demo, which matters the day someone turns signup back on and does not re-read the email provider. You can have both, which is the case this post is about. A bot that can reach /auth/v1/signup receives a session whose email is already confirmed. Password reset, email change, and any product rule that checks "email confirmed" then treat that session as a finished account.

Why a starter turns both on

Local development wants a user without an inbox. Confirm Email off, or a local Inbucket catcher, is how you get that. The configuration page is explicit that the disabled control does not mean "skip the email for now and confirm later." It means the row is confirmed. Shipping that config to the hosted project keeps the shortcut.

Open signup is the other default a generator leaves on, because the first screen of a vibe-coded app is a register form. The combination is quiet. Nothing in the dashboard shows a red banner that says the confirmed bit is free. The settings JSON does. disable_signup: false next to mailer_autoconfirm: true is the whole story.

That confirmed session is an authenticated role in Postgres. If row level security only checks auth.uid() is not null or auth.role() = 'authenticated', the new account can read every row those policies cover. The login-only RLS note is that hole. Auto-confirm does not create it. Auto-confirm removes the inbox step that would have slowed it down. The anon key post is the case where no session is required at all. Fix that first if the Data API returns rows with only the publishable key. Then fix policies that treat every session as the owner. Then decide whether signup and confirmation match the product.

Anonymous sign-in is a third door and a different finding, "Anonymous sign-in is enabled." It is covered in the posture post. Do not confuse it with auto-confirm. Anonymous users never had an email to confirm. Auto-confirm users submitted an email that was never proven.

What to change

For a hosted project, use the labels on the configuration page.

If the product is invite-only, turn off "Allow new users to sign up." Existing users can still sign in. New accounts come from an invite or from a server-side admin create, using the secret key on the server. The publishable key in the browser cannot be the thing that mints those users.

If the product is a public signup, turn Confirm Email on. Users confirm before the first sign-in. The configuration page says the disabled state is what implicitly confirms the address, so the enabled state is the one that stops doing that. The control is under the email provider, not a global security checkbox with a different name.

Phone confirmation is the same idea. If phone_autoconfirm or sms_autoconfirm is true, require the OTP path before the phone is confirmed. The scan's location line tells you which flag fired so you do not flip the email control and leave the phone flag set.

Local config, for projects that push supabase/config.toml, uses the keys the finding snippet already shows:

toml

[auth]
enable_signup = false

[auth.email]
enable_confirmations = true
secure_password_change = true

Turn enable_signup off only when the hosted control should be off too. A public app keeps signup enabled and sets enable_confirmations. secure_password_change is the adjacent control the snippet includes so a password change also requires a recent login. It does not replace confirmation on the first signup. After you change the file, apply it to the hosted project. A toml edit that never reaches the project leaves mailer_autoconfirm true, and the next scan will still report it.

Custom SMTP does not fix auto-confirm. The password guide says signup confirmation and password reset need an SMTP server to send mail. That is the transport. Confirm Email is the decision to send the mail and to wait for it. If confirmation is disabled, a working SMTP server still has nothing to do at signup. If confirmation is enabled and SMTP is missing, the mail does not arrive, which is a product outage, not a reason to disable the check in production. Use the local mail catcher in development. Keep Confirm Email on for the hosted project.

What the scan will and will not say

The settings read is a GET. It does not sign anyone up, does not send mail, and does not change a flag. A verified full scan runs it. An unverified preview does not. A response that is not a settings object produces no auth finding. The scan does not store the rest of the document beyond the flags it needs for the finding.

Open signup on a consumer app can stay. Triage the finding as accepted when that is the product, and keep Confirm Email on so the accepted finding is only the signup one. Auto-confirm on a production project should not be triaged away. The configuration page's own description is the reason: the email is confirmed without verification.

Rescan after the dashboard save. "Auth auto-confirm is enabled" clears when mailer_autoconfirm and the phone flags are not true. "Open signup is enabled on public Auth settings" clears when disable_signup is true. Leaving signup on, with confirmation required, clears only the auto-confirm finding. That is the result you want for a public app.

The free scan is the URL check. Domain verification stays in front of this read, because the settings document is a property of a project you have claimed. The fix prompt on each finding is the dashboard path and the toml snippet, copied from that finding, ready to paste into Lovable, Cursor, or Claude. The wider settings tour, including anonymous users and tables that list people, stays in the auth posture post.