Accounts, verify email without a link but a validation code


#1

Hello, I want to have a custom experience on user registration and provide a custom validation code instead of a verification link.

So, the email will have inside a code, for example

123456

That the user must enter to validate it’s account, password change, etc.

Is it possible to store the token (generated by Accounts) in the server, and then call Accounts.verifyEmail(token) when the user enters the validation code provided?

A method that returns the token using the validation code as input?

Thanks.


#2

For a React Native app, I have a custom verification email(with Accounts.emailTemplates.verifyEmail.text) which sends the first 4 characters of the token upon registration: user.services.email.verificationTokens[0].token.substr(0, TOKENLENGTH).toUpperCase()

Then they are taken to a new screen where they can enter the code. This fires of a Meteor method call which checks to see if the digits match, the user is marked as verified:


const TOKENLENGTH = 4

Meteor.methods({
  'verifyAccount' (code){

    check(code, String)
    const user = Meteor.users.findOne(this.userId)

    if (user.services.email.verificationTokens.length<1)
      throw new Meteor.Error('Error', 'No Token found')

    const valid = user.services.email.verificationTokens.filter( item =>
       item.token.substr(0, TOKENLENGTH).toUpperCase() === code.toUpperCase()
    )
    if (!valid.length){
      log.warn(`verifyAccount: Wrong Code ${code} by ${user.profile.name}`)
      throw new Meteor.Error('Error', 'Wrong code')
    }
    Meteor.users.update(
      { _id: this.userId },
      { $set: {
        'services.email.verificationTokens': [],
        'emails.0.verified': true
      } }
    )
    return true
  }
})

#3

Ok, I didn’t realize you can update the fields directly in the users collection.
So, any generated token may do the job.
Thanks!


#4

@eleventy Good job! I was wondering how verifying the code this way is working out for you, and would you consider this option for web as well? Also does the expiration on the code work like it does for the link, or do you have a add more code for the expiration?


#5

It’s working pretty well. But I would only use it for an App, not for web. The email link takes you directly to your meteor website, and the user is logged in automatically. So for comfort on the web, I use the build in method. For an ios/android app, this doesn’t work: the link opens a browser window to your meteor website, not your native app, and it confuses users.

Moreover, I trust Meteor’s security skills more than my own, so wherever possible, I try to stick to Meteor’s build-in functionality.

Expiration is not handled in the basic example above, but it’s easy to add, it’s right next to the token:

"services" : {
		"password" : {
			"bcrypt" : "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
		},
		"email" : {
			"verificationTokens" : [
				{
					"token" : "ZzjqQ4l9IKX4oBYSZRFDBqWyIUSE4ciklrGN8HAzJI5",
					"address" : "XXXXXXXXXXXXXXXX@XXX.XXX",
					"when" : ISODate("2017-02-18T14:40:50.593Z")
				}
			]
		}
	},


#6

@eleventy Thanks for your response! I appreciate the detailed explanation and code snippet!!