I am developing an api, an I am using nodejs, express, sequelize, mysql.
I am implementing my authentication routes and methods using bcrypt and jwt. My user registration works fine, the users are being saved with their encrypted password in my db.
The issue I am struggling with, is that when I want to authenticate a user the plain text password and the hashed password don´t match.
This is my route inside users.js
router.post('/', [
check('email', 'Plese include a valid email').isEmail(),
check('password', 'Password is required').exists()
],
async (req, res)=> {
const errors = validationResult(req);
if(!errors.isEmpty()){
return res.status(400).json({ errors:errors.array()}); //400 is for bad requests
}
const { email, password } = req.body;
try{
// Validate emil & password
if (!email || !password) {
return next(new ErrorResponse('Please provide an email and password', 400));
}
//See if user exists
let user = await User.findOne({ where: { email: email }, attributes: ['email', 'password'] });
if(!user){
return res.status(400).json({ errors: [{ msg:'Invalid credentials' }] });
}
//Compare the input password, plane text, to the encrypted password.
const isMatch = await user.matchPassword(password);
if(!isMatch){
return res.status(400).json({ errors: [{ msg:'Invalid credentials' }] });
}
//Return jsonwebtoken -> this for users to be logged in right after registration
sendTokenResponse(user, 200, res);
}catch(err){
console.error(err.message);
res.status(500).send('Server Error');
}
});
And this is my User model in user.model.js
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = require('../config/db');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
lastname: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
password: {
type: DataTypes.STRING,
allowNull: false
}
});
User.addHook('beforeCreate', async (user, options) => {
if (user.password) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
}
});
User.prototype.getSignedJwtToken = function() {
return jwt.sign({ id: this.id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRE
});
};
User.prototype.matchPassword = async function(enteredPassword) {
console.log(this);
console.log(enteredPassword, this.password);
console.log(await bcrypt.compare(enteredPassword, this.password));
return await bcrypt.compare(enteredPassword, this.password);
};
module.exports = User;
As you can see I have some methods inside my model to encrypt my password and to compare the inserted plain text password to the hashed password in the db.
For some reason when I enter correctly the credentials, my method matchPassword always returns false.
I´ll also leave my registering route, just in case I am encrypting differently the password with a different salt that the one being used in matchPassword
router.post('/register', [
check('name', 'Name is required')
.not()
.isEmpty(),
check('lastname', 'Lastname is required')
.not()
.isEmpty(),
check('email', 'Plese include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({min:6})
],
async (req, res) => {
const errors = validationResult(req);
if(!errors.isEmpty()){
return res.status(400).json({ errors:errors.array()}); //400 is for bad requests
}
const { name, lastname, email, password } = req.body;
try {
//Check if user exists
let user = await User.findOne({ where: { email } });
if(user){
return res.status(400).json({ errors: [{ msg: 'User already exists'}]});
}
//Encrypt password
const salt = await bcrypt.genSalt(10);
hashedPassword = await bcrypt.hash(password, salt);
//Create user
user = await User.create({ name, lastname, email, password:hashedPassword });
//Return jsonwebtoken -> this for users to be logged in right after registration
sendTokenResponse(user, 200, res);
} catch (error) {
console.error('Error:', error);
res.status(500).send('Server error');
}
});
You used the hash
function twice.
One of those is in your model definition. You have bcrypt.hash
in beforeCreate
hook, and the other is in the register
controller.
If you use bcrypt.hash
to encrypt the password, you don't need to use it in the register
controller. You have to pass the password as plain text. Because you have already that in beforeCreate
hook.