How should I store salts and passwords in MongoDB - node.js

I am trying to store passwords and salts in MongoDB and I'm not sure which datatype should be used. When I use strings, the encrypted password appears to be stored correctly, but the generated salt, which is created with new Buffer(crypto.randomBytes(16).toString('base64'), 'base64');, seems to have characters that weren't recognized. For example, I have a salt stored as �y_�6j(�l~Z}0ۡ\" and I don't think this is correct.
Is the problem that it's stored as a string?

While registering a user, you can generate a hashed password using bcrypt. Let's call this password as P#1. Save this hashed password (P#1) in your database only, and not the salt.
While logging in a user, generate hashed version of the password which the user sends, let's call it P#2. Now you just have to match P# and P#2. If they match, the user is authenticated. This way you can perform authentication without actually saving the salt in your database.
I will try to put it in simple way with the help of an example.
// My user schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var userSchema = new Schema({
username: String,
password: String
});
// hash the password
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
// checking if password is valid
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
var User = mongoose.model('user', userSchema);
module.exports = User;
// My APIs for registering and authenticating a user
var User = require('/path/to/user/model');
app.post('/register', function(req, res) {
var new_user = new User({
username: req.body.username
});
new_user.password = new_user.generateHash(req.body.password);
new_user.save();
});
app.post('/login', function(req, res) {
User.findOne({username: req.body.username}, function(err, user) {
if (!user.validPassword(req.body.password)) {
//password did not match
} else {
// password matched. proceed forward
}
});
});
Hope it helps you!

Ankit Gomkale's answer is correct (and IMHO clean!), but you might wonder how it's possible to verify the hashed password is the same as the input string being tested.
This is because the output of bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); is not a "hashed password", it is a "hash string", of the form (source):
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
\__/\/ \____________________/\_____________________________/
Alg Cost Salt Hash
Therefore, this string contains the password hash, but also the salt. Verification of a password (e.g. in bcrypt.compareSync(password, this.password);) will use this salt, not create a new random salt.

Related

Node Postgresql User Password Security

I've created a registration web page and I'm wondering what's the best way to upload password to server and how to store user passwords.
Currently, I just have a field for password and when the user clicks "submit", it makes a call to the server then insert the password into the db as is.
My question is at which step should I encrypt the password and what are some good methods of keeping this data secure?
I've looked around but haven't found much resource on this, maybe I'm not searching the right things.
The popular way of doing this in node is using bcrypt password encoder.
const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';
// hashing
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});
// comparing
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});

Updating password in Mongoose with pre hook results in passport.js password check failing

Ive implemented a password authentication with passport and local authentication in Node.js that is as follows:
userSchema.pre('save', function (next) {
if (this.password) {
this.salt =
Buffer.from(crypto.randomBytes(16).toString('base64'), 'base64');
this.password = this.hashPassword(this.password);
}
next();
});
userSchema.methods.hashPassword = function (password) {
return crypto.pbkdf2Sync(password, this.salt, 10000,
64, 'sha512').toString('base64');
};
Now I added a pre hook for updating the password where I create a new salt and hash the new password. The update itself is executed, but the resulting new salt and password hash fail the password check.
I added this code (which is a bit raw, but for now I would be happy if the concept works):
userSchema.pre('findOneAndUpdate', function (next) {
if (this._update.password) {
this.salt =
Buffer.from(crypto.randomBytes(16).toString('base64'), 'base64');
this._update.salt = this.salt;
this._update.password = crypto.pbkdf2Sync(this._update.password, this.salt, 10000,64,'sha512').toString('base64');
}
next();
});
The update is done with this code:
users.findOneAndUpdate({email: req.body.emailadres },
{password: req.body.password}).exec();
I see the salt and password values being updated in the collection, but the authentication returns a failure on login after the update.
I'd expect that the update of the fields as in the above code should work. Am i missing something? Also, how is the hashPassword reusable for the update hook?

Authenticating Users with Password in React Native

Guys I have some general questions about interacting with my PostgreSQL DB my ExpressJS API and my React Native Frontend.
I managed to write some queries that for example check if the username that is entered in my RN app matches the one that is on the DB and returns its userID.
I know that on my DB every User has its Password that is being stored inside the DB in a hashed way.
How would a Query look like that would return for example the userID as I am doing it but only if the username exists and it matches the Password that is stored on the DB?
Do I need to know how the passwords are hashed on the DB?
The Query for userid:
static getUserId (username, callback) {
db.query(`SELECT userid FROM employee WHERE username = '${username}'`, (err, res)=> {
if(err.error){
return callback(err.error)
} else {
return callback(res)
}
})
}
The passwords are saved in the same employee table under password.
EDIT: I think I can take two WHERE statements like this:
`SELECT userid FROM employee WHERE username = '${username}' AND WHERE password = '${password}'`
but if a User enters his password inside the app it is not hashed so do I need to send the same hash method to compare these values?
Thanks in advance!
Best regards Faded.
You can use bcrypt.js for hashing your passwords.
To hash password in SignUp.js:
app.post('/signup',(req,res,next)=> {
//Password from request body of register request
const myPlaintextPassword = req.body.password
//saltRounds are typically set to 12
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB with INSERT.
});
}
Now to compare with Hash in Login.js:
app.post('/login',async (req,res,next)=>{
const existingUser = await db.query(`SELECT userid FROM employee WHERE username = '${username}'`, (err, res)=> {
if(err.error){
return callback(err.error)
} else {
return callback(res)
}
})
const valid = await bcrypt.compare(req.body.password, existingUser.password);
if (!valid) {
res.status(401).send('Wrong pass');
return req.next()
}
}
You need to hash the incoming password and compare the two hashes.
That being said: if this is going to be at all used by real people I would suggest using a library to handle your auth instead of writing your own. There are a lot of things that can go wrong to make your application insecure.

Issue with nodejs bcrypt when using keyword this

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?

Bcrypt error: illegal arguments String unidentified

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.

Resources