Hayk

Senior full-stack developer working in ReactJS, Angular,Vue.js, Node.js, and other technologies.

How to Reset Password with Node.js and Angular [Part 1]

There are lots of tutorials about user authentication but all of them are just covering the topics like how to sign up and how to sign in. Even if you find a tutorial about reset password then you will see that it covers old school method without APIs and using jQuery based front-end. In this tutorial, we are going to use a Node.js based API which will connect to Angular front-end. Let’s get started:

Initializing the project

npm init
So I assume you are already familiar with generating projects with Node.js and Angular or React. So will not go further for basic topics. We need to install the Express framework, Body-parser, and Mongoose and other required dependencies (We are using Mongo DB) and Node mailer for sending the link to your email where you can set a new password.
npm install express body-parser nodemailer mongoose bcrypt cors cookie-parser
Let’s create our main server file:
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const auth = require('./routes/authRoutes');

const app = express();
app.use(cors());
const server = require('http').createServer(app);


const dbConfig = require('./config/secret');

app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
app.use(cookieParser());

mongoose.Promise = global.Promise;
mongoose.connect(
  dbConfig.url,
  {
    useNewUrlParser: true
  }
).then((conn) => {
  console.log("Mongo Connected")
}).catch((err) => {
  console.log(err)
})

app.use('/api/resetpassword', auth);

server.listen(3000, () => {
  console.log('Listening on port 3000');
});
You may notice that we added a Mongo DB URL from the config folder. You can use Mongo DB Atlas and insert URL by yourself.
You might also notice
authroute
which is not created yet. We will do it later. However, before it we are going to create
User model

 Creating Users Model

const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const Schema = mongoose.Schema;


const userSchema = new Schema({
username: {
type: String,
min: [5, 'Too short, min is 5 characters'],
max: [32, 'Too long, max is 32 characters']
},
email: {
type: String,
min: [5, 'Too short, min is 5 characters'],
max: [32, 'Too long, max is 32 characters'],
unique: true,
lowercase: true,
required: 'Email is required',
match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/]
},
password: {
type: String,
min: [5, 'Too short, min is 5 characters'],
max: [32, 'Too long, max is 32 characters'],
required: 'Password is required'
},
});
userSchema.statics.EncryptPassword = async function(password) {  const hash = await bcrypt.hash(password, 12);  return hash;};

module.exports = mongoose.model('User', userSchema);
We are using bcrypt for hashing our user's password.
Also, we need a create a model for reset token. Here is how it will look like.
const mongoose = require('mongoose');


const resettokenSchema = new mongoose.Schema({
_userId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
resettoken: { type: String, required: true },
createdAt: { type: Date, required: true, default: Date.now, expires: 43200 },
});


module.exports = mongoose.model('passwordResetToken', resettokenSchema);

Creating Auth Controller

It’s time to create an auth controller that will contain all controller functions.
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const nodemailer = require('nodemailer');
const User = require('../models/users');
const passwordResetToken = require('../models/resettoken');
module.exports = {
  async CreateUser(req, res) {
    const userEmail = await User.findOne({
      email: req.body.email
    });
    if (userEmail) {
      return res
        .status(409)
        .json({ message: 'Email already exist' });
    }

    const userName = await User.findOne({
      username: req.body.username
    });
    if (userName) {
      return res
        .status(409)
        .json({ message: 'Username already exist' });
    }

    return bcrypt.hash(req.body.password, 10, (err, hash) => {
      if (err) {
        return res
          .status(400)
          .json({ message: 'Error hashing password' });
      }
      const body = {
        username: req.body.username,
        email: req.body.email,
        password: hash
      };
      User.create(body)
        .then(user => {
          res
          res.status(201) .json({ message: 'User created successfully', user });
        })
        .catch(() => {
          res
            .status(500)
            .json({ message: 'Error occured' });
        });
    });
  },
For making reset password work we first need to create an account. In the above, you will see the controller function which can be used for registering a user. We use
Bcrypt
for hashing a password. it's also recommended to use
Joi
or e
xpress-validator
for adding extra validation.  You might think why both
bcrypt
and
crypto
were required. Later we will use
crypto
for
resettoken
There is a debate which one of those libraries are better and which one can be selected in different situations. The main point of using both of them in this tutorial was that you can see in which cases each of them can be used and how?

Creating ResetPassword controller function

async ResetPassword(req, res) {
    if (!req.body.email) {
    return res
    .status(500)
    .json({ message: 'Email is required' });
    }
    const user = await User.findOne({
    email:req.body.email
    });
    if (!user) {
    return res
    .status(409)
    .json({ message: 'Email does not exist' });
    }
    var resettoken = new passwordResetToken({ _userId: user._id, resettoken: crypto.randomBytes(16).toString('hex') });
    resettoken.save(function (err) {
    if (err) { return res.status(500).send({ msg: err.message }); }
    passwordResetToken.find({ _userId: user._id, resettoken: { $ne: resettoken.resettoken } }).remove().exec();
    res.status(200).json({ message: 'Reset Password successfully.' });
    var transporter = nodemailer.createTransport({
      service: 'Gmail',
      port: 465,
      auth: {
        user: 'user',
        pass: 'password'
      }
    });
    var mailOptions = {
    to: user.email,
    from: 'your email',
    subject: 'Node.js Password Reset',
    text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
    'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
    'http://localhost:4200/response-reset-password/' + resettoken.resettoken + '\n\n' +
    'If you did not request this, please ignore this email and your password will remain unchanged.\n'
    }
    transporter.sendMail(mailOptions, (err, info) => {
    })
    })
    },
Above
ResetPassword 
controller function generates a token which consists of random bytes and attaches it into the URL which will be included in the email. We use Node mailer to send email to the user and as a service provider Gmail was selected for this tutorial, but you can use Sendgrid's API. You can include from which email to send this email, customize the text and include your project’s domain name as the main URL.  Localhost:4200 was added because Angular is running locally on that port. However this functionality works a bit slow and not enough well on the localhost. Wile it will behave perfectly in production mode when you will deploy it and connect with your domain name.

Validating Token and setting a new password

Here is the final controller function which will be used for submitting new password. 
async ValidPasswordToken(req, res) {
        if (!req.body.resettoken) {
        return res
        .status(500)
        .json({ message: 'Token is required' });
        }
        const user = await passwordResetToken.findOne({
        resettoken: req.body.resettoken
        });
        if (!user) {
        return res
        .status(409)
        .json({ message: 'Invalid URL' });
        }
        User.findOneAndUpdate({ _id: user._userId }).then(() => {
        res.status(200).json({ message: 'Token verified successfully.' });
        }).catch((err) => {
        return res.status(500).send({ msg: err.message });
        });
    },
        async NewPassword(req, res) {
            passwordResetToken.findOne({ resettoken: req.body.resettoken }, function (err, userToken, next) {
              if (!userToken) {
                return res
                  .status(409)
                  .json({ message: 'Token has expired' });
              }
        
              User.findOne({
                _id: userToken._userId
              }, function (err, userEmail, next) {
                if (!userEmail) {
                  return res
                    .status(409)
                    .json({ message: 'User does not exist' });
                }
                return bcrypt.hash(req.body.newPassword, 10, (err, hash) => {
                  if (err) {
                    return res
                      .status(400)
                      .json({ message: 'Error hashing password' });
                  }
                  userEmail.password = hash;
                  userEmail.save(function (err) {
                    if (err) {
                      return res
                        .status(400)
                        .json({ message: 'Password can not reset.' });
                    } else {
                      userToken.remove();
                      return res
                        .status(201)
                        .json({ message: 'Password reset successfully' });
                    }
        
                  });
                });
              });
        
            })
        }
    }
Before submiting the new password it verifies the token with user ID.


Creating API routs

Before completing the back-end we need to create
authrouter
file that will include all endpoints.
const express = require('express');
const router = express.Router();
const AuthCtrl = require('../controllers/auth');


router.post('/register', AuthCtrl.CreateUser);
router.post('/req-reset-password', AuthCtrl.ResetPassword);
router.post('/new-password', AuthCtrl.NewPassword);
router.post('/valid-password-token', AuthCtrl.ValidPasswordToken);


module.exports = router;
That’s it! The back-end is ready and completely functional. We need to care about the front end now. To check the second part of this tutorial please visit by this link

Tags

Comments

December 7th, 2019

Following your code letter by letter, i got this error
{
“msg”: “passwordResetToken validation failed: _userId: Path _userId is required.”
}
Can you please tell me what I’m doing wrong?
this error occur immediately after i put my mail in the textt box and hit send

December 7th, 2019

Hey!

Have you downloaded the full source code from Github?
I included the link at the end of tutorial.

December 7th, 2019

Hey haykoyaghubyan!
Thanks for your fast response btw
Yes I’ve downloaded the source code and implemented it (changed the mail address to mine of course), but when i test-run, i didn’t receive any mail. I also tried with sendgrid, but its still the same. no email received. Could this be because I’m on local host?

More by Hayk

Topics of interest