MFA Passkey + OTP
This flow ensures users authenticate securely using OTP verification followed by passkey authentication.
This example can be used for both user signup (first-time registration) and signin (subsequent logins).
- Request OTP – Initialize an MFA session and sends an OTP via email.
- Verify OTP – The user enters the OTP, and LoginID validates it.
- Authenticate with Passkey – After OTP verification, passkey authentication is performed.
- Complete Authentication – If passkey authentication is successful, the authentication process is complete.
Prerequisites
- Create an application to obtain a base URL. The SDK uses this base URL to interact with the LoginID authentication service.
- Create an API key with at least the external-verify scope. You’ll need this to request authorization tokens from your backend.
Setup SDK
- Javascript
npm i @loginid/websdk3
Import and initialize an instance:
import { LoginIDMfa } from "@loginid/websdk3";
const lid = new LoginIDMfa({
baseUrl: process.env.LOGINID_BASE_URL,
});
Full Example
We begin a pre-authentication session and send an OTP to the user's email. If successful, the user proceeds to the OTP verification step.
import { LoginIDMfa } from "@loginid/websdk3";
const config = {
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL
};
const mfa = new LoginIDMfa(config);
const OtpRequest = ({ onOtpSent }) => {
const [username, setUsername] = useState("");
const [error, setError] = useState("");
const handleRequestOtp = async () => {
try {
// Step 1: Begin MFA session and request email OTP
await mfa.beginFlow(username);
await mfa.performAction("otp:email");
console.log("OTP sent successfully.");
onOtpSent(username);
} catch (err) {
console.error("OTP request failed:", err);
setError("Failed to send OTP. Please try again.");
}
};
};
Once the user receives the OTP, they enter it to verify their identity. If the OTP is valid, the user proceeds to passkey authentication, ensuring a secure multi-factor authentication flow.
import { LoginIDMfa } from "@loginid/websdk3";
const config = {
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL
};
const mfa = new LoginIDMfa(config);
const OtpVerification = ({ username, onOtpSuccess }) => {
const [otpCode, setOtpCode] = useState("");
const [error, setError] = useState("");
const handleVerifyOtp = async () => {
try {
// Step 2: Verify entered OTP from user
await mfa.performAction("otp:verify", { payload: otpCode });
console.log("OTP verified successfully.");
onOtpSuccess(username);
} catch (err) {
console.error("OTP verification failed:", err);
setError("Invalid OTP. Please try again.");
}
};
};
After OTP verification, the user is prompted to authenticate using their passkey. If the passkey authentication is successful, the user is fully authenticated.
import { LoginIDMfa } from "@loginid/websdk3";
const config = {
baseUrl: process.env.REACT_APP_LOGINID_BASE_URL
};
const mfa = new LoginIDMfa(config);
const PasskeyAuth = ({ username, onAuthSuccess }) => {
const [error, setError] = useState("");
const handlePasskeyAuth = async () => {
try {
// Step 3: Perform passkey creation/authentication to complete MFA
await mfa.performAction("passkey:reg");
console.log("Passkey authentication successful.");
onAuthSuccess();
} catch (err) {
console.warn("Passkey authentication failed, retrying OTP.");
setError("Passkey authentication failed. Try again.");
}
};
};
When the passkey authentication succeeds, the process is complete, and the user is successfully logged in.
import React, { useState } from "react";
import OtpRequest from "./OtpRequest";
import OtpVerification from "./OtpVerification";
import PasskeyAuth from "./PasskeyAuth";
const MFAFlow = () => {
const [username, setUsername] = useState(null);
const [otpVerified, setOtpVerified] = useState(false);
const [authComplete, setAuthComplete] = useState(false);
return (
<div>
{!username && <OtpRequest onOtpSent={setUsername} />}
{username && !otpVerified && (
<OtpVerification username={username} onOtpSuccess={() => setOtpVerified(true)} />
)}
{otpVerified && !authComplete && (
<PasskeyAuth username={username} onAuthSuccess={() => setAuthComplete(true)} />
)}
{authComplete && <h3>Authentication Successful!</h3>}
</div>
);
};