paint-brush
How to Reset Password with Node.js and Angular [Part 1]by@haykoyaghubyan
12,185 reads
12,185 reads

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

by HaykOctober 15th, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

How to reset password with Node.js and Angular [Part 1] How to Reset Password with.js and. Angular.js based API which will connect to. Angular front-end. We are using bcrypt for. hashing our user's password. We need to create a model for. users and create a controller that will contain all controller functions. It’s time to create an auth controller that. will contain functions that will. contain all functions required. We will use Mongo DB and Node mailer for sending the. link to your email where you can set a new password.

Coin Mentioned

Mention Thumbnail
featured image - How to Reset Password with Node.js and Angular [Part 1]
Hayk HackerNoon profile picture

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