By doing email verification every time, we ensure that we still have a working email address. If we use some email/username + password combination for login, we have to add some kind of regular “Is this still your email address?” feature, or find ourselves unable to contact our users.

So, for example, if we send an email asking for payment, the link can take them straight to the payment page, already logged in. This is the ideal situation, and we can do it with the tiniest amount of work (adding a query parameter to a URL in an email), because we can just re-use the existing login mechanism.

For any prompting or promotional emails that we send to a user, we can log them straight away in using this mechanism. As already discussed, this is not a reduction in security in the typical case. If we implement the system using a query string parameter containing a token and a generic middleware that checks the token, we can use this system on any page on the site with no extra work.

There are significant improvements for privacy concerns.

A typical email + password login system has some problems when it comes to privacy, because it is often possible for an attacker to determine that a certain person has an account with a web site. This can be often be done from several pages on the site:

The account creation form The log in form The password reset form

And it can be done in a number of ways:

By looking at the different error/validation messages that are returned by these pages, for the cases of existing or non-existing accounts. Even if the messages returned are identical, by doing timing attacks on the pages.

Fixing method 1 often results in UX problems — e.g. if a user doesn’t have an account and is trying to log in, we can no longer tell a user that they don’t have an account and need to create one, we can only tell them their email/password combination is incorrect, and leave them to struggle. Similarly with password reset. Our user encountering scenario 5 above now feels like this:

Fixing method 2 can be very hard. The use of strong password hashing makes a timing attack on the login page trivial if no precautions are taken. Django, for instance, was vulnerable to this for a long while. It now has rudimentary mitigation, which fixes trivial attacks, but a complete fix is very hard. Making the code paths for “yes we found a user record” and “no we didn’t” take exactly the same amount of time would be very hard, and an attacker who was in the same data centre as your server (where network transit noise is much reduced) would probably not have a hard time doing a timing attack on the current code.

However, with the system described in this post, these attacks, and the UX problems, are all completely mitigated. We send the verification email whether there is already an account or not, with exactly the same message (which doesn’t confuse the user), without looking up the account in the database first. We can check whether we need to create a new account or retrieve the old one when the email has been verified, so there is no timing attack possible on this part of the code.