Super-simple idea for Meteor two-factor login with email

Hi all,

I would to share an easy way that I have found to offer two-factor authentication using the new accounts-passwordless package.

  1. User enters email and password in login form
  2. Client calls Meteor.loginWithPassword()
Meteor.loginWithPassword("test@email.com", "abc123");
  1. 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
    }
  });
  1. 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)
  2. User receives the login email
  3. User enters token into the token input form (or clicks link)
  4. Client calls Meteor.passwordlessLoginWithToken()
Meteor.passwordlessLoginWithToken("test@email.com", "EFC932", (e)=>{if(e){console.log(e)}});
  1. 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.

11 Likes