With the new passkey support in Supabase Auth, a session established by signing in with a passkey is aal1 (AMR passkey), the same assurance level as a password sign-in. That is a sensible, conservative default. But there is currently no way for a project to choose otherwise, which has two consequences:
updateUser password change, managing passkeys while MFA is enabled).Local Supabase CLI stack, supabase_flutter 2.18, Chrome on Windows 11, user with a verified TOTP factor and a registered passkey:
auth.sessions.aal = 'aal1', mfa_amr_claims.authentication_method = 'passkey'.updateUser({ password }) → refused with insufficient_aal until a TOTP challenge is completed.Whether a passkey deserves AAL2 depends on the passkey and on the project's risk profile:
UV flag) combines possession of a private key with a biometric or device PIN, and it is phishing-resistant (origin-bound). In many contexts it can reasonably be treated as AAL2; NIST SP 800-63B and its 2024 supplement on syncable authenticators allow this under conditions.UV flag, and the backup eligibility/backup state flags (BE/BS), which tell a relying party whether a credential is syncable or has been synced.Secure by default: passkeys should stay aal1 unless a project explicitly opts in.
UV=1, BS=0), or any passkey with user verification.current_password. Recovery sessions keep their existing exemption.aal1 session to step up with a passkey assertion, the way mfa.challenge/mfa.verify works for TOTP, without a full sign-out and sign-in (subject to setting 1).UV/BE/BS (or a derived "synced"/"device-bound" field) in the passkey list, so apps can show users which kind they have. Today users usually can't tell; for example, the Windows prompt doesn't say whether a passkey is stored only on the device or synced.Applications that want admin actions protected by a second factor must either (a) ask passkey users for a TOTP code anyway, or (b) read the AMR claim themselves and treat passkey as a second factor in their own RLS, which then disagrees with Auth's own aal. We currently do (b) for our own policies and still hit (a) wherever Auth enforces AAL2 itself, such as password updates.
Happy to provide more detail or test a preview.
The user suggests improvements to Supabase Auth to allow passkeys to satisfy AAL2, enabling them to be used as a second factor or for re-authentication instead of passwords. They propose configurable settings to treat passkeys as AAL2 based on user verification and device binding, and to expose relevant flags for better user understanding. This would enhance security and flexibility for projects using passkeys.
Additional context: with passkeys counting toward AAL2 (or treated as a second factor by apps, as we do via the passkey AMR), there's a binding gap. Today a password-only aal1 session can register a user's first passkey (step-up only applies once a verified TOTP exists). That means the password alone can mint a credential that later satisfies a "second factor" check. If this enhancement is adopted, it would help to also have an option to require AAL2 or an existing-factor ceremony for all passkey registrations, including the first, or at least to expose on the session/AMR whether the passkey used was registered from an AAL2 session. Our workaround in the meantime is to require a TOTP for admin-tier accounts.
A related gap: removing all second factors re-opens factor changes to the password alone. Today Supabase only requires AAL2 to add or remove factors while a verified TOTP exists, and a passkey sign-in isn't AAL2. So on an account protected only by passkeys, anyone who knows the password can add their own TOTP or passkey, and that new factor then passes every "second factor" check. The same happens to a TOTP user who removes their last TOTP: the account drops back to password-only protection for factor changes. We'd suggest: once an account has any verified second factor (TOTP or passkey), adding or removing a factor requires verifying an existing one (or at least make this a configuration option when a project enables passkeys as AAL2). Together with letting a passkey satisfy AAL2, that would let apps treat passkeys and TOTP as equal second factors. Today we have to require a TOTP for admin accounts: we add our own out-of-band activation step when an admin's first second factor is bound, and only a TOTP can carry that activation forward, because only a TOTP makes Supabase require AAL2 for later factor changes. With this change, an activated admin could move between passkeys and TOTP (for example, to a new phone) without being re-activated.