Skip to content

Custom MFA factor: send a second factor through any channel_

Appwrite now supports a custom MFA factor. Appwrite generates and verifies the code, and your function delivers it through WhatsApp, a voice call, or any provider you choose.

Appwrite Authentication supports email, phone (SMS), and TOTP authenticator apps as second factors. Those cover most applications, but not all of them. A fintech application wants WhatsApp, because SMS delivery is unreliable in its market. A logistics company wants a voice call to a landline. An enterprise wants its own internal push system, because the security team does not accept a third-party channel.

Until now, Appwrite Authentication did not support those channels.

The custom MFA factor changes that. Appwrite generates the code and verifies it. You decide how the code reaches the user.

What the custom factor does

The custom factor is a fifth value for the factor parameter of a multi-factor challenge, next to email, phone, totp, and recoveryCode.

When your application creates a challenge with the custom factor, Appwrite generates a six-digit code, encrypts it, and stores it. Appwrite sends no email and no SMS. A new server-only endpoint gives that code to your backend, and your backend sends it through your channel. Verification then works exactly like every other factor.

Custom token login already uses the same pattern for sign-in. The custom factor applies that pattern to the second factor.

Set up the function

The delivery step needs a server. An Appwrite Function is the simplest option, and it removes the need to store an API key.

  1. In the Appwrite Console, open Functions.
  2. Create a function with the Node.js or Rust runtime.
  3. Open the Settings tab of the function.
  4. Under Scopes, enable users.read.
  5. Under Execute access, add the users role. Signed-in users can then call the function.

The scope matters. Appwrite passes a temporary API key to every execution in the x-appwrite-key header, and that key carries the scopes of the function. Your function reads the challenge code with that key, and you do not store a permanent secret in an environment variable.

The flow

Four calls, in this order.

  1. Client. account.createMFAChallenge({ factor: 'custom' })
  2. Client. functions.createExecution with the challenge ID
  3. Function. users.getMFAChallenge(userId, challengeId)
  4. Client. account.updateMFAChallenge({ challengeId, otp })

There is no event trigger for challenge creation. Your application calls the function. The function then reads the code. Use createExecution, and not the function domain. createExecution puts the ID of the signed-in user in the x-appwrite-user-id header. A direct call to the function domain leaves that header empty.

Create the challenge

Call this after the user completes the first factor.

The response carries the challenge ID and the expiry time. It carries no code.

Call the function

Keep the execution synchronous. Your application then knows that the delivery succeeded before it shows the code input field.

Read the code and deliver it

The function reads the code and sends it.

Complete the challenge

The user reads the code on their phone and types it.

Availability

The custom MFA factor is available in Appwrite Cloud. Update your server and client SDKs to latest.

Resources

Read next

Introducing Appwrite Explorer

Eldad Fux

Appwrite Explorer brings the Appwrite REST API into the Console. Browse every endpoint, build requests with guided forms, send live calls against your project, and inspect responses without leaving the browser.

8 min read

Introducing Appwrite Terminal

Eldad Fux

Appwrite Terminal runs the Appwrite CLI directly inside the Console. Your session and project context are preconfigured, with keyboard-first controls and multi-tab workflows, so you can inspect resources without leaving the project.

7 min read

Ready to build?_