Verify a challenge
Submit a code to satisfy an MFA challenge.
Complete the MFA flow by submitting the code the user supplied for an outstanding mfa.challenge. On success GoTrue returns a fresh access token with the elevated assurance level (aal2), the new tokens are saved to the local session, and a MFA_CHALLENGE_VERIFIED event is dispatched to any auth-state-change subscribers.
A live session is required — verify calls get_session internally and raises Supabase::Auth::Errors::AuthSessionMissing if the user is signed out.
Signature
supabase.auth.mfa.verify(params)params is a hash. Pass it as a literal ({ factor_id: "...", challenge_id: "...", code: "..." }) or use Ruby's hash-literal shorthand.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
factor_id | String | Required | The ID of the enrolled factor that the challenge belongs to. |
challenge_id | String | Required | The challenge ID returned by mfa.challenge. |
code | String | Required | The one-time code the user obtained — for TOTP, the 6-digit code from their authenticator app; for phone factors, the code from the SMS / WhatsApp message. |
Returns
A Struct with :access_token, :token_type, :expires_in, :refresh_token, and :user. The new access token has its aal claim upgraded to aal2. The same tokens are also persisted in the client's session store and the bearer is rotated, so subsequent calls automatically use the upgraded JWT.
Example — verify a TOTP code
verify = supabase.auth.mfa.verify(
factor_id: factor_id,
challenge_id: challenge.id,
code: "123456"
)
verify.access_token # => new JWT with aal: "aal2"
verify.user.emailExample — listen for MFA_CHALLENGE_VERIFIED
supabase.auth.on_auth_state_change do |event, session|
if event == "MFA_CHALLENGE_VERIFIED"
puts "User upgraded to AAL2 at #{Time.now}"
end
end
# Later, in the verify step:
supabase.auth.mfa.verify(
factor_id: factor_id,
challenge_id: challenge.id,
code: code
)
# => block above fires with event = "MFA_CHALLENGE_VERIFIED"Example — handling a bad code
begin
supabase.auth.mfa.verify(
factor_id: factor_id,
challenge_id: challenge.id,
code: user_supplied_code
)
rescue Supabase::Auth::Errors::AuthApiError => e
# GoTrue returns 4xx for invalid / expired codes.
flash[:error] = "That code didn't work. Try again or request a new challenge."
endVerify save-session edge case
If GoTrue ever returns a response without an access_token field, the local-session save is silently skipped and MFA_CHALLENGE_VERIFIED is NOT fired. In practice GoTrue always returns the full session on success, so this only matters if you're proxying responses through a custom layer.