Example finding How it works Coverage PENTEST METHODOLOGY DOCS PRICING FAQ MCP CONTACT LOG IN SIGN UP →

Supabase users can sign up without confirming their email: the fix

FIX / EMAIL CONFIRMATION / 9 MIN READ

Open the email provider settings in the Auth section of the Supabase dashboard, turn on Confirm email, and save. When this option is off, a new user gets an account for any email address, without proof that the user owns it. The user does not have to click a link. Anyone can then sign up with the address of another person.

This guide explains the risk, shows how to confirm the setting, and gives the steps to fix it. It also shows how to find and remove the unconfirmed accounts that already exist.

What does it mean when users sign up without confirming their email?

It means that your project creates a working account before it checks the email address. The Supabase docs describe the setting in the general Auth configuration: “Users will need to confirm their email address before signing in for the first time.”

When the setting is on, Supabase sends an email with a link. The user must open the link. Until then, the user cannot sign in. When the setting is off, there is no email step.

Some teams turn it off on purpose to reduce friction. AI coding tools also turn it off, because it makes testing easier. A developer types a fake email, and the app lets the person in at once. The setting stays off when the app goes live.

The missing check causes these problems:

Problem What happens
Fake accounts A bot creates thousands of accounts with random addresses
Address impersonation A person signs up with the email address of someone else
Bad data Typos in the address mean you cannot contact the user
Account pre-hijack An attacker creates an account with a victim address before the victim does
Broken trust rules Rules that use the email domain, such as “company staff only”, accept any address
Cost Each fake user can use your paid features and your email quota

The account pre-hijack risk needs more detail. An attacker signs up with the email address of the victim and sets a password. The victim later signs in with a Google button or with a reset link. Depending on how your app links accounts, the attacker and the victim can end up with access to the same account. Email confirmation reduces this risk, because the attacker cannot confirm an address that the attacker does not own.

How do I confirm that my project has this problem?

Check the setting and then test the real behavior.

  1. Open the Supabase dashboard and select your project.
  2. Open Authentication, then Sign In / Providers.
  3. Open the Email provider.
  4. Read the Confirm email option. If it is off, the problem exists.

The docs say that the option is in the provider-specific configuration of the email provider. The page layout can change over time, so use the labels as a guide.

Then test the behavior. Use an email address that you do not own and cannot receive mail on, such as an address on example.com. Send a sign-up request to your own project:

curl -s -X POST "https://YOUR-PROJECT-REF.supabase.co/auth/v1/signup" \
  -H "apikey: YOUR_PUBLISHABLE_OR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"nobody+test1@example.com","password":"a-long-test-password-9"}'

Read the response body. If it contains an access_token, the project gave a session to an address that nobody confirmed. The setting is off. If the response has a user object but no access_token, the project waits for a confirmation. Compare the result with the setting that you read in the dashboard.

You can also check the users that exist. Run this query in the SQL editor:

select count(*) filter (where email_confirmed_at is null) as unconfirmed,
       count(*) filter (where email_confirmed_at is not null) as confirmed
from auth.users;

A high count of users with no confirmation time shows that people used the app without confirming. It also includes users who signed up recently and have not clicked the link yet, so read the dates too.

Note: Users who sign in with Google, GitHub, or another provider can have a confirmation time set by the provider. Their rows are not a problem. Check the provider of each row before you act on a bulk change.

Delete the test user afterward. Open Authentication, then Users, and remove the account.

How do I fix it? Steps in order of urgency

Do the steps in this order. Step 1 stops new unconfirmed accounts. The later steps handle the accounts that already exist.

  1. Turn on Confirm email. Open the email provider settings and switch on Confirm email. Save the change.
  2. Set up custom SMTP before you launch. The Supabase email auth docs say that the default email service is limited to 2 emails per hour and works on a best-effort basis. They recommend a custom SMTP server for production. With the default service, most new users never get the link.
  3. Set the site URL and the redirect URLs. The same docs say that the user goes to your site URL when you give no redirect. The default is localhost:3000. Set the real site URL in the Auth URL settings, and add the allowed redirect URLs.
  4. Run the curl test again. Use a new address. The response must have no access_token.
  5. Update the sign-up screen. After sign-up, show a message: “Check your email and click the link.” Do not send the user to the signed-in area.
  6. Handle the unconfirmed state. If a user tries to sign in before the confirmation, show a clear message and an option to send the email again.
  7. Test the whole flow. Sign up with an address that you own. Click the link. Confirm that you land on your site and that the session works.
  8. Deal with the existing accounts. See the next section.

Warning: Do not turn on Confirm email and leave the SMTP on the default service. New users will not get their emails, and your sign-ups will stop. Do the SMTP step first.

What does the sign-up call return now?

With the setting on, the client library call to signUp creates the user but the user cannot sign in yet. Read the signUp reference for the exact return values of your client version. The reference also notes that for an address that already has an account, you may get back an error message that hides this fact. The hidden error protects your users from a person who tests which addresses are registered.

Your code must not assume that a session exists after sign-up. Check the returned session before you redirect:

const { data, error } = await supabase.auth.signUp({
  email,
  password,
  options: { emailRedirectTo: "https://your-app.example/welcome" },
});

if (error) return showError(error.message);
if (!data.session) return showMessage("Check your email to confirm your account.");

The redirect URL must be in the allowed list. A URL that is not in the list does not work.

How do I find and remove the accounts that signed up without confirming?

Turning on the setting does not change old accounts. Decide what to do with each group.

First, list the unconfirmed accounts:

select id, email, created_at, last_sign_in_at
from auth.users
where email_confirmed_at is null
order by created_at desc;

Then sort them into groups.

Group Sign Action
Real users who signed up before the change Normal address, normal use, real activity Ask them to confirm. Send a new confirmation email
Bots Random names, many accounts in a short time, no real activity Delete the accounts
Impersonation Address that belongs to a known person or a company, used with no confirmation Delete the account and look at what it did
Old and unused No sign-in for a long time Delete after a notice period, if your policy allows it

Caution: Do not run a bulk delete before you check what the accounts own. A user row can have related rows in your own tables. Back up the data first. Look at your foreign keys to see which data the delete removes.

To delete a user, use the dashboard or the Auth admin API. Do not delete rows from auth.users by hand unless you know your foreign keys. The admin API runs the delete in the correct way.

To ask real users to confirm, send a new confirmation email. The client library has a resend call for this. Send in small groups so that you stay below the limit of your SMTP provider.

How do I check the damage?

Look for abuse in the logs, the tables, and the email records.

  1. Open the Auth logs. In the dashboard, open Logs and select the Auth logs. Look for bursts of sign-up events.
  2. Look for patterns. Many accounts in a short time, similar address names, and one source address are signs of a bot.
  3. Check your email provider. Read the sending logs and the bounce rate. Many bounces mean that the addresses were fake or mistyped. A high bounce rate can harm the reputation of your sending domain.
  4. Check the paid features. If a sign-up gives credits, a trial, or API access, look for accounts that used them and never confirmed.
  5. Check for impersonation. Search for accounts that use addresses of your own staff or your own domain. These must not exist without confirmation.
  6. Check the roles. Make sure that none of the unconfirmed accounts has an admin role or a paid plan. If your app assigns roles by email domain, review that logic now.
  7. Check your policies. Row Level Security (RLS) rules that trust the email address need the address to be confirmed. See the Supabase RLS guide.

If the same address pattern appears in other tables, such as invites or team members, remove those rows too.

What other sign-up settings should I review?

Confirm email is one of several settings. Review these in the same visit. The general Auth configuration docs describe them.

Setting What it does When to change it
Allow new users to sign up When off, only existing users can sign in Turn it off for an internal app or for an invite-only app
Allow anonymous sign-ins Lets people create anonymous users Keep it off unless your app needs it
Confirm email Requires confirmation before the first sign-in Keep it on for public apps
Minimum password length Sets the shortest allowed password Use 10 or more

An internal tool is a special case. If only your team uses the app, a public sign-up is a risk even with confirmation. Turn off Allow new users to sign up and create the users yourself or invite them.

Anonymous sign-ins are another source of accounts without an address. If your app does not need guest users, keep them off. If it does, give anonymous users the least access that works, and write your RLS policies with that in mind.

For the password side of the settings, read the authentication implementation guide. For the reset flow, see the Lovable forgot password guide.

How do I stop the problem from coming back?

Make the safe settings part of the launch routine.

  1. Add the Auth settings to your launch checklist. Check Confirm email, sign-up access, and anonymous sign-ins before the first public release.
  2. Match all environments. A new staging project or a copy of the project starts with default settings. Compare the Auth settings in each project.
  3. Add a sign-up test. Send a request with a throw-away address and fail the check when the response has an access_token.
  4. Put a rate limit on sign-up. A bot can still send thousands of sign-up requests, and each one sends an email. Limit sign-ups by address and by source. See API abuse protection.
  5. Add a CAPTCHA if bots continue. A CAPTCHA on the sign-up form blocks simple bots. Check the Supabase docs for the supported providers.
  6. Never trust the email without confirmation. In your own code and policies, treat an address as proof of identity only when its confirmation time is set.
  7. Scan the live app. A scan tests the real sign-up behavior of the deployed app.

When should I get help?

Get help in these cases:

  • Many unconfirmed accounts used paid features, and you cannot tell the cost. Ask your payment and email providers for their records.
  • You find accounts that use the addresses of real customers. Contact the affected customers and check what each account did.
  • Confirmation emails still do not arrive after you set up SMTP. Check the sender domain records, such as SPF and DKIM, with your email provider.
  • Your app grants admin rights or access by email address. Ask a security engineer to review the logic. A wrong rule can give access to an attacker.

After the fix, repeat the curl test with a new address. The response must have no access_token, and the test account must stay unable to sign in until a link is clicked.

Common questions

Why can users sign in without confirming their email?
The Confirm email setting is off in your project. The docs say that when it is on, users must confirm before the first sign-in.
Is it safe to leave Confirm email off?
Only if no part of your app trusts the email address. Most apps do trust it for reset links, invites, and domain rules. For these apps, turn it on.
Where is the Confirm email option?
It is in the settings of the email provider on the Sign In / Providers page of the Auth section in the dashboard.
Will the change affect users who already exist?
The setting controls new sign-ups. Existing accounts keep their state. Check each existing account that has no confirmation time.
Why do my confirmation emails not arrive?
The default Supabase email service has a very low limit and is best-effort only. Set up a custom SMTP server.

Test your sign up flow

A VibeEval scan tests the sign-up endpoint of your deployed app and reports when an account gets a session before the email address is confirmed.

14-day free trial · No credit card · Cancel anytime

SCAN YOUR APP →