Better email verification workflows

The general idea of email verification boils down to storing an “inactive” user, and activate them when they verify their email. While this might be the most popular way to do it, it creates a very bad user experience. This article aims to tell you why, and how you can make your verification workflow better.

This article is language agnostic. Therefore, any ideas mentioned here can be implemented in any language and framework.

What’s the fuzz about?

Let’s say that I have your email address, and you’ve not created an account yet on Github. Because I know your email address, I create an account with it, even though I am not the actual owner.

Alright! Github is asking me to enter a verification code they sent to your email address. But under the hood, an user account has actually been created with bad data. Just to confirm this, let’s say that you want to create an account with your email now. So, you open up a new browser, enter your email and continue to signup.

as you can see here, Github issues you an error — an account has already been created with your email. This account has not been verified but is still stored.

In order to get access to your account, you can reset your password on Github. But this just makes your users feel insecure (your application stores unverified user data) and creates a bad user experience.

It’s not just Github that implements email verification in this way, however. Companies like Discord, Reddit, Quora, Disqus, StackOverflow and much more do the same, and it pains me that even in 2021, there has not been a better verification workflow enforced prominently.

Possible solutions

In this article, We will go about other ways we can possibly implement an email verification workflow, as well as their pros and cons. Most of the workflows mentioned have been based off how other companies have implemented their email verification system.

workflow 1

  • user data is validated, and stored in the session temporarily.
  • an email verification link is sent to the user.
  • when the link appears to be valid, the session data is saved as an user account and (the session data) is discarded.

pros of this workflow

  • we don’t store bad user data.
  • only the same browser which tried to register for an account can verify the associated email address (because of session cookies).

cons of this workflow

  • you need a session management system in order to make this work, which means that mobile apps cannot be supported.

workflow 2

To understand this workflow, let’s imagine that the following data model is in place.
  • user enters only their email address and clicks on a register button.
  • a call is made to the backend to check whether an account already exists for the email and the hasPassword field is set to true. If so, the user is asked to enter their password and login instead.
  • otherwise, an user account is created with the hasPassword field set to false. A temporary login code is created and set as the user’s password. This login code is sent via email and the user is required to enter it.
  • after reaching the dashboard page, the user is asked to set their “actual” password, if their hasPassword field is set to false.

pros of this workflow

  • users can complete registration as long as their hasPassword attribute is set to false, so a bad user experience is not created.

cons of this workflow

  • this only works well with user models which have their email as their unique identifier.
  • we’re still storing unverified users in our database, though. This spends unnecessary resources which we could utilize.

workflow 3

  • user enters only their email address and clicks on a register button.
  • a JWT (Json Web Token) is created with a pair of RSA keys — a public and a private key. The subject for the token would be the entered email. This token is sent to the email.
  • the user clicks on their verification link (which generally looks like this: https://id.atlassian.com/signup/welcome?token=XXXX)
  • upon reaching this page, the token is verified using the public key which is made available to anyone.
  • when completing registration, the token is also passed as a field and is evaluated by the backend. If the token’s subject matches the entered email, the user account is created.

pros of this workflow

  • we don’t store the confirmation codes in our database.
  • the client can invalidate tokens immediately with the help of a public key.

cons of this workflow

  • because json web tokens are stateless, it is hard to ensure that a particular confirmation code can be created only once. We also don’t know if we created a confirmation code for a particular email recently.

an alternative version of this workflow also exists where the json web token is signed with the HS256 algorithm. This is what Scale AI uses, but this cuts down the ability to validate the token received at the client side (as token validation can only be done on the server side).

workflow 4

To understand this workflow, let’s imagine that the following data models are in place.
  • user enters required fields and clicks on the register button.
  • as the user types in values for fields, they get validated. This is necessary because fields will be stored internally until the user completes signup.
  • a call is made to the backend to send a confirmation code to the entered email. All the other fields are stored in an internal state.
  • when the user enters the confirmation code, a request is made to the backend containing the code, as well as the internally stored fields. This is when the user is created (assuming that the confirmation code was valid).

pros of this workflow

  • bad user data is never stored.
  • we can ensure that only one confirmation code can exist at one time per email address by adding a unique constraint to the Signup Code's email field.

cons of this workflow

  • a lot of race conditions are possible because fields are stored in an internal state, until the user enters their confirmation code. While the user is trying to enter a confirmation code, someone else might take their username!

Social logins

And that brings us to the end of this article! Hopefully, you could pick the right workflow for your project based on it’s needs. Is something missing? Please let me know in the comments below!

ReactJS and GraphQL Enthusiast | Experienced Python developer