Skip to content
Fluxer API

Multi-factor authentication

An account can use a TOTP authenticator app and WebAuthn credentials such as passkeys as a second factor, with one-use backup codes as a fallback. A registered passkey works for sudo mode and passwordless login right away, but password login only asks for it after Set WebAuthn two-factor authentication turns it on.

Fluxer accepts six-digit HMAC-SHA-1 codes with a 30-second time step and one step of clock skew either way. Your client generates the Base32 secret, shows it to the user and sends it with a code to Enable TOTP MFA.

An account gets 10 backup codes when it enables TOTP, and when it turns on passkeys as a second factor while it has no unused codes. Each code works once, and Fluxer ignores case, spaces and hyphens when it compares them. You can send a backup code wherever a TOTP code is accepted, except in Enable TOTP MFA. Fluxer deletes every code when the account is left with no second factor.

A user who lost their codes and their authenticator can get them back by email without sudo mode.

  1. Start MFA backup codes challenge emails a code and returns a ticket that lasts 30 minutes.
  2. Verify MFA backup codes challenge code trades the code, which lasts 10 minutes, for the current codes and a proof.
  3. Regenerate MFA backup codes uses the proof to replace the set.

The account needs a verified email address and a second factor turned on. On an instance where people sign in with a username these routes return 400 EMAIL_UNAVAILABLE_ON_INSTANCE, and on an instance that sends no email they return 400 EMAIL_CODES_UNAVAILABLE. Ticket, code and proof errors come back as INVALID_FORM_BODY validation errors.

Each step also has a limit of its own, on top of its route bucket.

StepLimit
Start3 emails per 15 minutes for each account
Resend3 emails per 15 minutes for each account, and 30 seconds between emails on a ticket
Verify5 attempts per 15 minutes for each ticket
Regenerate5 attempts per 15 minutes for each ticket

Sudo mode is a recent proof that the account holder is present, and security-sensitive operations need it. Send the proof as sudo verification object fields in the JSON body, or send an earlier token in the X-Fluxer-Sudo-Mode-JWT header. After a TOTP, backup code, passkey or email code proof, Fluxer returns a token in the same response header, and it works for five minutes on any of the account's sessions. A password proof returns no token, and the account sends password on each call.

The first row that matches the account says which proofs it can send.

Account stateProof to send
A second factor is turned onmfa_method of totp or webauthn with its fields
Has a passwordpassword, or webauthn with a passkey
Has a confirmed email address on an instance that sends emailmfa_method of email_code after Send a sudo email code, or webauthn with a passkey
Connected to SSONothing, from a session that signed in with SSO in the last 10 minutes. Older sessions sign in with SSO again first
None of theseNothing, and the operation goes ahead

Without a proof, the request returns 403 SUDO_MODE_REQUIRED with the sudo mode methods fields. A wrong password returns 400 INVALID_PASSWORD on the path password, and a wrong TOTP code, backup code or passkey assertion returns 400 INVALID_MFA_CODE on the path mfa_code. The totp method allows 10 attempts per 15 minutes for each account, and a correct code resets the count. A wrong email code returns 400 INVALID_SIGN_IN_CODE, a ticket past 10 minutes or 5 wrong tries returns 400 SIGN_IN_CODE_EXPIRED, and an unknown ticket returns 400 UNKNOWN_SIGN_IN_TICKET.

SUDO_MODE_REQUIRED responses have these fields at the top level of the error response.

FieldTypeDescription
has_mfabooleanWhether the account has TOTP or at least one passkey
methodssudo mode method availability objectThe proofs the account can give
FieldTypeDescription
totpbooleanWhether the account has TOTP enabled
webauthnbooleanWhether the account has at least one passkey
backup_codes1booleanWhether the account has at least one unused backup code
email_code?booleanWhether the account can prove sudo mode with an emailed code
password?booleanWhether the account can prove sudo mode with its password
sso_reauthbooleanWhether the account proves sudo mode by signing in with SSO again. Always false while the instance has SSO turned off

1 A backup code goes in mfa_code with mfa_method set to totp, also on an account without TOTP. There is no backup_codes method

FieldTypeDescription
totpbooleanWhether the account has TOTP enabled
webauthnbooleanWhether the account has at least one passkey
backup_codesbooleanWhether the account has at least one unused backup code
has_mfabooleanWhether the account has TOTP or at least one passkey
email_code?booleanWhether the account can prove sudo mode with an emailed code
password?booleanWhether the account can prove sudo mode with its password

Operations that need sudo mode accept these fields in their JSON body.

FieldTypeDescription
password?stringAccount password (8-256 characters)
mfa_method?stringOne of totp, webauthn or email_code
mfa_code?stringTOTP code or unused backup code, for totp (1-32 characters)
webauthn_response?WebAuthn assertion objectAssertion for webauthn_challenge, for webauthn
webauthn_challenge?stringChallenge from Create sudo WebAuthn authentication options (1-256 characters)
email_code_ticket?stringTicket from Send a sudo email code, for email_code (1-128 characters)
email_code?stringThe 6-digit code from the email, for email_code
FieldTypeDescription
backup_codesarray[MFA backup code object]The account's current backup codes
{
"backup_codes": [
{"code": "a3f2-9kd7", "consumed": false},
{"code": "b81c-4nq0", "consumed": true}
]
}
FieldTypeDescription
codestringThe code, two groups of four lowercase letters and digits
consumedbooleanWhether the code has been used
FieldTypeDescription
ticketstringThe ticket to send in later steps
code_expires_atISO8601 timestampWhen the emailed code expires
resend_available_atISO8601 timestampWhen you can ask for another code
FieldTypeDescription
backup_codesarray[MFA backup code object]The account's current backup codes
verification_proofstringThe proof for Regenerate MFA backup codes

An account can have up to 10 WebAuthn credentials.

FieldTypeDescription
idstringBase64url credential ID
namestringName the user gave the credential (1-100 characters)
created_atISO8601 timestampWhen the credential was registered
last_used_at?ISO8601 timestampWhen the credential was last used, or null if never
rp_idstringThe domain the passkey was created for
FieldTypeDescription
idstringBase64url credential ID
typestringAlways public-key
transports?array[string]Transports the authenticator reported at registration, or internal, hybrid, usb, nfc and ble when it reported none
ValueDescription
bleBluetooth Low Energy
cableCloud-assisted Bluetooth Low Energy
hybridHybrid transport
internalPlatform authenticator
nfcNear-field communication
smart-cardSmart card
usbUSB authenticator

The PublicKeyCredential JSON the browser WebAuthn API produces.

FieldTypeDescription
idstringBase64url credential ID
rawIdstringBase64url raw credential ID
responseWebAuthn assertion response objectAuthenticator assertion response
authenticatorAttachment?stringEither cross-platform or platform
clientExtensionResultsWebAuthn client extension results objectClient extension outputs
typestringAlways public-key
FieldTypeDescription
clientDataJSONstringBase64url client data JSON
authenticatorDatastringBase64url authenticator data
signaturestringBase64url assertion signature
userHandle?stringBase64url user handle, for a discoverable credential
FieldTypeDescription
idstringBase64url credential ID
rawIdstringBase64url raw credential ID
responseWebAuthn attestation response objectAuthenticator attestation response
authenticatorAttachment?stringEither cross-platform or platform
clientExtensionResultsWebAuthn client extension results objectClient extension outputs
typestringAlways public-key
FieldTypeDescription
clientDataJSONstringBase64url client data JSON
attestationObjectstringBase64url attestation object
authenticatorData?stringBase64url authenticator data
transports?array[string]Transports the authenticator supports
publicKeyAlgorithm?integerCOSE public key algorithm identifier
publicKey?stringBase64url credential public key

Fluxer accepts extension result fields beyond these.

FieldTypeDescription
appid?booleanWhether the AppID extension was used
credProps?WebAuthn credential properties objectCredential properties output
hmacCreateSecret?booleanWhether the authenticator created an HMAC secret
FieldTypeDescription
rk?booleanWhether the created credential is discoverable

The options to pass to navigator.credentials.get(), returned for sudo mode here and for login on HTTP authentication.

FieldTypeDescription
challengestringBase64url one-use challenge, valid for five minutes
timeoutintegerAuthenticator timeout in milliseconds, always 60000
rpIdstringRelying party ID, the instance's FLUXER_PASSKEY_RP_ID
allowCredentials?array[WebAuthn credential descriptor object]The account's passkeys, left out for discoverable login
userVerificationstringOne of discouraged, preferred or required

The options to pass to navigator.credentials.create().

FieldTypeDescription
rpWebAuthn relying party objectRelying party identity
userWebAuthn registration user objectThe account the credential belongs to
challengestringBase64url one-use challenge, valid for five minutes
pubKeyCredParamsarray[WebAuthn public key parameter object]Accepted algorithms, COSE -8, -7 and -257
timeoutintegerAuthenticator timeout in milliseconds, always 60000
excludeCredentialsarray[WebAuthn credential descriptor object]The account's existing credentials
authenticatorSelectionWebAuthn authenticator selection objectAuthenticator requirements
attestationstringAlways none
extensionsWebAuthn client extension inputs objectRequested extensions, credProps only
hintsarray[string]Always empty
FieldTypeDescription
namestringRelying party display name
idstringRelying party ID, the instance's FLUXER_PASSKEY_RP_ID
FieldTypeDescription
idstringUser handle, the account snowflake as a decimal string in base64url
namestringAccount username
displayNamestringAccount username
FieldTypeDescription
algintegerCOSE algorithm identifier
typestringAlways public-key
FieldTypeDescription
authenticatorAttachment?stringEither cross-platform or platform
requireResidentKey?booleanWhether a discoverable credential is required
residentKey?stringOne of discouraged, preferred or required
userVerification?stringOne of discouraged, preferred or required
FieldTypeDescription
appid?stringAppID extension value
credProps?booleanWhether credential properties are requested
hmacCreateSecret?booleanWhether HMAC secret creation is requested
minPinLength?booleanWhether minimum PIN length is requested
FieldTypeDescription
ticketstringThe value to send back as email_code_ticket
code_expires_atISO8601 timestampWhen the emailed code expires, 10 minutes after sending
resend_available_atISO8601 timestampWhen you can ask for another code, 30 seconds after sending
FieldTypeDescription
useruser objectThe updated account
backup_codes?array[MFA backup code object]The 10 new codes, or null when the call created none
POST/v1/users/@me/mfa/totp/enableMFA

Enables TOTP and returns 10 new backup codes. You need sudo mode and a verified account. Sends a User Update Gateway event.

The body extends the sudo verification object with these fields.

FieldTypeDescription
secretstringBase32 TOTP secret your client generated (1-256 characters)
codestringCurrent TOTP code for secret, not a backup code (1-32 characters)
StatusBodyCondition
200MFA backup codes objectTOTP was enabled
400error responseINVALID_CODE on the path code, or TWO_FA_ALREADY_ENABLED when TOTP is already enabled
403error responseMFA_EMAIL_VERIFICATION_REQUIRED or ACCOUNT_VERIFICATION_REQUIRED when the account isn't verified

The rate limit is 10 requests per minute, on the user:mfa:totp:enable bucket.

POST/v1/users/@me/mfa/totp/disableMFA

Disables TOTP under sudo mode and returns 204. This deletes every backup code unless passkeys stay on as a second factor. Sends a User Update Gateway event.

The body extends the sudo verification object with this field.

FieldTypeDescription
code1stringCurrent TOTP code or unused backup code (1-32 characters)

1 Without a sudo token or mfa_method, code also proves sudo mode

StatusBodyCondition
204emptyTOTP was disabled
400error responseINVALID_CODE on the path code, or INVALID_MFA_CODE on the path mfa_code when code proves sudo mode
400error responseTWO_FACTOR_REQUIRED when TOTP isn't enabled

The rate limit is 10 requests per minute, on the user:mfa:totp:disable bucket.

POST/v1/users/@me/mfa/backup-codesMFA

Returns the account's backup codes, and replaces them with 10 new codes first when regenerate is true. You need sudo mode, and replacing the codes also needs a verified account.

The body extends the sudo verification object with this field.

FieldTypeDescription
regeneratebooleanWhether to delete the current codes and create 10 new ones
StatusBodyCondition
200MFA backup codes objectThe current or new codes were returned
403error responseMFA_EMAIL_VERIFICATION_REQUIRED or ACCOUNT_VERIFICATION_REQUIRED when regenerate is true and the account isn't verified

The rate limit is 6 requests per minute, on the user:mfa:backup_codes bucket.

POST/v1/users/@me/mfa/backup-codes/challenge

Emails a verification code to the account and returns a ticket. The body is empty or {}.

StatusBodyCondition
200MFA backup codes challenge objectThe code was sent
400error responseTWO_FACTOR_REQUIRED when the account has no second factor turned on
400error responseEMAIL_CODES_UNAVAILABLE when the instance doesn't send email
403error responseMFA_EMAIL_VERIFICATION_REQUIRED when the email address is unverified

The rate limit is 6 requests per minute, on the user:mfa:backup_codes_challenge:start bucket.

POST/v1/users/@me/mfa/backup-codes/challenge/resend

Emails a new code for the ticket and returns 204. The old code and any proof already issued for the ticket stop working.

FieldTypeDescription
ticketstringTicket from Start MFA backup codes challenge (1-256 characters)
StatusBodyCondition
204emptyA new code was sent
400error responseINVALID_OR_EXPIRED_TICKET when the ticket is unknown or expired

The rate limit is 6 requests per minute, on the user:mfa:backup_codes_challenge:resend bucket.

POST/v1/users/@me/mfa/backup-codes/challenge/verify

Checks the emailed code and returns the account's backup codes and a proof. A code works once.

FieldTypeDescription
ticketstringTicket from Start MFA backup codes challenge (1-256 characters)
codestringThe code from the email (1-256 characters)
StatusBodyCondition
200MFA backup codes verification objectThe code was accepted
400error responseINVALID_VERIFICATION_CODE, VERIFICATION_CODE_EXPIRED or VERIFICATION_CODE_NOT_ISSUED

The rate limit is 20 requests per minute, on the user:mfa:backup_codes_challenge:verify bucket.

POST/v1/users/@me/mfa/backup-codes/challenge/regenerate

Replaces the account's backup codes with 10 new ones. The ticket and proof can be used again until the ticket expires.

FieldTypeDescription
ticketstringTicket from Start MFA backup codes challenge (1-256 characters)
verification_proofstringProof from Verify MFA backup codes challenge code (1-256 characters)
StatusBodyCondition
200MFA backup codes objectThe codes were replaced
400error responseINVALID_PROOF_TOKEN, INVALID_OR_EXPIRED_TICKET or TWO_FACTOR_REQUIRED

The rate limit is 6 requests per minute, on the user:mfa:backup_codes_challenge:regenerate bucket.

GET/v1/users/@me/mfa/webauthn/credentials

Returns the account's WebAuthn credentials.

StatusBodyCondition
200array[WebAuthn credential object]The credentials were returned

The rate limit is 40 requests per 10 seconds, on the mfa:webauthn:list bucket.

POST/v1/users/@me/mfa/webauthn/credentials/registration-optionsMFA

Returns a registration challenge for Register WebAuthn credential. You need sudo mode and a verified account or one that only needs a password, and the response never has a sudo token.

The body is a sudo verification object.

StatusBodyCondition
200WebAuthn registration options objectThe challenge was created
400error responseWEBAUTHN_CREDENTIAL_LIMIT_REACHED when the account has 10 credentials
403error responseMFA_EMAIL_VERIFICATION_REQUIRED when the account needs to confirm or add an email address to be verified

The rate limit is 20 requests per 10 seconds, on the mfa:webauthn:registration_options bucket.

POST/v1/users/@me/mfa/webauthn/credentials

Adds a WebAuthn credential to the account and returns 204. A challenge works only once, even when registration fails, and you need new options to try again. Sends a WebAuthn Credentials Update Gateway event, and a User Update event for the account's first credential.

FieldTypeDescription
responseWebAuthn registration response objectThe authenticator's response to the challenge
challengestringChallenge from Create WebAuthn registration options (1-1024 characters)
namestringName for the credential (1-100 characters)
StatusBodyCondition
204emptyThe credential was registered
400error responseINVALID_WEBAUTHN_CREDENTIAL, INVALID_WEBAUTHN_PUBLIC_KEY_FORMAT or INVALID_WEBAUTHN_CREDENTIAL_COUNTER
400error responseWEBAUTHN_CREDENTIAL_LIMIT_REACHED when the account has 10 credentials
403error responseMFA_EMAIL_VERIFICATION_REQUIRED when the account needs to confirm or add an email address to be verified

The rate limit is 10 requests per minute, on the mfa:webauthn:register bucket.

PATCH/v1/users/@me/mfa/webauthn/credentials/{credential_id}MFA

Renames a WebAuthn credential under sudo mode and returns 204. Sends a WebAuthn Credentials Update Gateway event.

The body extends the sudo verification object with this field.

FieldTypeDescription
namestringNew name for the credential (1-100 characters)
StatusBodyCondition
204emptyThe credential was renamed
404error responseUNKNOWN_WEBAUTHN_CREDENTIAL

The rate limit is 20 requests per 10 seconds, on the mfa:webauthn:update bucket.

DELETE/v1/users/@me/mfa/webauthn/credentials/{credential_id}MFA

Deletes a WebAuthn credential under sudo mode and returns 204. Deleting the last credential also turns off passkeys as a second factor, and without TOTP it deletes every backup code. Sends a WebAuthn Credentials Update Gateway event, and a User Update event when it deletes the last credential.

The body is a sudo verification object.

StatusBodyCondition
204emptyThe credential was deleted
404error responseUNKNOWN_WEBAUTHN_CREDENTIAL

The rate limit is 10 requests per minute, on the mfa:webauthn:delete bucket.

PUT/v1/users/@me/mfa/webauthn/two-factorMFA

Turns passkeys on or off as a second factor at password login. You need sudo mode, and turning it on also needs a verified account. Turning it on creates 10 backup codes when the account has no unused codes, and turning it off without TOTP deletes every backup code. Sends a User Update Gateway event when the setting changes.

The body extends the sudo verification object with this field.

FieldTypeDescription
enabledbooleanWhether passkeys are a second factor at password login
StatusBodyCondition
200WebAuthn two-factor objectThe setting was saved
400error responseNO_PASSKEYS_REGISTERED when turning it on with no credentials
403error responseMFA_EMAIL_VERIFICATION_REQUIRED or ACCOUNT_VERIFICATION_REQUIRED when turning it on and the account isn't verified

The rate limit is 10 requests per minute, on the mfa:webauthn:two_factor bucket.

GET/v1/users/@me/sudo/mfa-methods

Returns the sudo mode proofs the account can give.

StatusBodyCondition
200sudo MFA methods objectThe methods were returned

The rate limit is 20 requests per minute, on the sudo:mfa:methods bucket.

POST/v1/users/@me/sudo/email-code

Emails a 6-digit code for sudo mode to an account that has no password and no second factor. The code lasts 10 minutes and allows 5 wrong tries.

StatusBodyCondition
200sudo email code objectThe code was sent
400error responseSUDO_SECOND_FACTOR_REQUIRED when the account has a second factor turned on
400error responseEMAIL_CODES_UNAVAILABLE when the account has a password or no verified email address, or the instance doesn't send email
429rate limit responseToo many codes were sent or too many wrong codes were tried

The rate limit is 5 requests per 15 minutes, on the sudo:email_code bucket.

Create sudo WebAuthn authentication options

Section titled “Create sudo WebAuthn authentication options”
POST/v1/users/@me/sudo/webauthn/authentication-options

Returns a sudo challenge that lists the account's passkeys. Send the signed result as webauthn_response and webauthn_challenge in a sudo verification object.

StatusBodyCondition
200WebAuthn authentication options objectThe challenge was created
400error responseNO_PASSKEYS_REGISTERED

The rate limit is 10 requests per minute, on the sudo:webauthn:options bucket.