4

How to Implement a Forgot Password Flow (With Pseudo Code)

 3 years ago
source link: https://hackernoon.com/how-to-implement-a-forgot-password-flow-with-pseudo-code-7u1j379a
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How to Implement a Forgot Password Flow (With Pseudo Code)

@supertokens.ioSuperTokens

The most secure and easy to implement solution for user session management

Table of Contents:

Security Issues to consider:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  • Brute Force Attacks
  • Theft of password reset tokens from the database
  • Reusing existing tokens
  • Stealing tokens through email hijacking

How to implement a secure password reset flow:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  • Enable users to enter their email to initiate the flow
  • Create the password reset token and store it in the DB
  • Send the token to the user and verify it when used

What actually happens on the backend (pseudo code):

Applications need to account for the frequency with which users forget their passwords.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This opens a potential attack vector because anyone can request a new password on behalf of the legitimate user. Resetting a password requires sending a token to a user’s email address and this provides an opening for attackers.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Making sure you have a secure process for handling the password reset tokens will ensure your users’ accounts remain safe from attackers.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Security Issues to Consider

Brute Force Attacks

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This is a common threat for all web applications.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Attackers may attempt to detect patterns in the password reset tokens - like if it’s derived from a user’s userId, time they signed up, their email or any other information. Attackers may also try all possible combinations of letters and numbers (brute force), and may even succeed if the generated tokens are not long or random enough (i.e. have low entropy).  

0 reactions
heart.png
light.png
money.png
thumbs-down.png

To prevent this, we must ensure that tokens are generated using a secure random source, and that they are long enough (we recommend >= 64 characters). Later on in this blog, we will see one such method.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Database theft of password reset tokens

There are several ways an attacker can gain access to an application's database: SQL injection attacks, targeting unpatched database vulnerabilities, and exploiting unused database services.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

They could even gain access if someone hasn't updated the default login credentials.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

While there are plenty of problems with an attacker getting database access, one of them is their ability to get users' password reset tokens, like in this research on Paleohacks.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

To mitigate this risk, we store only the hashed version of tokens in the database. Passwords are hashed and stored and it’s important that password reset tokens are too (for the same reasons). 

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Another related attack vector is the use of JWTs as the password reset token. Whilst this makes development easy, a major risk is that if the secret key used to sign them is compromised, the attacker can use that to generate their own valid JWT.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This would allot them to reset any user’s password. The JWT secret key (or signing key) must be carefully protected and hence we do not recommend using JWTs as the password reset token.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Reusing Existing Tokens

0 reactions
heart.png
light.png
money.png
thumbs-down.png

For simplicity of development, it may be tempting to store a “static” password reset token per user. This token might be randomly generated on user sign up, or based on their password’s hash or some other information.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This in turn implies that these tokens cannot be stored in a hashed form in the database - since we will need to send this token over email (because if we hash it, we can’t unhash it at the time). Therefore, if the database is compromised, these tokens can be used to reset a user’s password.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Another risk of reusing tokens is that if a token somehow gets leaked, even if it has been redeemed by the actual user, it can still be used by an attacker to change that user’s password. 

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Stealing Tokens From Email Hijacking

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Attackers can gain access to a user's email account through email hijacking. This usually happens with phishing emails, social engineering, or by getting them to enter their credentials in a bogus login form.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Once they have access to a user's email, they are able to trigger a password reset link and gain access to the user’s account.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This risk can be mitigated by enabling two-factor authentication via SMS or an authenticator app, or by asking secret questions to the user before allowing them to reset their password.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

How to Implement a Secure Password Reset Flow

To ensure that your password reset process is as secure as possible, here is a potential flow that takes into account the security issues we discussed above.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Users Enter Their Email to Initiate the Password Reset Flow

0 reactions
heart.png
light.png
money.png
thumbs-down.png

This is how users will begin the process for updating their passwords.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

There's usually a simple form available that lets them enter the email address associated with their account.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

When they submit the email address, this will trigger the back-end to check if that email exists in the database. Even if the email doesn't exist, we'll show a message that says the email has been sent successfully. That way we don't give attackers any indication that they should try a different email address.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

If the email does exist in the database, then we create a new password reset token, store its hashed version in the database, and generate a password reset link that's sent to the user's email address.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Create a Password Reset Token

0 reactions
heart.png
light.png
money.png
thumbs-down.png

With Supertokens, the password reset token is generated using a random 64 character secure random string. This prevents brute force attacks mentioned above since new tokens are unguessable, and have high entropy. 

0 reactions
heart.png
light.png
money.png
thumbs-down.png

ForJava ‍(uptill line 120)

0 reactions
heart.png
light.png
money.png
thumbs-down.png

ForNodeJS

0 reactions
heart.png
light.png
money.png
thumbs-down.png

ForPython and use 

secrets.
token_urlsafe
0 reactions
heart.png
light.png
money.png
thumbs-down.png

Store the Token in the Database

0 reactions
heart.png
light.png
money.png
thumbs-down.png

After the token has been created, it's hashed using SHA256 and stored in the database along with the user’s ID and it's assigned an expiration time.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

That way the token is only valid for a set amount of time, blocking attacks that could happen if the token never expired. Here’s an example of the db schema we can use to store password reset tokens:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
CREATE TABLE password_reset_tokens (
    user_id VARCHAR(36) NOT NULL,
    token VARCHAR(128) NOT NULL UNIQUE,
    token_expiry BIGINT UNSIGNED NOT NULL,
    PRIMARY KEY (user_id, token),
); 

If you notice, we allow multiple tokens to be stored per user. This is necessary since we only store the hashed version of the tokens in the db. This means that if a user requests multiple tokens at the same time, we cannot send them the same previously generated token (which is not yet redeemed), since it’s stored in hashed form.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

At the end, we want to generate a password reset link which points to a link on your website that displays the “enter new password” form, and also contains the token. An example of this is:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
https://example.com/reset-password?token=
<Token here>
0 reactions
heart.png
light.png
money.png
thumbs-down.png

The User Clicks on the Link in the Email

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Once the user has received the email, they will click on the link and it will redirect them to a page on the website to enter their new password. The password validators should follow the same rules as that in a signup form.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

The new Password is Submitted Along with the Token to the Back-end

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Once they enter the new password, the reset token and the new password are sent to the back-end. The reset password token is obtained from the password reset link’s query params.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

In summary, if the token’s hash matches what was stored in the database, the user's password will be updated with the new password. Otherwise, the user will have to request a new reset token and go through the process again.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

What Happens on the Back-end

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Here is a pseudo-code of your backend API logic for redeeming a token:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
function redeemToken(passwordResetToken, newPassword) {
       /*
       First we hash the token, and query the db based on the hashed value. If nothing is found, then we throw an error.
    */
       hashedToken = hash_sha256(passwordResetToken);
       rowFromDb = db.getRowTheContains(hashedToken)
       if (rowFromDb == null) {
           throw Error(“invalid password reset token”)
       }
       userId = rowFromDb.user_id
       /*
       Now we know that the token exists, so it is valid. We start a transaction to prevent race conditions.
     */
       
       db_startTransaction() {
                /*   allTokensForUser is an array of db rows. We have to use a query that locks all the rows in the table that belong to this userId. We can use something like “SELECT *              FROM password_reset_tokens where user_id = userId FOR UPDATE”. The “FOR UPDATE" part locks all the relevant rows.      */  
               allTokensForUser = db_getAllTokensBasedOnUser(userId)
               /*
                   We search for the row that matches the input token’s hash, so that we know that another transaction has not redeemed it already.                
            */ 
             matchedRow = null;
             allTokensForUser.forEach(row => {
                   if (row.token == hashedToken) {
                          matchedRow = row;
                   }
              });
             if (matchedRow == null) {
                  /* The token was redeemed by another transaction. So we exit */
                  throw Error(“invalid password reset token”)
             }
             /*
             Now we will delete all the tokens belonging to this user to prevent duplicate use              
            */
            db_deleteAllRowsForUser(userId)
             /*
            Now we check if the current token has expired or not.
            */
            if (matchedRow.token_expiry < time_now()) {
                db_rollback();
                throw Error(“Token has expired. Please try again”);
             }
             /* Now all checks have been completed. We can change the user’s password */
             hashedAndSaltedPassword = hashAndSaltPassword(newPassword);
             db_saveNewPassword(userId, hashedAndSaltedPassword);
             db_commitTransaction();
        }
 }

Conclusion

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Password reset flows are easy to get wrong.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

One needs to be knowledgeable in cryptography, database transactions / distributed locking, and should be able to think about all the edge cases in the flow. The cost of getting these wrong can lead to compromised user accounts.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

At SuperTokens.io, we have tried to provide an easy-to-use, open-source, password reset solution (and lots of other auth modules) that you can use to secure your app and save time.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Written by the Folks at SuperTokens — hope you enjoyed! We are always available on our Discord server. Join us if you have any questions or need any help.

0 reactions
heart.png
light.png
money.png
thumbs-down.png
0 reactions
heart.png
light.png
money.png
thumbs-down.png
5
heart.pngheart.pngheart.pngheart.png
light.pnglight.pnglight.pnglight.png
boat.pngboat.pngboat.pngboat.png
money.pngmoney.pngmoney.pngmoney.png
by SuperTokens @supertokens.io. The most secure and easy to implement solution for user session managementSee our website for repositories
Join Hacker Noon

Create your free account to unlock your custom reading experience.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK