Demo, all content is generated
Question

Added 2FA with Supabase but login never asks for the code

Solved · 983 views · asked by jasper_t · edited

Claude Code built TOTP enrollment for me: QR code, you scan it with Google Authenticator, enter the code, it says enrolled. The factor shows as 'verified' in the Supabase dashboard. But when I log out and log in again with email + password I'm straight in. No code asked. What's the point then?

What I’ve tried

Asked Claude to 'enforce 2FA at login'. It added a page asking for the code, but you can just navigate away from it to /dashboard.

Comment
Good that you noticed. Many apps ship exactly this and call it 2FA. jb_supa · edited

2 answers

Marked as helpful by the asker
jb_supa · edited

Supabase enrolls the factor, but enforcing it is your job. After password login the session is at aal1 (one factor). After the TOTP code it's aal2.

In the app, after login:

const { data } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
if (data.nextLevel === 'aal2' && data.currentLevel !== 'aal2') {
  // show the code screen, then mfa.challenge + mfa.verify
}

That's UX only; users can still navigate away, as you found. The real lock is in the database. A restrictive policy on sensitive tables:

create policy "require mfa when enrolled" on invoices
as restrictive for all to authenticated
using (
  array[auth.jwt()->>'aal'] <@ (
    select case when count(*) > 0 then array['aal2'] else array['aal1','aal2'] end
    from auth.mfa_factors where user_id = auth.uid() and status = 'verified'
  )
);

Now a user with a verified factor gets no data until they've entered the code, whatever page they go to. Also check it in your middleware so pages redirect nicely.

Comment
Didn't know about restrictive policies. Tested: skip the code screen, dashboard loads but empty. Exactly what I wanted. jasper_t · edited
This is the pattern from the Supabase MFA docs. 'as restrictive' is the key word, a normal permissive policy would be OR'ed with your other policies and do nothing. mira_dev · edited
amir_h · edited

Related: once 2FA exists, require an aal2 session for changing security settings too (email, password, removing the factor). Otherwise someone with a stolen password just disables 2FA first.

Comment