Supabase Auth OTP long expiry warning: how to fix it
The Supabase “Auth OTP long expiry” warning means that your email one-time passwords stay valid for longer than the recommended time, so set Email OTP expiration to 3600 seconds or less. The setting is in the Auth provider settings for Email. The change takes less than five minutes. This guide shows the exact steps, the side effects, and the checks that you must do afterward.
What does the “Auth OTP long expiry” warning mean?
The warning means that the time limit of your email one-time password (OTP) is longer than the value that Supabase recommends. The warning appears in the Security Advisor in your project. Vibe coding tools such as Lovable also show it in their security scan.
An OTP is a short code that Supabase sends by email. The user types the code to sign in or to confirm an action. The same setting also controls magic links. The Supabase passwordless login documentation states that the Email OTP expiration setting “also governs the validity of Magic Links and other email links.”
The default value is 3600 seconds, which is 1 hour. The warning does not appear for the default value. It appears after somebody raised the value. A person or a tool can do this to stop the “OTP expired” error that users report.
The Supabase production checklist tells you to set the expiry to “3600 seconds (1 hour) or lower.” It also tells you to make the OTP longer if you need more entropy.
Note: I did not find an official page that states the exact value at which the warning starts. Use 3600 seconds as the target. Then you are inside the checklist for all cases.
Why is a long OTP expiry a risk?
A code that stays valid for a long time gives an attacker more time to use it. The risk has three causes.
- Guessing. A 6-digit code has 1,000,000 possible values. An attacker who has more time can try more values.
- Stolen email. A code stays in the mailbox of the user. If somebody gets access to the mailbox later, an old code can still work.
- Forwarded or shared links. A magic link that is valid for many hours can be used by anyone who gets the link. Mail scanners, shared mailboxes, and browser history can all hold the link.
The default limits of Supabase reduce the guessing risk. The Supabase config reference gives a default of 30 OTP and magic link verifications in a 5 minute interval for each IP address. This is 360 tries each hour for one IP address. For a 24 hour code, this is 8,640 tries, or about 0.9 percent of the 6-digit space.
These numbers are not an exact model of an attack. An attacker can use many IP addresses. The limit applies to each IP address and not to each user. Use the numbers only to see that more time gives more tries.
A long expiry does not give an attacker access by itself. The attacker still needs a valid code. For this reason, Supabase shows a warning and not an error. Fix it, but do not treat it as a sign of a breach.
How do I confirm the current OTP expiry in my project?
Use one of these checks. The dashboard check is the fastest.
Check in the dashboard
- Open your project in the Supabase dashboard.
- Go to Authentication, then Sign In / Providers.
- Open Auth Providers and select Email.
- Read the value of Email OTP expiration.
The documentation gives this path: Authentication > Sign In / Providers > Auth Providers > Email > Email OTP expiration. The dashboard layout can change, so look for the setting name if a menu has a different label.
Check in a local project
If you use the Supabase CLI and a local config.toml file, search for the key otp_expiry. According to the config reference, the key is auth.email.otp_expiry. Its default is 3600 and its unit is seconds.
[auth.email]
otp_expiry = 3600
otp_length = 6
If the file has a value above 3600, the local project has the problem. A hosted project uses its own dashboard setting. Push the config with the CLI if you manage the hosted project from the file.
Check what your app really sends
- Request a sign-in code for a test account.
- Do not use the code. Wait for a time that is longer than your target, for example 2 hours.
- Use the code. If the sign-in works, the expiry is longer than your target.
Use a test account only. Do not test with the account of a real user.
How do I fix the OTP long expiry warning now?
Do the steps in this order. Steps 1 to 3 remove the warning. The other steps remove the risk that stays.
Caution: Codes and links that users already received keep their old expiry. Wait for the old time to pass if you must be sure that no old code works.
- Open the Email provider settings. Go to Authentication, Sign In / Providers, Auth Providers, Email.
- Set Email OTP expiration to 3600 or less. The value is in seconds. For a sign-in code, 600 seconds (10 minutes) is a common choice. For an invite flow, keep 3600 seconds so that people have time to open the mail.
- Save the setting. Then request a new code and check that the code works.
- Make the code longer if you need more safety. The
auth.email.otp_lengthkey accepts values from 6 to 10 digits. Each extra digit gives 10 times more possible codes. - Check the rate limits. Keep the limit for OTP and magic link verifications. The config reference lists
auth.rate_limit.token_verificationswith a default of 30 each 5 minutes for each IP address. - Run the Security Advisor again. The warning must go away. If it stays, you changed a different project, or the value is still above the limit.
What if the value is above 86400 seconds?
An expiry of more than one day is a special case. The Supabase documentation states that “an expiry duration of more than 86,400 seconds (one day) is strongly discouraged.” It also says that you can set such a value only through the Management API.
If your project has a value above 86400, somebody used the API on purpose or a script did it. Look for the script. Search your repository and your deployment scripts for mailer_otp_exp. This is the field name in the Management API for the auth config, which you update with PATCH /v1/projects/{ref}/config/auth.
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
-H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mailer_otp_exp": 3600}'
Create the access token in your account settings and keep it out of the repository. Delete the token after you finish if you do not need it again. Do the same check as before: open the dashboard and read the new value.
What breaks when I shorten the expiry?
A shorter expiry can break flows where people open the mail late. You must know these cases before you change the value.
| Flow | Effect of a short expiry | What to do |
|---|---|---|
| Sign-in code | Users who wait too long must request a new code | Show a clear “Send a new code” button |
| Magic link | The link stops working after the time ends | Keep 3600 seconds if your users open mail slowly |
| Password reset link | The link expires sooner. This is safe | Keep the short value |
| Invite email | The invited person can find an expired link | Send the invite again, or use a longer value with care |
| Email confirmation | The link can expire before the user opens it | Allow users to request a new confirmation email |
The setting is global for the email provider. You cannot give each flow its own expiry. A community report in the Supabase discussions says that users must choose one value for invites and for password resets. I did not find an official statement for per-flow values, so plan for one value.
If your product depends on slow invite flows, choose 3600 seconds. This value is the highest value that the checklist accepts. A flow that needs more than one hour must use a different method. Send the invited person a new link on request, and do not extend the life of one link.
Also check the user interface. An expired code returns an error. Many vibe coded apps show a raw error text or no text. Add a message and a button that sends a new code. Then a short expiry does not become a support problem.
How do I check if somebody already used a long-lived code?
A long expiry does not show that an attacker used a code. Do these checks to be sure. They take about 15 minutes.
- Read the Auth logs. In the dashboard, open the logs for Auth. Look for many failed verification attempts from the same IP address or for the same user.
- Look for repeated code requests. A burst of requests for one email address can show that somebody tries to log in as that user. The default limit is one request each 60 seconds for each user, so a high count is visible.
- Check recent sign-ins. Run a query on the
auth.userstable in the SQL editor. Look for sign-ins at unusual times or for accounts that you do not know. - Ask users with admin rights. An admin account is the most valuable target. Ask these users to confirm their recent sign-ins.
This query lists the latest sign-ins and the latest recovery requests:
select id, email, last_sign_in_at, recovery_sent_at, confirmation_sent_at
from auth.users
order by last_sign_in_at desc nulls last
limit 50;
Warning: The auth.users table contains personal data. Do not copy the result into a chat, a ticket, or a public file.
If you find a sign-in that the owner of the account did not make, treat the account as taken over. Reset the password of the account and sign out its sessions. Check what data the account could read. If an admin account is affected, read privilege escalation in SaaS apps for the next steps.
How do I prevent the warning from coming back?
The warning comes back when a person or a tool raises the value again. Use these controls.
- Store the value in code. If you use the CLI, keep
otp_expiryinconfig.tomland review each change in a pull request. - Tell your coding tool the limit. Add a rule to the project instructions: “Never set the email OTP expiry above 3600 seconds.” AI tools often raise the value to remove an “OTP expired” error.
- Fix the cause of the expired error. If users see “OTP expired” often, the mail may arrive late. Set up a custom SMTP server, as the production checklist recommends. A longer expiry is not the right fix for slow mail.
- Keep the rate limits. Do not raise the limit for verifications without a clear need.
- Run the Security Advisor before each release. Fix the warnings of the Advisor in the same way as the test failures.
- Scan the deployed app. A scan checks the behavior of the live app and not only the settings. See the auth flows pattern for magic links, OTP, and reset for the checks that you can do yourself.
The OTP setting is one part of your Auth setup. The Security Advisor also lists warnings for Row Level Security (RLS) and for functions. If your project has those warnings, read the Supabase RLS guide.
When should I get help?
Get help in these cases:
- The Auth logs show many failed or successful verifications that you cannot explain. Ask a security engineer to read the logs with you.
- An admin account has a sign-in that the owner did not make.
- Your app handles payments or health data. A takeover of one account can cause legal duties, and you must report a breach in some regions.
- The warning stays after you set the value to 3600 or less. Check that you changed the correct project, and contact Supabase support if the value is correct.
- A script sets
mailer_otp_expand you do not know who owns the script. Review the access tokens of your account and delete the tokens that you do not use.
After you finish, request one new code and one new magic link. Check that both work inside the new time and fail after it.
Common questions
Is the OTP long expiry warning a critical problem?
What value must I use for the OTP expiry?
Does a shorter OTP expiry also change magic links?
Can I make the OTP longer instead of keeping it short?
Do I need to sign out my users after I change the setting?
Test your auth flows
A VibeEval scan tests the sign-in, magic link, and password reset flows of your deployed app and reports one-time codes and links that stay valid for too long.
14-day free trial · No credit card · Cancel anytime