Hi all,
I would to share an easy way that I have found to offer two-factor authentication using the new accounts-passwordless package.
- User enters email and password in login form
- Client calls Meteor.loginWithPassword()
Meteor.loginWithPassword("test@email.com", "abc123");
- I have defined an Accounts.validateLoginAttempt() function
// Simple function to enable 2 factor authentication via email
Accounts.validateLoginAttempt(function(attempt: Accounts.IValidateLoginAttemptCbOpts){
if(attempt.type === "password") {
// Only if the initial authentication passed, send an email with the 2FA token (reduces costs!)
if(attempt.allowed) {
try {
// This function is the server-side implementation of Accounts.requestLoginTokenForUser() on the client
// See https://github.com/meteor/meteor/blob/19e25478d8d5fabd14142a4452b491a919fbf711/packages/accounts-passwordless/passwordless_server.js#L127
Meteor.call("requestLoginTokenForUser", {
selector: attempt.methodArguments[0].user,
userData: {},
options: {
userCreationDisabled: true
}
});
} catch (e: any) {
console.log(e);
}
}
// Regardless of whether the initial authentication succeeded or failed, return 2FA challenge error type
// TODO - implement timeout for invalid credentials case to avoid timing attack
throw new Meteor.Error("2FA", "2-factor authentication required. Please check email.")
}
else {
// in any other login case (e.g. "passwordless", "resume", etc.), we just return the login result
return attempt.allowed
}
});
- When client receives “2FA” error from meteor, it shows a token input form (or a message to check email and just use the link params to login)
- User receives the login email
- User enters token into the token input form (or clicks link)
- Client calls Meteor.passwordlessLoginWithToken()
Meteor.passwordlessLoginWithToken("test@email.com", "EFC932", (e)=>{if(e){console.log(e)}});
- Now Accounts.validateLoginAttempt() should return true, since I didn’t use a password and the attempt.type is “passwordless”, the token is correct, and the user is logged in.
And there you have it, just one small Accounts.validateLoginAttempt function is needed to do 2FA with email.