Fallback by email
When WebAuthn is not available, use the email fallback. All methods return Result<T, TryMellonError>.
Flow
- Start the flow: send OTP to the user’s email.
- Ask the user for the code.
- Verify the code and get a session token.
- Send the session token to your backend (same as with passkeys).
Example
The backend requires both userId and email for sending the OTP. Use the same value for both when the user identifier is their email.
// 1. Send OTP (userId = external id, email = where to send the code)
const startResult = await client.fallback.email.start({
userId: 'user_123',
email: 'user@example.com',
})
if (!startResult.ok) {
console.error(startResult.error)
return
}
// 2. Get code from user
const code = prompt('Enter the code sent by email:')
// 3. Verify
const verifyResult = await client.fallback.email.verify({
userId: 'user_123',
code: code,
})
if (!verifyResult.ok) {
console.error(verifyResult.error)
return
}
// 4. Send to backend
await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionToken: verifyResult.value.sessionToken }),
})
Combined flow (passkey with fallback)
async function authenticateUser(userId: string, email: string) {
if (!TryMellon.isSupported()) {
return await authenticateWithEmail(userId, email)
}
const authResult = await client.authenticate({ externalUserId: userId })
if (authResult.ok) return authResult
if (
authResult.error.code === 'PASSKEY_NOT_FOUND' ||
authResult.error.code === 'NOT_SUPPORTED'
) {
return await authenticateWithEmail(userId, email)
}
return authResult
}
async function authenticateWithEmail(userId: string, email: string) {
const startRes = await client.fallback.email.start({ userId, email })
if (!startRes.ok) return startRes
const code = prompt('Enter the code sent by email:')
return await client.fallback.email.verify({ userId, code })
}