A few days ago, I saw an article about someone’s Playstation Network account getting stolen. The problem wasn’t so much that the account got stolen, as this apparently happens more often than not, but that Sony has created a system so convoluted that it’s possible for the thief to keep your account, without you having any recourse, not even after you prove your name, purchases, and anything else about the account.
Having worked in web security for years, I know how hard it is to get authentication right, especially when users will find ingenious ways to defeat your system, such as storing their “do not store these codes on your phone” two-factor authentication (2FA) codes on the phone and then throwing the phone in the ocean. Another user surprised me when, instead of properly setting up their authenticator app, they brilliantly used one of the ten backup codes to finish their 2FA setup (and didn’t even store the rest), thus locking themselves out of their account immediately. I fixed that bug immediately and found new respect for the bug-finding abilities of users.
Those (and many more) occurrences have made it painfully obvious to me that securing an authentication system is very hard UX, and, since the user is always right, we need to find ways to make systems that are both secure and easy to use. While working for my previous employer, an encrypted communications company called Silent Circle, we had to find ways to solve this problem, and we arrived at something I believe provides a very good balance between security and usability. I will explain how this system works, and urge you to implement something similar for your authentication, especially if it’s protecting high-value accounts like Playstation Network’s.
The conflicting goals of authentication
Broadly, an authentication system has to achieve two competing goals:
- Make it easy for a user to log in to their account, even if they forget their credentials.
- Make it hard for a third party to log in to a user’s account, even if they have (some of) the user’s credentials.
These two goals are an obvious tradeoff, because the easier you make it for people who forgot their credentials to access an account, the easier it is for an attacker to pretend they are a user who forgot their credentials and gain access to the same account. Conversely, the harder it you make it for attackers, the harder it is for legitimate users who just lost their password.
Fortunately, there are ways to make an attacker’s life harder without significantly impacting legitimate users.
Securing the account
To start out, we need to take some rudimentary steps to ensure the authentication process meets some minimum security threshold. This is essential, because a fundamentally insecure authentication process helps nobody, and the way to do this is to make sure we implement one or more of the following two-factor authentication methods, in order of security/desirability:
- OATH (TOTP/HOTP), i.e. those six digits you use to log in to your account.
- U2F, a way to provide two-factor authentication that can be based on physical USB keys, and is much more secure than OATH.
- WebAuthn/FIDO2, which is great and more secure than anything else but hasn’t come out properly yet.
If you’re wondering why SMS-based auth is not on the list above, it’s because SMS is how you lose users’ accounts. For every time you have implemented SMS-based two-factor authentication, you need to quit one job. Do not use/implement/touch SMS-based auth, it is less secure than no two-factor auth.
I cannot stress this enough.
If you want, you can nudge the user towards using more secure passwords, just make sure to not be too strict about it, or you’ll be counter-intuitively decreasing security. A simple warning that their password might be too weak is fine, and I also urge you to use Troy Hunt’s excellent Pwned Password API, which can securely tell you whether the password your user is trying to set has already appeared in any lists of compromised passwords, without divulging the password itself.
After we’ve done the minimum above, we’re ready to start helping users regain access to their accounts.
Making it easier for users to recover accounts
Given that it’s a fact of life that users will lose their two-factor keys/apps no matter how dire you make the warnings, you will be greatly inconveniencing users if you make it impossible for them to reset their accounts. You should have your support staff verify that the user knows some detail about their account that is not on their actual account page (ie that’s not something an attacker who has access to their account can see) before resetting their two-factor auth or password.
However, at Silent Circle, we added a second way to allow prudent users to recover their account: A long, random, single-use recovery key. This key is on a dedicated, printable page, along with a QR code that will immediately take the user to a “password reset” page and disable 2FA on the account.
The user indicates that they have stored this key safely by clicking a button, and the code is not shown any more. This way, if a user’s account is compromised and the attacker locks the user out, the user can still use the printed page to reset their account, change their password and add two-factor auth to make the account more secure.
You can prevent the attacker from printing such a recovery page by only allowing it to be printed N days after some important credential (such as the email address) has changed, to handle the case where the attacker logs in, changes the password/email address and then prints a recovery page in case they lose the account later.
However, the recovery page will not prevent social engineering attacks against support (which is a much more common way to steal an account than, say, compromising 2FA) on its own. For that, we need the next measure.
Making it harder for attackers to impersonate users
The second thing we did to make sure that we had eliminated social engineering attacks was to add an option to completely disable account recovery by support. The actual setting at Silent Circle is a bit more fine-grained than this, allowing you to select whether you’d like to allow recovery by various methods of verification, but there’s an overriding “disallow everything” checkbox.
Checking this box programmatically disables the support staff’s interface for resetting an account, ie it literally grays out the “reset account” button so they have no way to reset, even if they wanted to. A developer with production database access could still go in and change the data directly, but those tend to be a smaller group and thus easier to educate on this.
This box should come with adequate warning that nobody will be able to help the user if the credentials are lost, but it is a great way for users who use a password manager and reliable two-factor (or have printed out the recovery page) to indicate that they believe that the probability of account compromise is higher than the probability of losing their credentials.
What’s especially saddening about the Playstation Network account compromise above was that the user had taken steps to secure their account, but the thieves had enough knowledge about how to game the support system that none of that mattered, and the user lost the account anyway. Perversely, Sony in the end figured out that this account was high-risk, and did end up “locking” it to the then owner so no transfer could occur again, except that owner was the thief.
With a system like the one I described above, the user would be able to disallow resets via support when initially setting up the account, preventing any theft, or at least would have been able to use the recovery key the first time the account was stolen to recover it and then disable resets.
In either case, the problem would have been avoided, and the account would have remained with the rightful owner.