I am using ExpressJS and MongoDB. I am using bcrypt for hashing the password before storing it in the database.
Here is the code :
if (bcrypt.compare(req.body.password === result.password))
How can I make it case sensitive? Thank you.
Your problem here is that you are using the bcrypt.compare function incorrectly.
From the docs found here: https://www.npmjs.com/package/bcrypt
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
// result == false
});
And here's a complete, working example with an upper vs. lower case password checked:
const bcrypt = require('bcrypt');
const testPassword = '12345678abcdefg';
// generate a hash:
bcrypt.hash(testPassword, 10, function(err, hash) {
// test a wrong password:
const nonMatchingPassword = '12345678ABCDEFG';
bcrypt.compare(nonMatchingPassword, hash, function(err, matches) {
console.log('should not match:', matches);
// test the right password:
bcrypt.compare(testPassword, hash, function(err, matches) {
console.log('should match:', matches);
});
});
});
The output from this example:
should not match: false
should match: true
Related
I know that question has already been asked a few times (like here, here or there, or even on Github, but none of the answers actually worked for me...
I am trying to develop authentication for a NodeJS app using Mongoose and Passport, and using Bcrypt-NodeJS to hash the users' passwords.
Everything was working without any problem before I decided to refactor the User schema and to use the async methods of bcrypt. The hashing still works while creating a new user but I am now unable to verify a password against its hash stored in MongoDB.
What do I know?
bcrypt.compare() always returns false whatever the password is correct or not, and whatever the password (I tried several strings).
The password is only hashed once (so the hash is not re-hashed) on user's creation.
The password and the hash given to the compare method are the right ones, in the right order.
The password and the hash are of type "String".
The hash isn't truncated when stored in the database (60 characters long string).
The hash fetched in the database is the same as the one stored on user's creation.
Code
User schema
Some fields have been stripped to keep it clear, but I kept the relevant parts.
var userSchema = mongoose.Schema({
// Local authentication
password: {
hash: {
type: String,
select: false
},
modified: {
type: Date,
default: Date.now
}
},
// User data
profile: {
email: {
type: String,
required: true,
unique: true
}
},
// Dates
lastSignedIn: {
type: Date,
default: Date.now
}
});
Password hashing
userSchema.statics.hashPassword = function(password, callback) {
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
if (err) return callback(err);
callback(null, hash);
});
}
Password comparison
userSchema.methods.comparePassword = function(password, callback) {
// Here, `password` is the string entered in the login form
// and `this.password.hash` is the hash stored in the database
// No problem so far
bcrypt.compare(password, this.password.hash, function(err, match) {
// Here, `err == null` and `match == false` whatever the password
if (err) return callback(err);
callback(null, match);
});
}
User authentication
userSchema.statics.authenticate = function(email, password, callback) {
this.findOne({ 'profile.email': email })
.select('+password.hash')
.exec(function(err, user) {
if (err) return callback(err);
if (!user) return callback(null, false);
user.comparePassword(password, function(err, match) {
// Here, `err == null` and `match == false`
if (err) return callback(err);
if (!match) return callback(null, false);
// Update the user
user.lastSignedIn = Date.now();
user.save(function(err) {
if (err) return callback(err);
user.password.hash = undefined;
callback(null, user);
});
});
});
}
It may be a "simple" mistake I made but I wasn't able to find anything wrong in a few hours... May you have any idea to make that method work, I would be glad to read it.
Thank you guys.
Edit:
When running this bit of code, match is actually equal to true. So I know my methods are correct. I suspect this has something to do with the storage of the hash in the database, but I really have no idea of what can cause this error to occur.
var pwd = 'TestingPwd01!';
mongoose.model('User').hashPassword(pwd, function(err, hash) {
console.log('Password: ' + pwd);
console.log('Hash: ' + hash);
user.password.hash = hash;
user.comparePassword(pwd, function(err, match) {
console.log('Match: ' + match);
});
});
Edit 2 (and solution) :
I put it there in case it could be helpful to someone one day...
I found the error in my code, which was occurring during the user's registration (and actually the only piece of code I didn't post here). I was hashing the user.password object instead of user.password.plaintext...
It's only by changing my dependencies from "brcypt-nodejs" to "bcryptjs" that I was able to find the error because bcryptjs throws an error when asked to hash an object, while brcypt-nodejs just hashes the object as if it were a string.
I know the solution has been found but just in case you are landing here out of google search and have the same issue, especially if you are using a schema.pre("save") function, sometimes there is a tendency of saving the same model several times, hence re-hashing the password each time. This is especially true if you are using references in mongoDB to create schema relationship. Here is what my registration function looked like:
Signup Code
User.create(newUser, (err, user) => {
if (err || !user) {
console.warn("Error at stage 1");
return res.json(transformedApiRes(err, "Signup error", false)).status(400);
}
let personData: PersonInterface = <PersonInterface>{};
personData.firstName = req.body.first_name;
personData.lastName = req.body.last_name;
personData.user = user._id;
Person.create(personData, function (err1: Error, person: any): any {
if (err1 || !person) {
return res.json(transformedApiRes(err1, "Error while saving to Persons", false));
}
/* One-to-One relationship */
user.person = person;
user.save(function (err, user) {
if (err || !user) {
return res.json({error: err}, "Error while linking user and person models", false);
}
emitter.emit("userRegistered", user);
return res.json(transformedApiRes(user, `Signup Successful`, true));
});
});
});
As you can see there is a nested save on User because I had to link the User model with Person model (one-to-one). As a result, I had the mismatch error because I was using a pre-save function and every time I triggered User.create or User.save, the function would be called and it would re-hash the existing password. A console statement inside pre-save gave me the following, showing that indeed that password was re-hashed:
Console debug after a single signup call
{ plain: 'passwd',
hash: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S' }
{ plain: '$2b$10$S2g9jIcmjGxE0aT1ASd6lujHqT87kijqXTss1XtUHJCIkAlk0Vi0S',
hash: '$2b$10$KRkVY3M8a8KX9FcZRX.l8.oTSupI/Fg0xij9lezgOxN8Lld7RCHXm' }
The Fix, The Solution
To fix this, you have to modify your pre("save") code to ensure the password is only hashed if it is the first time it is being saved to the db or if it has been modified. To do this, surround your pre-save code in these blocks:
if (user.isModified("password") || user.isNew) {
//Perform password hashing here
} else {
return next();
}
Here is how the whole of my pre-save function looks like
UsersSchema.pre("save", function (next: NextFunction): any {
let user: any = this;
if (user.isModified("password") || user.isNew) {
bcrypt.genSalt(10, function (err: Error, salt: string): any {
if (err) {
return next(err);
}
bcrypt.hash(user.password, salt, function (err: Error, hash: string) {
if (err) {
console.log(err);
return next(err);
}
console.warn({plain: user.password, hash: hash});
user.password = hash;
next();
});
});
} else {
return next();
}
});
Hopefully this helps someone.
I am dropping this here because it might help someone someday.
In my own case, the reason why I was having bcrypt.compare as false even when I supplied the right authentication details was because of the constraints on the datatype in the model. So each time the hash was saved in the DB, it was truncated in order to fit into the 50 characters constraints.
I had
'password': {
type: DataTypes.STRING(50),
allowNull: false,
comment: "null"
},
The string could only contain 50 characters but the result of bcrypt.hash was more than that.
FIX
I modified the model thus DataTypes.STRING(255)
bcrypt.hash() has 3 arguments... you have 4 for some reason.
Instead of
bcrypt.hash(password, bcrypt.genSaltSync(12), null, function(err, hash) {
it should be
bcrypt.hash(password, bcrypt.genSaltSync(12), function(err, hash) {
Since you were hashing only during user creation, then you might not have been hashing properly. You may need to re-create the users.
Tip: If you are switching
then().then()
Block always check return value.
You can always check the max length for the password field in the database. Make sure it is large. In my case, I have set it to 500. And then the code worked flawlessly!
TS version
const { phone, password } = loginDto;
const user = await this.usersService.findUserByPhone(phone);
const match = await compare(password, user.password);
if (user && match){
return user
}else{
throw new UnauthorizedException();
}
JS version
const { phone, password } = loginDto;
const user = await this.usersService.findUserByPhone(phone);
const match = await bcrypt.compare(password, user.password);
if (user && match){
return user
}else{
throw new UnauthorizedException();
}
I am new to feathersJs and trying to learn how to perform authentication using hooks and services. I am using Couchdb database and cradle.
This is the post method to encrypt password in hashPassword using "users" hooks service. The post method is as below:
app.post('/dev',function(req,res,next){
var username = req.body.username;
var password = req.body.password;
app.service('database').create({username,password}).then(user => {
db.save(user, function (err, docs) {
// Handle response
res.json(docs);
});
console.log('User Created Successfully.', user);
}).catch(console.error);
})
and service is:
app.service('authentication').hooks({
before: {
create: [
// You can chain multiple strategies
auth.hooks.authenticate(['jwt', 'local'])
],
remove: [
auth.hooks.authenticate('jwt')
]
}
});
app.service('database').hooks({
before: {
find: [
auth.hooks.authenticate('jwt')
],
create: [
local.hooks.hashPassword({ passwordField: 'password' })
]
}
});
now i am using this to retrive data :
app.post('/devget',function(req,res,next){
var User = {
username: req.body.username,
password: req.body.password
};
app.service('dataget').find(User).then(user => {
db.view('byuser/user',{key: User.username}, function (err, docs) {
// Handle response
res.json(docs);
});
console.log('User Get Successfully.', user);
}).catch(console.error);
})
this will give me data in response as:
Response [
{ id: '060ab48a4826da7125d8ae45350037ee',
key: 'w',
value:
{ _id: '060ab48a4826da7125d8ae45350037ee',
_rev: '1-ea9a18d3724ce4542019dc5752c1fd4d',
username: 'w',
password: '$2a$10$yBJVJTmVXfTk0V4CCiWkd.GvAZZB9dF2pckKJ9wb/lJcAK8Ou.v06',
id: 0 } } ]
this works fine and password is encrypted but i am not getting how to decrypt password and authenticate user.
Note: i just want o do it with hooks and services or custom service or class but not using passport.
You do not decrypt the password; you compare the encrypted password to a function that will encrypt the password (after you've found the user to make a password comparison to).
const bcrypt = require('bcryptjs');
var hash = bcrypt.hashSync("bacon");
bcrypt.compareSync("bacon", hash); // true
bcrypt.compareSync("veggies", hash); // false
I would use the comparePassword function in this file
https://github.com/feathersjs/feathers/blob/be5d8df3b7afa39852ff1b5643676d9140ba8203/packages/authentication-local/src/strategy.ts#L94
Based on that you could write a hook and consider contributing it to the feathersJS project.
I am new to nodejs but I am trying to use it as server for an online game I am designing. I come across the tutorial and I am just playing around with the code to see how I can modify it to suit me need.
https://github.com/scotch-io/easy-node-authentication/tree/linking
1 thing I couldn't solve however is while using bcrypt, my code below is trying to isolate the issue:
//check-hash
var bcrypt = require('bcrypt-nodejs');
var salt = bcrypt.genSaltSync(8);
var newAccount = {
username: "admin",
password: "1235",
email: "test#domain.com",
AccCreateOn: Date.now(),
LastLogin: Date.now()
}
newAccount.generateHash = function(password) {
return bcrypt.hashSync(password, salt);
};
// checking if password is valid
newAccount.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
console.log (newAccount.password);
newAccount.password = newAccount.generateHash(this.password);
console.log(newAccount.validPassword("1235")); //false
console.log(newAccount.validPassword("1236"));//false
I already figure out the line at fault is
return bcrypt.compareSync(password, this.password);
which if I change this.password to newAccount.password it will work just fine.
what is wrong as I thought this.password in this context is newAccount.password, or is it not?
I've been through various similar questions here and i tried them all, but still, the result is always "wrong password".
I'm using bcrypt for nodejs and my password hashes are stored in an Postgresql database.
The hash and compare methods are as follow :
generateHash : function(password, callBack){
bcrypt.genSalt(8, function(err, salt) {
bcrypt.hash(password, salt, callBack);
});
}
validPassword : function(password, callBack){
bcrypt.compare(password, this.password, callBack);
}
I'm using these function in the following piece of code :
//Generating hashing and DB storing
User.generateHash(password, function(err, hash) {
// if there is no user with that email
// create the user
var newUser = User.build({
email: email,
password: hash
})
.save()
.then(function(newUser) {
return done(null, newUser);
})
.catch(function(err){
return done(err, false);
});
});
//...
//Checking the password input for login
user.validPassword(password, function(err, res) {
if(err) throw err;
if(!res){
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
}
else{
// all is well, return successful user
return done(null, user);
}
});
I hope that was clear. Thanks in advance. Ciao.
Update I : callBack added to validPassword, although this didn't fix the problem. And i have also checked the this.password value, it's correct and as expected. So, the problem is still present.
I think you forgot to add callBack as parameter to
validPassword : function(password){
Try if adding that solves your problem, so change it to
validPassword : function(password, callBack){
Also, I don't know where your validPassword function is in, but you might want to check if this.password does indeed refer to the users password.
i just solved the problem. lol it was a series of errors that made this hard to figure it out. So i'm just going to enumerate what must be done to avoid such things :
The hash must be stored in the database as a varchar and not as a char. The latest cause the hash to be of a non correct length, and so the comparison will fail. varchar is the solution to this.
Handling the comparison result must be done inside of the callBack function. This is due to nodejs being asynchronous. This was correct (see code in the question) i just want to point it out. Otherwise, the result of the comparison would be undefined.
I hope this will help some of you.
here is my complete code
var express = require('express'),
app = express(),
mongoose = require('mongoose'),
bodyParser = require('body-parser'),
morgan = require('morgan'),
webToken = require('jsonwebtoken'),
bcrypt = require('bcryptjs'),
assert = require('assert');
Schema = mongoose.Schema,
secretKey = "omjdiuwkslxmshsoepdukslsj";
//User Schema
var userSchema = new Schema({
username: {type: String, required: true, index: {unique:true}},
password: {type: String, required: true, select: false}
})
userSchema.pre('save', function(next){
var user = this;
if(!user.isModified('password')) return next();
bcrypt.hash(user.password, null, null, function(err, hash){
if(err) return next(err);
user.password = hash;
next();
})
});
userSchema.methods.comparePassword = function(password){
var user = this;
bcrypt.compare(password, user.password, function(err, result){
if(err){
console.log(err);
}
else {
console.log("passwords match!");
return;
}
})
}
var userModel = mongoose.model('users', userSchema);
//Connecting to Mongo
mongoose.connect("mongodb://localhost/userstories", function(err){
if(err) {
console.log(err);
}
else {
console.log("Connected to database!");
}
});
//Creating Token
function createToken(user){
var token = webToken.sign({
_id: user.id,
username: user.username
}, secretKey,{
expiresIn: 30 * 60 * 1000
})
return token;
}
//Middlewares
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(morgan('dev'));
//Api's
app.post('/signup', function(req, res){
var user = new userModel({
username: req.body.username,
password: req.body.password
})
user.save(function(err){
if(err){
console.log(err);
}
else{
res.json({message: "User created!"});
}
})
})
app.post('/login', function(req, res){
userModel.findOne({username: req.body.username}, function(err, user){
if(err) console.log(err);
if(!user){
res.send("User not found!");
}
else if(user){
var validPassword = user.comparePassword(req.body.password);
if(validPassword){
var tokens = createToken(user);
res.json({
success: true,
message: "Successfully logged In",
token: tokens
});
}
else {
res.send("Invalid password");
}
}
})
});
//Running the server
app.listen(3000, function(err){
if(err) console.log("port not working");
else{
console.log("Everything went just fine");
}
})
I've tried every approaches and saw all the answers here. But no one seem to come across this illegal argument error. Please figure this one out for me Im sure there is a bug I cant see
In your User Schema, you are setting select to false for the password field. This means that anytime you look for a user in the schema as you're trying to do in the login request, you won't get the value of the password field or any other field that has select false defined in the schema.
What you need to do is to specify you need the password when the user is found:
app.post('/login', function(req, res){
userModel.findOne({username: req.body.username}, 'password', function(err, user){
// continue
}
This will return only the _id and the password from the DB. If you want to return other fields, you'd have to add them in:
app.post('/login', function(req, res){
userModel.findOne({username: req.body.username}, 'password firstName lastName email', function(err, user){
// continue
}
I have tried the same code for authentication once and got the same error Error: Illegal arguments: string, function.
But I did not see any issue with the code. The thing is, I had registered two users with the same user name and a different password. Then, when I tried to log in with the user name and one password this error occurred and stopped the server.
So it seems that you are also facing the same problem. Make sure there are no errors with this stuff if you do not want to have a bug in your code.
Check the value of user.password before sending it to bcrypt.compare().
Chances are, you've fetched the user without including the password property, resulting in a value of undefined. This can happen in Sequelize if you set custom attributes or if you're using a scope that excludes props.
Your code is wrong in this place. You may see it.
var validPassword = user.comparePassword(req.body.password);
If you use bcryptjs thrid party plugins, like that
let validPassword = bcrypt.compare(req.body.password, user.password);
bcrypt.compare(password, hashedPassword);
In my particular case, I was dealing with this error, checking out all the code up and down unsuccessfully for almost two days.
Finally, realized that the column PASSWORD in MariaDB was in uppercase. Theoretically that shouldn't affect at all, but I decided to rename it to lowercase and bum! problem solved.
For those using async/await for database calls, make sure you don't forget the await keyword on the User.findOne() call.
In my case, I had forgotten the await keyword while fetching the user. This as a result, was giving me a Promise object instead of User object and hence the password property on it was undefined.
I also encountered the same error when I was using
bcrypt.compareSync("input to be compared with the hash", hash).
Later on I discovered that I was supposed to pass the actual value in the first input parameter i.e (The actual value from which the hash was generated) and the hash in the second input parameter, but I was passing hashed values in both the input parameters.
After correcting the same it was giving me the desired output as true or false.
You can also run and check your code here.
Do like this:
UserSchema.pre('save', async function (next) {
const hash = await bcrypt.hash(this.password, 10);
this.password = hash;
next()
})
You need to specify that you also want the password because you have set the select property to false on password.
So when you are fetching the user, just make sure to explicitly specify that you also want the password. Add .select('+password') on the user object when you are querying a user.
In my case it was a simple spelling mistake before sending to bcrypt.hash :
If you're testing this with Postman, I just found an issue where the default Content-Type header is set to text/plain. If you untick the default header (as it doesn't allow you to change it) and add another Content-Type header with a value of application/json, it works.