Enroll a factor
Enroll a new TOTP or phone MFA factor.
Register a new multi-factor authentication factor for the currently signed-in user. After enrolling, the factor stays in unverified status until a challenge is solved with mfa.verify (or mfa.challenge_and_verify).
A live session is required — enroll calls get_session internally and raises Supabase::Auth::Errors::AuthSessionMissing if the user is signed out.
Signature
supabase.auth.mfa.enroll(params)params is a hash. Pass it as a literal ({ factor_type: "totp" }) or use Ruby's hash-literal shorthand (factor_type: "totp").
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
factor_type | String | Required | Either "totp" or "phone". Determines which sub-fields are honored and what payload is returned. |
friendly_name | String | Optional | Optional human-readable label for the factor (e.g. "iPhone Authenticator"). Shown in factor listings. |
issuer | String | Optional | TOTP only. The "issuer" string embedded in the otpauth:// URI — most authenticator apps display it next to the code. Defaults to the project hostname. |
phone | String | Optional | Phone factors only. The recipient phone number in E.164 format. |
Returns
A Struct with :id, :type, :friendly_name, :totp, and :phone. For TOTP factors :totp is itself a Struct with :qr_code (a data:image/svg+xml;utf-8,... URL ready to embed in an <img> tag), :secret (the raw base32 seed), and :uri (the full otpauth:// URI). For phone factors :phone carries the enrolled number; :totp is nil.
Example — TOTP full enroll → challenge → verify cycle
# Step 1 — enroll a TOTP factor
enroll = supabase.auth.mfa.enroll(
factor_type: "totp",
friendly_name: "Ada's iPhone",
issuer: "Example App"
)
factor_id = enroll.id
qr_code = enroll.totp.qr_code # data:image/svg+xml;utf-8,...
secret = enroll.totp.secret # base32 seed for manual entry
# Show qr_code in your UI; ask the user to scan it with their authenticator app.
# Step 2 — start a challenge once the user is ready to enter a code
challenge = supabase.auth.mfa.challenge(factor_id: factor_id)
challenge_id = challenge.id
# Step 3 — collect the 6-digit code from the user and verify
verify = supabase.auth.mfa.verify(
factor_id: factor_id,
challenge_id: challenge_id,
code: "123456"
)
verify.access_token # => upgraded JWT (aal2)
verify.user.factors # => includes the now-verified factorExample — phone factor
response = supabase.auth.mfa.enroll(
factor_type: "phone",
friendly_name: "Personal SMS",
phone: "+15555550123"
)
response.id # => factor id, status is "unverified" until challenge + verify
response.phone # => "+15555550123"
response.totp # => nilExample — minimal TOTP enrollment
# Most fields are optional; only factor_type is required.
response = supabase.auth.mfa.enroll(factor_type: "totp")
response.totp.uri # otpauth://totp/...?secret=...&issuer=...The :qr_code returned by GoTrue is a raw SVG string; enroll rewrites it as a data:image/svg+xml;utf-8,... URL so it can be dropped straight into an <img src> without a base64 step. Passing factor_type: "phone" without phone: surfaces as a GoTrue 4xx, not a client-side error.