- Published on
A Better Sign-up Flow
- Authors
- Name
- Abi Raja
- Follow me on Twitter @_abi_
Sometimes you have a website where you don't want to reveal whether someone has an account. Ashley Madison is one extreme example. No one would use a website to discreetly cheat on their spouse if their spouse could easily find out that they were cheating.
But it could be another high security corporate or national security situation as well. Say there is a website that helps you recruit CTOs. Knowing the CEO has an account on there now when they didn’t have one a week ago might cause the current CTO to start looking for new jobs. That’s a bit of contrived example but you get the point. Disclosure of account presence can have a real impact on the business in many circumstances.
One of the most commonly used off-the-shelf authentication solutions out there, Firebase, violates this desired non-disclosure. During login, once you type in an email and a password, the error message is “User is not registered” when the email doesn’t have an account but “Incorrect password” when the account exists but password is incorrect. I’m not sure why Firebase does this but like many aspects of Firebase auth, it’s poor design.
This problem is simple enough to fix. Login should never reveal information to the user about what went wrong per the OWASP guidelines. We can go with a generic "Username or password is incorrect" message instead.
But what about sign-ups? On a lot of websites, if you type in an email that already has an account, the website will tell you that an account with that email already exists. How can we avoid this disclosure?
What if you let a user sign up even if an email already has an associated account and then, in the final step, have them confirm their email? An imposter who don't have access to the email won't be able to verify their email and activate their account. In the case that the email has an existing account, we just discard the new sign up and send a warning email to the existing account holder about an attempted sign-up with their email. We've solved the disclosure problem: it's not possible to tell whether a particular email has an account on this website.
While this sign-up solves the disclosure problem, there are a few problems. First, account squatting. I can create an account for elon@tesla.com and never verify the email. When the real Elon Musk comes along to create his account, what do you do with the previously created unverified account? Is the first account an imposter or just the real Elon who forgot to complete the verification step?
You could run a cron job to delete accounts created that are unverified but that still prevents the real Elon from creating an account until the cron job runs. If it runs on a daily cadence, then real Elon is locked out from account creation for up-to a day.
Another problem is that people click on links in emails all the time without thinking and they just might click on a confirmation email from the imposters sign up process. Now an imposter has a verified account with their email.
Here's a better flow:
- Enter your email
- Verify email to continue with sign-up process
- Choose your password and complete the sign up process
This is great because until the email is verified, no account is created. So, no need for cron jobs. Only the email owner can create an account. Accidental clicks aren't a problem because whoever clicks on the email is the one picking a password. The process also reveals nothing about whether an email has an account or not.
One final thing to consider: in cryptography, side channel timing attacks leak information by taking a different amount of time when a username is incorrect vs. when the username is correct. That way, it's possible to mount an enumeration attack. In the above flow, it's still possible to leak information by going down a different code path when an email has an existing account vs. not so all the advice about timing attack prevention still applies here.