Skip to content
← Back to the blog
securityapps

How to add two-factor authentication to your app

Which second factor to offer, how 2FA works in Supabase Auth, how to enforce it in the database, and how to handle lost phones without opening a back door.

By Devonauts Team5 min read
Leer en español →
A finger taps a number on a passcode keypad on a tablet screen

To add two-factor authentication (2FA) to your app, let users enrol an authenticator app (TOTP) or a passkey as a second factor, ask for it at sign-in after the password, and enforce it on the server so that sessions without the second factor can't reach protected data. Make it mandatory for admins, optional or risk-based for everyone else, and plan account recovery before launch: a lost phone shouldn't lock someone out for good, and the recovery path shouldn't let an attacker skip the second factor.

This guide compares the factor types, walks through 2FA in Supabase Auth (the backend most AI-built apps use), shows how to enforce it in the database, and covers recovery and testing.

Which second factor should I offer?

FactorHow it worksStrengthNotes
Passkey / WebAuthnDevice or security key signs a challenge, unlocked by fingerprint, face or PINStrongest, resists phishingBest user experience on modern devices
Authenticator app (TOTP)Six-digit code from an app that changes every 30 secondsGoodFree to run, works offline, can still be phished
SMS or phone codeCode sent by text or WhatsAppWeakestSIM-swap risk, costs per message
Email codeCode sent to the account emailLimitedNot a real second factor if email is also how passwords get reset

OWASP's Multifactor Authentication Cheat Sheet recommends offering TOTP as a practical default and passkeys where you can, and treats SMS as the weakest option. NIST classes SMS codes as a "restricted" authenticator because of SIM swapping and interception. If your app handles money or sensitive personal data, avoid SMS as the only option.

How does 2FA work in Supabase Auth?

Supabase calls it MFA, and the Supabase MFA guide documents two factor types: authenticator app (TOTP) and phone (SMS or WhatsApp).

  • TOTP is free and enabled on all Supabase projects by default.
  • Phone MFA is a paid add-on. At the time of writing Supabase lists it at $75 a month for the first project and $10 a month for each additional one, billed by the hour it's enabled, plus whatever your SMS provider charges per message. Supabase's own docs warn that phone MFA is vulnerable to SIM-swap attacks.

The key idea is the assurance level in the user's token. After a password (or magic link, or social) sign-in, the session is aal1. After the user also verifies a second factor, it becomes aal2. Your frontend, your server and your database policies can all check it.

Enrolment

  1. The user opens security settings and chooses "Set up authenticator app".
  2. Your app calls supabase.auth.mfa.enroll({ factorType: 'totp' }), which returns a QR code and a secret.
  3. The user scans the QR code in their authenticator app.
  4. Your app calls challenge and then verify with the six-digit code they type. Only now is the factor active.

Sign-in

  1. The user signs in with their password as usual.
  2. Your app calls supabase.auth.mfa.getAuthenticatorAssuranceLevel(). If the current level is aal1 and the next level is aal2, the user has a factor and must be asked for a code.
  3. Show the code screen, then challenge and verify. The session is upgraded to aal2.

Redirecting to the code screen in the frontend is only the user-facing half. The other half is enforcement.

How do I enforce 2FA so it can't be skipped?

If only the frontend checks for the second factor, someone with the password can call your API or database directly with the aal1 session. Enforce it where the data lives. For tables that should require 2FA from everyone who reaches them, Supabase's docs recommend a restrictive policy:

create policy "Require MFA"
on public.payouts
as restrictive
to authenticated
using ((select auth.jwt() ->> 'aal') = 'aal2');

Restrictive policies are combined with your normal ones, so a user needs both the usual ownership rule and an aal2 session. Supabase's guide also shows variants that apply only to users who have enrolled a factor, which is useful when 2FA is optional. In your own server functions, read the aal claim from the verified token and reject aal1 for sensitive actions.

Who should be required to use 2FA?

  • Admins and staff: always. Their accounts reach everyone's data. Our guide on securing your admin panel covers the rest of that setup.
  • Regular users: offer it, and encourage it for accounts with money, sensitive data or team admin rights.
  • Step-up for sensitive actions. Even when 2FA isn't mandatory, ask for the second factor before changing the email or password, turning 2FA off, exporting data or adding a payout method. OWASP lists exactly these as moments to require it.

How do users recover if they lose their phone?

Plan this before launch, because it's where 2FA systems are usually broken. Supabase's MFA guide doesn't describe recovery codes, so you need to decide on a recovery method yourself. Common options:

  1. Let users enrol more than one factor (for example, an authenticator on two devices, or a passkey plus TOTP) and encourage a backup at setup.
  2. Recovery codes generated at enrolment, shown once, stored hashed, each usable once. You'd build these yourself.
  3. A support process where a person verifies identity before removing a factor, with a waiting period and an email to the account address.

Whatever you choose, removing a factor should require an aal2 session or a verified recovery step, and should trigger an email to the account address. The password reset flow must not quietly remove 2FA either: resetting a password should still leave the second factor in place.

How do I test my 2FA setup?

  1. Enrol a factor on a test account, sign out, and sign in again. You should be asked for a code.
  2. At the code screen, skip it by opening a protected page directly. The page must not load data.
  3. With the aal1 session (copy a request from the browser's Network tab), call a protected table or function. It must be refused.
  4. Enter a wrong code several times. You should hit a limit.
  5. Unenrol the factor and confirm an email notification is sent.
  6. Run your recovery process end to end with a test account.

The OWASP Top 10 puts authentication failures at number seven; see our overview of the OWASP Top 10 for AI-built apps for the related risks.

Quick answers

Is email-code 2FA good enough? Only weakly. If someone controls the user's email, they get both the reset link and the code.

Will 2FA annoy my users? Less if it's optional for most, required for sensitive actions, and quick on trusted devices. Passkeys are often faster than typing a password.

Does Supabase support passkeys as a second factor? Its MFA guide currently documents TOTP and phone. Check the current docs if passkeys matter to you.

Can my AI tool add 2FA? It can generate the screens. Check that enforcement happens in the database or server, not only in the frontend.

Want help setting it up?

The free health check shows what your app exposes from outside. If you'd like a person to add 2FA with proper enforcement and recovery, that fits our Supabase security review or maintenance plans, or you can contact us.

Still stuck with your app?

Run the free health check on your app's URL, or get the $299 App Rescue Audit: a senior engineer reviews the code and sends you a written report in 2 business days.