Authentication Security: Password Reset Best Practices and More

This article covers measures to secure an authentication server along with real-life examples.

You can find tons of tutorials on implementing an authentication server online, but not so much on how to actually secure one. Throughout our years developing, refactoring , or reviewing a user authentication flow (our own or others’), we’ve seen countless pitfalls. With some fellow developers, we aggregated a series of notable ‘traps’ below. We’ll go through each of them, identify possible security breaches and issues, and fix them.

What is Authentication?

Authentication is a process to validate a user on his/her identity claim, or in short, who you are. An authentication server offers such service through various flows (e.g., traditional username + password, passwordless, SSO, etc.).

Authentication vs Authorization

It’s confusing when someone says “auth” but you have no idea whether it refers to authentication or authorization. This StackOverflow piece provides a more in-depth insight on this.

Pitfalls in a ‘Forgot Password’ Flow and Best Practices to Address It

We’ll start with resetting user passwords and what security issues are often overlooked in this flow. Below are some notable issues we’ve seen in a ‘forgot password’ flow. All solutions are backed with references from OWASP’s ‘forgot password’ cheat sheet, and you should read them if you’re looking for password reset best practices.

✕ Allowing Login ID Guesses

Let’s assume that your ‘forgot password’ application form lets a user key in an email (i.e., the login ID and forgot password email recipient). If an account is registered under that email, a reset password link is sent to it. Otherwise, nothing happens.

A very 101 concept on security can be applied here, as suggested by OWASP: Always show a consistent message (i.e., “an email will be sent to this email if an account is registered under it.”) when an email is entered, whether the account exists or not. This prevents attackers from being able to match a login ID.

Give the ‘reset password’ link a reasonably short period of time. This reduces the chance of an attacker intercepting one and gaining access to it by resetting a password.

never put user id or other pii in a reset password link

A way to identify a password reset session is to pass a URL token as the query string of the URL, as suggested by OWASP.

Avoid using any personally identifiable information (PII). Never take chances when it comes to PII. Even if encryption is applied, it can still be broken/decrypted by attackers, where they can then use the PII to match a user from your system.

We’ve seen “encrypted” user IDs being used as the password reset token passed in a URL, which is not a very good idea, as aforementioned. In our case, the token encryption wasn’t done properly where a cryptographically broken algorithm (MD5) was adopted, which resulted in the quoted word to be encrypted in the last sentence.

Always use randomly generated ID as the identifier. Give each generated ‘reset password’ session a life span and prevent brute-force matching attempts on the ID by implementing rate-limit mechanisms on the URL token.

✓ Ensure Password Security Policy is Applied

Ensure that the same password policy is applied in all password setting stages, no matter if it’s during account creation or password reset. Although this is more client-related and a bit of a side-track, it’s worth mentioning that a strong password policy should enforce a user password to have:

  1. A minimum length of 8 characters
  2. A not-too-low maximum length to discourage users from creating longer passphrases
  3. A strength meter (i.e., zxcvbn) to measure the password complexity
  4. Common words that are banned

The above password rules are suggested by OWASP and Microsoft. Enforcing password history and rotation is another good measure to ensure passwords are secure enough.

✓ Invalidate existing sessions

Upon a successful reset attempt, remind the user to review all existing logged-in sessions. It’s also common that all sessions are invalidated during this stage. Again, this is backed by OWASP.

✕ Password Not Hashed Properly

Password hashing is frequently discussed in authentication security. Like the previous point we mentioned, some systems may ignore the importance of hashing user passwords. Hashing a password with a not-so-strong algorithms like MD5 is not recommended.

A simple and effective solution is to choose a hashing function widely regarded as secure, like argon2 or bcrypt, as suggested by OWASP. You can also apply key stretching with salts to further secure your passwords.

You might be wondering: Why bcrypt, but not SHA-256 or SHA-512? One argument is that the SHAs’ computation can be accelerated by a GPU, while bcrypt’s can’t. This puts the attackers (those trying to compute and match the hash value of your password) at a disadvantage as they can’t guess it quickly or easily.

This answer on StackExchange sums up the comparison of PBKDF2 vs bcrypt vs SHA256 pretty well. Here’s another good read on password hashing and some common hashing algorithms.

✕ No Expiry on Access Token

Let’s assume that you are generating access tokens properly with safe encryption. If there’s no expiry mechanism, the tokens that were already generated will haunt you forever. This is literally giving hackers unlimited time to pull off a token sidejacking. Just imagine an attacker getting their hands on an access token! They can authenticate themselves and go into your system and do whatever they want. This is quite likely to happen. Just open your cookies manager on your favorite browser and check how many access tokens are stored there.

Even if your machine is kept safe and all transits are conducted with HTTPs, access token with no limited life span can still pose serious threats. Even if your connection is protected by HTTPs, with enough computing power (which isn’t hard to come by nowadays) and time, an attacker can intercept your exchanged data and crack your sessions/tokens out of it. 

To prevent attacks like this, you can set a reasonably short expiry time and rotate your sessions. That way, it becomes virtually impossible for someone to decrypt sessions/tokens from communication data before they expire. Let’s say your tokens live for an hour only. The attacker has to intercept the traffic, break your encryption, and finally log in with the cracked credentials — all within 60 minutes, which is onerous.

✕ Hard-coded Secrets / Tokens

Sometimes a developer might have a tight schedule and need to rush things through. However, hard-coding secrets and any sensitive information in your code is never ever a good idea. One of our horror stories is that we have seen a JWT hard-coded in a function.

If you host your own version control server and it’s hosted with plain HTTPs; or some developer includes .git in deployments or some other operations so that it got leaked, your source code may be leaked, which will result in exposing secrets. That means your system is pretty much cracked wide open, with all security measures null and void.

So, don’t hard-code secrets / tokens! Also, serve with HTTPs and don’t include git files to deployments!

Consider encrypting your secrets (we often use blackbox) in file/s separate from the source code if they need to be shared. You can also generate them on the fly and gitignore them during development.

✕ Sensitive Information in Log

To debug and get an idea on a data flow, developers love to log things. Don’t log sensitive information though! Imagine you have this console.log(jwt) somewhere in your JS code and the web app gets deployed. Always keep such data hidden by sensitive filters.

✓ Apply Data Masking

By introducing a SensitiveContent interface and encapsulating all sensitive data values with it, you can force developers to think twice before logging sensitive information.

Examples of Sensitive Data

Below are some examples of data that should never be logged. A more complete list can be found in in the Logging cheat sheet from OWASP:

  • Tokens
  • Passwords
  • Connection strings
  • Secret keys
  • Bank/card data
apply data masking on all sensitive data

✕ No Guarding Measures Against IDN Homograph Attack

This authentication-related attack is a mind-blower if you’ve never heard of it, and often overlooked by developers. Let’s say there’s an admin of the authentication server called “admin e,” who posts regular updates and release notes. Can you tell the difference between the words below?

аdmin е vs admin e

You can visualize the difference between the two usernames by looking at each Unicode. The second row item is the actual “admin e,” while the first one uses Cyrillic characters to fake the username. I have used another font family in the table below to show their font-level difference as well.

Username Unicode
admin e u0061u0064u006du0069u006e u0065
аdmin е u0430u0064u006du0069u006e u0435

Simply disallow the input of these characters to defend against this kind of attack.

✓ Password Rate Limiting

Sometimes password rate limiting is non-existent in an auth system and it’s never a good idea. You’re basically inviting brute forcers to guess a password over and over again without limitations, which boosts their chances of getting a match, and hence stealing one successfully.

Password rate limiting (or number of retries) reduces the success rate of password-guessing attempts simply by not letting the attempts to get through, often under these circumstances:

  • A lot of failed logins from the same IP address, no matter the login ID (e.g., username, email, phone etc.)
  • A sequence of failed logins on one account from various IP addresses over a short period of time. For example, if the system receives three login requests on the same account from three different continents, it’s likely a brute-force password attack.
  • A bunch of logins on various login IDs with a pattern/sequence, like { John1, John2, John3 } or { [email protected][email protected][email protected]}
  • Oher cases covered by OWASP’s Blocking Brute Force Attacks guide

Apply password retry limit. Refer to the above rules/cases to evaluate whether one is a brute-force attempt, and make sure not to set the retry limit too low — a lot of users need to try a few times before they figure out their password.

✓ Ensure One-Time Tokens Are Really Used Once

We’ve encountered several authentication servers with features involving one-time tokens like a passwordless login email. These features, when done properly, can enhance a user’s experience in a safe manner. However, there may be chances where you may not realize that you didn’t really use a one-time token only once.

Keep track of the one-time tokens and ID them (i.e., the token itself can be an ID already). If an ID is on the list of ‘used’ IDs, or if its expiry is computed with its created_at, decline the request on whatever the one-time token user is asking for.


The issues we outlined are just the tip of the iceberg. If your auth service has some of them, it’s likely that its architecture wasn’t well designed or has other underlying flaws. Not to toot our own horn, but given the tons of our development projects, we’ve become proficient in conducting code reviews — and auth is one of our main focus areas.

We’re fairly confident with that, as we developed our own auth as a service — AuthGear — from scratch. It has gone through rounds of professional security auditing. All authentication features mentioned throughout this article are supported by AuthGear, plus a few more, like session management, password policy and authentication UI done for you (yes, the whole authentication stack are taken care of for you!).

Feel free to drop us an email! We also provide free code review on authentication and user identity management!

The post Authentication Security: Password Reset Best Practices and More appeared first on Oursky Code Blog.

Source: Oursky

Leave a Reply

Your email address will not be published.