I implemented a password reset flow in a personal project and I thought I should write about it. This article assumes you can set up an express server, query a database, create routes... and all that fun stuff.
Packages we'd use:
SendGrid (for emails) - npm i @sendgrid/mail
Knex (A Database Query Builder. Of course you can use any) - npm i pg knex
uuid (to generate unique ids) - npm i uuid
bcrypt (to hash passwords) - npm i bcryptjs
Go ahead and install them!
First I'd explain the process:
Step 1:
- Create a 'password_reset' table in your database with columns "id", "email".
Step 2:
- Users submit their email requesting for a password change, we check if this email exists in our "users" table (existing users table). If the user exists, we insert a unique id and the submitted email into the "password_reset" table and send them an email with a link containing that id.
Step 3:
- We use the id in the link to verify the user and update with the newly entered password
Easy stuff! Now let's write some code.
#### Step 2
//imports
const knex = require("--your db setup--")
//sendgrid for emails
const sgMail = require('@sendgrid/mail');
//uuid
const {
v4: uuid
} = require("uuid")
//add your api key. Please sign up on https://sendgrid.com to get yours
sgMail.setApiKey(--Your API key--);
router.post("/password_reset", async (req, res)=> {
//email user submitted
const {
email
} = req.body
try {
//check if user exists
const user = await knex("users").select("email").where({
email
})
if (user.length > 0) {
//generate a unique id
let id = uuid()
//insert into the "password_reset" table
await knex("password_reset").insert([{
id,
email: user[0].email
}])
//email content sent with SendGrid. The unique id is sent in the email as a link
const content = {
to: email,
from: "support@me.com",
subject: "Password Recovery",
html: `<body>
<p>Click to set a new password : <a href="yoururl/password/reset/${id}">Reset password</a></p>
</body>`
}
await sgMail.send(content)
return res.send("Please check your email for the next step")
}
//if email doesn't exist
return res.status(404).send("email does not exist in our records")
} catch (error) {
res.send(error.message)
})
Next step is to verify the user by checking the "password_reset" table with the id passed in the email url.
#### Step 3A
router.get("/verify_email/:id", async (req, res) => {
//pass in the id as a parameter
const {
id
} = req.params
try {
//query for the email with the provided id
const email = await knex("password_reset").select("email").where({
id
})
//send back the email
return res.send(email[0])
} catch (error) {
res.status(404).send(error.message)
}
}
)
Finally We update the user's password in the "users" table, and clear the record in the "password_reset" table to ensure the link sent in the email in "Step 2" is useless.
##### Step 3B:
router.post("/change_password", async(req,res)=>{
const {
email,
id,
newpassword
} = req.body
try {
//final check if user exists in "password_reset" table
const user = await knex("password_reset").select("email").where({
email
})
if (user.length === 0) {
return res.status(404).send("an error occured")
}
//hash new password
const salt = await bcrypt.genSalt(10)
const hashedpassword = await bcrypt.hash(newpassword, salt)
//update the users table!
await knex('users')
.where({
email
})
.update({
password: hashedpassword
})
//clear the record in "password_reset" table for security
await knex('password_reset')
.where({
id
})
.del()
return res.send("password successfully changed!")
} catch (error) {
res.send(error.message)
}
})
The End!
Note:
- There are probably other ways to do this, this is what I came up with.
Top comments (1)
Hi thanks for the post. Whats url shoul I put here ===> href="yoururl/password/reset/${id}"
The one where I have the fields to enter the new password?
Thanks!