{
    "componentChunkName": "component---src-pages-blog-markdown-remark-fields-slug-js",
    "path": "/blog/implementing-a-forgot-password-flow",
    "result": {"data":{"markdownRemark":{"html":"<h3 id=\"table-of-contents\" style=\"position:relative;\"><a href=\"#table-of-contents\" aria-label=\"table of contents permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Table of contents:</h3>\n<ol>\n<li><a href=\"#implementing-a-secure-forgot-password-flow\">Implementing a Secure Forgot Password Flow</a></li>\n<li><a href=\"#best-practices-for-password-reset-flow\">Best Practices for Password Reset Flow</a></li>\n<li><a href=\"#3-common-issues-in-implementing-forgot-password-flow\">3 Common Issues in Implementing Forgot Password Flow</a></li>\n<li><a href=\"#conclusion\">Conclusion</a></li>\n</ol>\n<h2 id=\"implementing-a-secure-forgot-password-flow\" style=\"position:relative;\"><a href=\"#implementing-a-secure-forgot-password-flow\" aria-label=\"implementing a secure forgot password flow permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Implementing a Secure Forgot Password Flow</h2>\n<h3 id=\"step-1-user-enters-their-email\" style=\"position:relative;\"><a href=\"#step-1-user-enters-their-email\" aria-label=\"step 1 user enters their email permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Step 1: User Enters Their Email</h3>\n<p>When users forget their passwords, they start the password reset process by entering their email address into a form. This form should be simple and clear, guiding users to input the email address associated with their account.</p>\n<h3 id=\"step-2-create-a-password-reset-token\" style=\"position:relative;\"><a href=\"#step-2-create-a-password-reset-token\" aria-label=\"step 2 create a password reset token permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Step 2: Create a Password Reset Token</h3>\n<p>Once the email is submitted, the backend checks if the email exists in the database. Regardless of whether the email exists or not, the system should display a message stating that an email has been sent. This prevents attackers from determining if an email address is valid.</p>\n<p>If the email exists, a new password reset token is generated. Using a secure random source, the token should be a long (at least 64 characters) random string to prevent brute force attacks.</p>\n<h3 id=\"step-3-store-the-token-in-the-database\" style=\"position:relative;\"><a href=\"#step-3-store-the-token-in-the-database\" aria-label=\"step 3 store the token in the database permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Step 3: Store the Token in the Database</h3>\n<p>The generated token is then hashed using SHA-256 and stored in the database along with the user’s ID and an expiration time. This ensures that the token is only valid for a limited period, mitigating the risk of attacks using expired tokens. Here’s an example schema for storing password reset tokens:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"4396627837433442300\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`CREATE TABLE password_reset_tokens (\n    user_id VARCHAR(36) NOT NULL,\n    token VARCHAR(128) NOT NULL UNIQUE,\n    token_expiry BIGINT UNSIGNED NOT NULL,\n    PRIMARY KEY (user_id, token)\n);`, `4396627837433442300`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"sql\"><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">CREATE</span> <span class=\"token keyword\">TABLE</span> password_reset_tokens <span class=\"token punctuation\">(</span>\n    user_id <span class=\"token keyword\">VARCHAR</span><span class=\"token punctuation\">(</span><span class=\"token number\">36</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">NOT</span> <span class=\"token boolean\">NULL</span><span class=\"token punctuation\">,</span>\n    token <span class=\"token keyword\">VARCHAR</span><span class=\"token punctuation\">(</span><span class=\"token number\">128</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">NOT</span> <span class=\"token boolean\">NULL</span> <span class=\"token keyword\">UNIQUE</span><span class=\"token punctuation\">,</span>\n    token_expiry <span class=\"token keyword\">BIGINT</span> <span class=\"token keyword\">UNSIGNED</span> <span class=\"token operator\">NOT</span> <span class=\"token boolean\">NULL</span><span class=\"token punctuation\">,</span>\n    <span class=\"token keyword\">PRIMARY</span> <span class=\"token keyword\">KEY</span> <span class=\"token punctuation\">(</span>user_id<span class=\"token punctuation\">,</span> token<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h3 id=\"step-4-send-the-token-to-the-user\" style=\"position:relative;\"><a href=\"#step-4-send-the-token-to-the-user\" aria-label=\"step 4 send the token to the user permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Step 4: Send the Token to the User</h3>\n<p>A password reset link containing the token is sent to the user’s email address. The link should redirect the user to a secure page on the website where they can enter their new password. An example link might look like this:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"62982097123386630000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`https://example.com/reset-password?token=<Token here>`, `62982097123386630000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\">https://example.com/reset-password?token<span class=\"token operator\">=</span><span class=\"token operator\">&lt;</span>Token here<span class=\"token operator\">></span></code></pre></div>\n<h3 id=\"step-5-verify-the-token-and-update-the-password\" style=\"position:relative;\"><a href=\"#step-5-verify-the-token-and-update-the-password\" aria-label=\"step 5 verify the token and update the password permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Step 5: Verify the Token and Update the Password</h3>\n<p>When the user clicks the link and submits the new password, the backend verifies the token. If the token’s hash matches the one stored in the database and it hasn’t expired, the user’s password is updated. Here’s a pseudo code example of the backend logic:</p>\n<div\n              class=\"gatsby-code-button-container\"\n              data-toaster-id=\"86586540556824500000\"\n              data-toaster-class=\"gatsby-code-button-toaster\"\n              data-toaster-text-class=\"gatsby-code-button-toaster-text\"\n              data-toaster-text=\"Copied!\"\n              data-toaster-duration=\"3500\"\n              onClick=\"copyToClipboard(`function redeemToken(passwordResetToken, newPassword) {\n    hashedToken = hash_sha256(passwordResetToken);\n    rowFromDb = db.getRowTheContains(hashedToken);\n    if (rowFromDb == null) {\n        throw Error(&quot;Invalid password reset token&quot;);\n    }\n    userId = rowFromDb.user_id;\n\n    db_startTransaction(() => {\n        allTokensForUser = db_getAllTokensBasedOnUser(userId);\n\n        matchedRow = allTokensForUser.find(row => row.token == hashedToken);\n        if (matchedRow == null) {\n            throw Error(&quot;Invalid password reset token&quot;);\n        }\n\n        if (matchedRow.token_expiry < time_now()) {\n            db_rollback();\n            throw Error(&quot;Token has expired. Please try again&quot;);\n        }\n\n        db_deleteAllRowsForUser(userId);\n\n        hashedAndSaltedPassword = hashAndSaltPassword(newPassword);\n        db_saveNewPassword(userId, hashedAndSaltedPassword);\n        db_commitTransaction();\n    });\n}`, `86586540556824500000`)\"\n            >\n              <div\n                class=\"gatsby-code-button\"\n                data-tooltip=\"\"\n              >\n                <svg class=\"gatsby-code-button-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 1H2v16h2V3h12V1zm-1 4l6 6v12H6V5h9zm-1 7h5.5L14 6.5V12z\"/></svg>\n              </div>\n            </div>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">function</span> <span class=\"token function\">redeemToken</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">passwordResetToken<span class=\"token punctuation\">,</span> newPassword</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    hashedToken <span class=\"token operator\">=</span> <span class=\"token function\">hash_sha256</span><span class=\"token punctuation\">(</span>passwordResetToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    rowFromDb <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token function\">getRowTheContains</span><span class=\"token punctuation\">(</span>hashedToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>rowFromDb <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">throw</span> <span class=\"token function\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Invalid password reset token\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    userId <span class=\"token operator\">=</span> rowFromDb<span class=\"token punctuation\">.</span>user_id<span class=\"token punctuation\">;</span>\n\n    <span class=\"token function\">db_startTransaction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        allTokensForUser <span class=\"token operator\">=</span> <span class=\"token function\">db_getAllTokensBasedOnUser</span><span class=\"token punctuation\">(</span>userId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        matchedRow <span class=\"token operator\">=</span> allTokensForUser<span class=\"token punctuation\">.</span><span class=\"token function\">find</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">row</span> <span class=\"token operator\">=></span> row<span class=\"token punctuation\">.</span>token <span class=\"token operator\">==</span> hashedToken<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>matchedRow <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token function\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Invalid password reset token\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>matchedRow<span class=\"token punctuation\">.</span>token_expiry <span class=\"token operator\">&lt;</span> <span class=\"token function\">time_now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token function\">db_rollback</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token function\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Token has expired. Please try again\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token function\">db_deleteAllRowsForUser</span><span class=\"token punctuation\">(</span>userId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        hashedAndSaltedPassword <span class=\"token operator\">=</span> <span class=\"token function\">hashAndSaltPassword</span><span class=\"token punctuation\">(</span>newPassword<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token function\">db_saveNewPassword</span><span class=\"token punctuation\">(</span>userId<span class=\"token punctuation\">,</span> hashedAndSaltedPassword<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token function\">db_commitTransaction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h2 id=\"best-practices-for-password-reset-flow\" style=\"position:relative;\"><a href=\"#best-practices-for-password-reset-flow\" aria-label=\"best practices for password reset flow permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Best Practices for Password Reset Flow</h2>\n<ul>\n<li><strong>Use Secure Random Tokens</strong>: Ensure tokens are generated from a secure random source and are sufficiently long (>= 64 characters) to prevent brute force attacks.</li>\n<li><strong>Hash Tokens</strong> in the Database: Store only the hashed version of tokens in the database to protect against database theft.</li>\n<li><strong>Expire Tokens</strong>: Set a reasonable expiration time for tokens to limit the window of opportunity for an attack.</li>\n<li><strong>Implement Rate Limiting</strong>: To prevent abuse, limit the number of password reset requests that can be made within a given time frame.</li>\n<li><strong>Secure Token Delivery</strong>: Send tokens via secure email channels and consider using additional layers of authentication, such as two-factor authentication.</li>\n</ul>\n<h2 id=\"common-issues-in-implementing-forgot-password-flow\" style=\"position:relative;\"><a href=\"#common-issues-in-implementing-forgot-password-flow\" aria-label=\"common issues in implementing forgot password flow permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Common Issues in Implementing Forgot Password Flow</h2>\n<h3 id=\"issue-1-brute-force-attacks\" style=\"position:relative;\"><a href=\"#issue-1-brute-force-attacks\" aria-label=\"issue 1 brute force attacks permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Issue #1: Brute Force Attacks</h3>\n<p>This is a common threat for all web applications. 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).</p>\n<p>Solution: Generate tokens using a secure random source and ensure they are long enough to be unguessable. Implement rate limiting to prevent repeated attempts.</p>\n<h3 id=\"issue-2-database-theft-of-tokens\" style=\"position:relative;\"><a href=\"#issue-2-database-theft-of-tokens\" aria-label=\"issue 2 database theft of tokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Issue #2: Database Theft of Tokens</h3>\n<p>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. They could even gain access if someone hasn’t updated the default login credentials.</p>\n<p>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. 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).</p>\n<p>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. 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.</p>\n<h3 id=\"issue-3-reusing-existing-tokens\" style=\"position:relative;\"><a href=\"#issue-3-reusing-existing-tokens\" aria-label=\"issue 3 reusing existing tokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Issue #3: Reusing Existing Tokens</h3>\n<p>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.</p>\n<p>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.</p>\n<p>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.</p>\n<p><strong>What actually happens on the backend (pseudo code)</strong></p>\n<p>Applications need to account for the frequency with which users forget their passwords. 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. Making sure you have a secure process for handling the password reset tokens will ensure your users’ accounts remain safe from attackers.</p>\n<h2 id=\"security-issues-to-consider\" style=\"position:relative;\"><a href=\"#security-issues-to-consider\" aria-label=\"security issues to consider permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Security Issues to Consider</h2>\n<h3 id=\"brute-force-attacks\" style=\"position:relative;\"><a href=\"#brute-force-attacks\" aria-label=\"brute force attacks permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Brute force attacks</h3>\n<p>This is a common threat for all web applications. 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).</p>\n<p>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.</p>\n<h2 id=\"database-theft-of-password-reset-tokens\" style=\"position:relative;\"><a href=\"#database-theft-of-password-reset-tokens\" aria-label=\"database theft of password reset tokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Database theft of password reset tokens</h2>\n<p>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. They could even gain access if someone hasn’t updated the default login credentials.</p>\n<p>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 <a href=\"https://www.zdnet.com/article/paleohacks-data-leak-exposes-customer-records-password-reset-tokens/\" target=\"_blank\" rel=\"nofollow\">Paleohacks</a>. 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).</p>\n<p>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. 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.</p>\n<h2 id=\"reusing-existing-tokens\" style=\"position:relative;\"><a href=\"#reusing-existing-tokens\" aria-label=\"reusing existing tokens permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Reusing existing tokens</h2>\n<p>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.</p>\n<p>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.</p>\n<p>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.</p>\n<h2 id=\"conclusion\" style=\"position:relative;\"><a href=\"#conclusion\" aria-label=\"conclusion permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Conclusion</h2>\n<p>Implementing a secure password reset flow requires attention to detail and a thorough understanding of security best practices. By generating secure tokens, storing them safely, and using additional authentication measures, you can protect your users’ accounts from common threats. At SuperTokens, we provide easy-to-use, open-source authentication solutions to help you secure your applications and save development time.</p>","frontmatter":{"date":"July 13, 2024","title":"How to Implement a Forgot Password Flow? Complete Guide","cover":"implementing-a-forgot-password-flow.png","author":"Joel Coutinho","description":"What should happen on the backend when a user forgets their password? Read to find a pseudo code implementation of the simplest way to reset passwords securely."},"fields":{"slug":"/implementing-a-forgot-password-flow/"}},"site":{"siteMetadata":{"title":"SuperTokens Blog"}}},"pageContext":{"id":"ad480c67-67de-5ee7-afbf-085ae0f55186","fields__slug":"/implementing-a-forgot-password-flow/","__params":{"fields__slug":"implementing-a-forgot-password-flow"}}},
    "staticQueryHashes": []}