Multi-factor authentication
Alchemy Signer supports Time-based One-Time Passwords (TOTP) multi-factor authentication (MFA). This lets you prompt users to set up a TOTP authenticator (e.g. Google Authenticator) as an additional security factor.
Setting up Multi-Factor Authentication
- Prerequesit: Your user is already logged in with at least one authentication factor (e.g. email OTP, email magic-link).
1. Add a new TOTP factor
Once the user is authenticated, you can call addMfa
to enable TOTP. This returns factor details including an ID and setup information that your app can display to the user (e.g. a QR code or otpauth
link that the user can scan in Google Authenticator).
import { signer } from "./signer";
const { multiFactors } = await signer.addMFA({
multiFactorType: "totp",
});
// Display the QR code or secret to the user
const totpUrl = result?.multiFactors[0].multiFactorTotpUrl;
const multiFactorId = result?.multiFactors[0].multiFactorId;
You can show the multiFactorTotpUrl
in your UI as a QR code or link for the user to add it to their authenticator app.
2. Verify the TOTP setup
Once the user has scanned the TOTP secret, have them enter the 6-digit code from their authenticator app. Then call verifyMfa
:
import { signer } from "./signer";
await signer.verifyMfa({
multiFactorId, // from addMfa
multiFactorCode: "123456",
});
3. Remove a TOTP factor
If a user wants to disable TOTP, call removeMfa
with the multiFactorId
you want to remove:
import { signer } from "./signer";
await signer.removeMfa({
multiFactorIds: [multiFactorId],
});
4. Get a list of existing MFA factors
import { signer } from "./signer";
const { multiFactors } = await signer.getMfaFactors();
Authenticating Email OTP with multi-factor TOTP
Step 1: Send an OTP to user's email
import { signer } from "./signer";
signer.authenticate({
type: "email",
emailMode: "otp",
email: "user@mail.com",
});
Step 2: Submit the email OTP code
import { signer } from "./signer";
signer.authenticate({
type: "otp",
otpCode: "EMAIL_OTP_CODE",
});
Step 3: Submit the TOTP code (authenticator app code)
import { signer } from "./signer";
const user = await signer?.validateMultiFactors({
multiFactorCode: totpCode,
});
Authenticating Email magic-link with multi-factor TOTP
When calling authenticate
with emailMode="magicLink"
, you can catch a MfaRequiredError
. Then you can collect the TOTP code and resubmit.
import { MfaRequiredError } from "@account-kit/signer";
import { signer } from "./signer";
const promptUserForCode = async () => {
// Prompt user for TOTP code
// const totpCode = await promptUserForCode();
return "123456";
};
try {
await signer.authenticate({
type: "email",
email: "user@mail.com",
emailMode: "magicLink",
});
} catch (err) {
if (err instanceof MfaRequiredError) {
// Prompt user for TOTP code
const totpCode = await promptUserForCode();
const { multiFactorId } = err.multiFactors[0];
await signer.authenticate({
type: "email",
emailMode: "magicLink",
email: "user@mail.com",
multiFactors: [
{
multiFactorId,
multiFactorCode: totpCode,
},
],
});
} else {
// handle other errors
}
}
Authenticating Social Login with multi-factor TOTP
When a user has MFA enabled using an authenticator app, the authentication process for social login is seamless. Unlike email authentication flows, you don't need to handle the MFA challenge manually in your code.
The TOTP verification happens automatically during the OAuth callback flow:
- The user authenticates with the social provider (Google, Facebook, etc.)
- After successful provider authentication, they're prompted for their TOTP code on the OAuth callback page
- Once verified, authentication completes normally
Simply use the standard social login authentication as shown in the Social Login Authentication guide:
import { signer } from "./signer";
await signer.authenticate({
type: "oauth",
authProviderId: "google", // Choose between the auth providers you selected to support from your auth policy
mode: "redirect", // Alternatively, you can choose "popup" mode
redirectUrl: "/", // After logging in, redirect to the index page
});