JWT updating payload from node.js - javascript

I am using Satellizer in my MEAN Stack webapp to login users. The satellizer module uses JSON Web Tokens.
The token is created in:
var jwt = require('jwt-simple');
function createJWT(user) {
var payload = {
sub: user._id,
user: {
displayName: user.displayName,
email: user.email,
admin: user.admin
},
iat: moment().unix(),
exp: moment().add(2, 'hours').unix()
};
return jwt.encode(payload, config.TOKEN_SECRET);
}
app.post('/auth/login', function(req, res) {
User.findOne({ email: req.body.email }, '+password', function(err, user) {
if (!user) {
return res.status(401).send({ message: 'Wrong email and/or password' });
}
user.comparePassword(req.body.password, function(err, isMatch) {
if (!isMatch) {
return res.status(401).send({ message: 'Wrong email and/or password' });
}
res.send({ token: createJWT(user) });
});
});
});
The thing is that later in a function, I need to update the user key inside the payload object.
Is this possible?

Basically token looks like string. when you change payload then your token is changed (new string). You can't change token / payload without changing string. You can create new one based on previous.
Remember to return new token to client application.

Related

Mongoose gives no response when updating object in Mongo

I have a simple ExpressJS/Node backend that contains a MongoDB database for which I use mongoose to interact. I can add objects to the db based on the UserSchema:
const userSchema = mongoose.Schema({
email : {
type: String,
required: true,
trim: true,
unique: 1
},
password : {
type: String,
required: true,
minlength: 5
},
name : {
type: String,
required: true,
maxlength: 30
},
lastname : {
type: String,
required: true,
maxlength: 30
},
cart : {
type : Array,
default: []
},
history : {
type: Array,
default: []
},
role : {
type: Number,
default : 0
},
token : {
type: String
}
});
From the express Server, I can register and add a new user to the DB and I know this works
Server.js
//========================================
// Register User
//========================================
app.post('/api/users/register', (req, res) => {
//create new User
const user = new User(req.body);
//save user
user.save((err, doc) => {
if(err)
return res.json({success: false, err});
res.status(200).json({
success : true,
userdata: doc
});
});
})
In User.js
//========================================
// SAVE in DB
//========================================
const User = mongoose.model('User', userSchema);
Now when I want to login, operation where I need to check the email and password match I encounter a problem when everything is fine and I want to add the JWT to the object all is good until it gets to the save method, there nothing happens and it doesn't respond anymore. It's like it goes in an infinite loop. I get error when something is wrong, but on the positive case, it disappears and sends no response, to either mongo, node, debug anything.
Server.js
app.post('/api/users/login', (req, res) => {
//find the email for the user
User.findOne({'email' : req.body.email} , (err, user) =>{
if(!user)
return res.json({loginSuccess : false, message : 'Authentication failed, email not found'});
//check the password
user.comparePassword(req.body.password, (error, isMatch) => {
if(!isMatch)
return res.json({loginSuccess : false, message : 'Wrong password'});
//generate token
user.generateToken((err, user) => {
if(err)
return res.status(400).send(err);
//store token as a cookie
res.cookie('w_auth', user.token).status(200).json({
loginSuccess : true
})
})
})
})
})
User.js
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const SALT_I = 10;
require('dotenv').config();
//========================================
// User Login
//========================================
userSchema.methods.comparePassword = function (candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(error, isMatch){
if(error)
return cb(error);
cb(null, isMatch);
})
}
userSchema.methods.generateToken = function (cb) {
var user = this;
var token = jwt.sign(user._id.toHexString(),process.env.SECRET)
user.token = token;
user.markModified('anything');
user.save(function(err,user){
if(err) return cb(err);
cb(null,user);
})
}
I get no more feedback in node console, debug, Mongo or even Postmen(I can wait here for minutes ) after user.save(...). I know it gets the good user and everything but I don't really know where to get from here. Also in Mongo I see no field for the token, I initially add an object with no token, can this affect everything? Is there another procedure to update an existing object in the collection?
In case GitHub is needed to see the code: Link
Indeed it's really strange, couldn't really debug what's wrong with this 'save' method. As a workaround, however, this one seems to work fine:
userSchema.methods.generateToken = function (cb) {
var user = this;
var token = jwt.sign(user._id.toHexString(), "mystupidsecret");
console.log("in generateToken");
console.log(user);
user.token = token;
console.log(user.token);
var email = user.email;
//save token
User.updateOne({ _id: user._id }, { $set: { token: token } }, function(err, user){
if(err) {
console.log(err);
return cb(err);
}
cb(null, user);
// this one is for debug only!
User.findOne({'email' : email} , (err, user) =>{
console.log("After update: ", user)
});
});
console.log('done');
}
It yields the following:
After update: { cart: [],
history: [],
role: 0,
_id: 5f3e48f09c7edc3f1c24a860,
email: 'abc233#wp.pl',
password:
'$2b$10$iDeeehLOzbQi3dawqW8Lg.HPOvcRBDIS/YD9D1EmqBOH9Be31WpX2',
name: 'ABCDEFGH',
lastname: 'Doeasdasdas',
__v: 0,
token:
'eyJhbGciOiJIUzI1NiJ9.NWYzZTQ4ZjA5YzdlZGMzZjFjMjRhODYw.aH9tCMbIK9t3CReiQg3Azln9Ca8xS7W0xL3qCMOKniY' }

NodeJS API POST request not flowing through

I am building a Node, Express-based API for user authentication. I am using mongo to store the data. Using postman, I submitted a /post request by passing in the following object
{
"username": "abc",
"password": "abc123",
"email": "abc#ghi.com"
}
under req.body.
This is how my /post function looks in the code:
//create one user
router.post('/createUser', async (req, res) => {
if (!req.body.email || !req.body.password || !req.body.username) {
return res.status(400).send({
message: "No username/password/email specified"
});
}
const newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password
});
await User.create(newUser, (err, data) => {
//res.send(data);
if(err) {
console.log(err);
res.status(500).send('Error creating user');
}
});
});
User.create() method calls .save() method under the covers. I have a pre-condition on saving to encrypt passwords. On running the post, I get an error that says UnhandledPromiseRejectionWarning: Error: data and salt arguments required
I did some console logging and noticed that this is happening because user.password is coming in as undefined. So it looks like my request is not going through properly from the postman.
Edit:
Here is the schema:
const userSchema = new mongoose.Schema({
id: {
type: Number
},
email: {
type: String,
unique: true,
required: true,
},
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
});
userSchema.pre('save', (next) => {
const user = this;
console.log(user.password);
bcrypt.hash(user.password, 10, (err, hash) => {
if (err) {
next(err);
} else {
user.password = hash;
next();
}
});
});
Can someone please help me understand what's wrong?
You cannot use arrow function in .pre hooks because arrow function does not bind "this". "this" is supposed to refer to each individual user that about to be saved. however if you use "this" inside the arrow function, it will point to the global object. run this code console.log(this) you will see. use arrow functions for standalone functions. in your case, you are creating a method that part of the object so you should avoid using arrow function
I do not use .pre, because some mongoose queries bypass mongoose middleware, so u need to do extra work. so instead I hash the password inside the router, so everything related to user will be in the same place. single source of truth
const bcrypt = require("bcrypt");
router.post('/createUser', async (req, res) => {
if (!req.body.email || !req.body.password || !req.body.username) {
return res.status(400).send({
message: "No username/password/email specified"
});
}
const newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password
});
//we created newUser and now we have to hash the password
const salt = await bcrypt.genSalt(10);
newUser.password = await bcrypt.hash(newUser.password, salt);
await newUser.save();
res.status(201).send(newUser)
//201 code success for something is created
});
here is the list of http status codes:
https://httpstatuses.com/
The password from postman is getting received on your NodeJS code.
In your code:
const newUser = new User({
email: req.body.email,
username: req.body.username,
password: req.body.password
});
when you do this, your expected output from newUser changes.
so when your code reaches here...
const user = this;
console.log(user.password);
Instead of logging user.password
try logging user itself like...
console.log(user)
and see if
"(const user=this)" is giving what you expected.
signUp: (req, res, next) => {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(req.body.password, salt, (err, hashedPass) => {
let insertquery = {
'_id': new mongoose.Types.ObjectId(),
'username': req.body.username,
'email': req.body.password,
'salt': salt,
'password': hashedPass
};
user.create(insertquery, function (err, item) {
});
});
});
}

Expected "payload" to be a plain object : MEAN

This is my code from routes file(users.js)
User.findOne({linkedin_id: req.body.linkedin_id}, function(err, linkedinUser) {
if(err) {
console.log('err in finding linkedin user '+err);
}
// if user exits
else if(linkedinUser) {
console.log('user exist');
const token = jwt.sign(linkedinUser, config.secret, {expiresIn: 604800});
res.json({success: true, token: 'JWT '+token, user: {
id: linkedinUser._id,
linkedin_id: linkedinUser.linkedin_id,
name: linkedinUser.name,
username: linkedinUser.username,
email: linkedinUser.email,
lkprofilePic: linkedinUser.profilePic
}, msg: 'user exits'
});
}
// if user doesn't exist
else {
User.create({
linkedin_id: req.body.linkedin_id,
name: req.body.name,
username: req.body.username,
email: req.body.email,
lkprofilePic: req.body.lkprofilePic
}, function(err, result) {
if(err) {
res.json({success: false, msg: 'failed to add'})
console.log('error in adding the data '+err);
}
else if(result) {
const token = jwt.sign(linkedinUser,config.secret,{ expiresIn: 604800 });
res.json({success: true, token: 'JWT '+token, user: {
id: result._id,
linkedin_id: result.linkedin_id,
name: result.name,
username: result.username,
email: result.email,
lkprofilePic: result.profilePic
}, msg: 'User added ' });
}
});
}
});
This from the config -> secret
module.exports = {
secret: 'bigfish'
}
This is the error I'm getting in the nodejs console
Receiving linkedin data
D:\product\project-1\node_modules\mongodb\lib\utils.js:132
throw err;
^
Error: Expected "payload" to be a plain object.
at validate (D:\product\project-1\node_modules\jsonwebtoken\sign.js:34:11)
at validatePayload (D:\product\project-1\node_modules\jsonwebtoken\sign.js:56:10)
at Object.module.exports [as sign] (D:\product\project-1\node_modules\jsonwebtoken\sign.js:108:7)
at D:\product\project-1\routes\users.js:415:29
at Function. (D:\product\project-1\node_modules\mongoose\lib\model.js:4177:16)
at parallel (D:\product\project-1\node_modules\mongoose\lib\model.js:2230:12)
at D:\product\project-1\node_modules\mongoose\node_modules\async\internal\parallel.js:35:9
at D:\product\project-1\node_modules\mongoose\node_modules\async\internal\once.js:12:16
at iteratorCallback (D:\product\project-1\node_modules\mongoose\node_modules\async\eachOf.js:52:13)
at D:\product\project-1\node_modules\mongoose\node_modules\async\internal\onlyOnce.js:12:16
at D:\product\project-1\node_modules\mongoose\node_modules\async\internal\parallel.js:32:13
at apply (D:\product\project-1\node_modules\lodash_apply.js:15:25)
at D:\product\project-1\node_modules\lodash_overRest.js:32:12
at callbackWrapper (D:\product\project-1\node_modules\mongoose\lib\model.js:2199:11)
at D:\product\project-1\node_modules\mongoose\lib\model.js:4177:16
at model.$__save.error (D:\product\project-1\node_modules\mongoose\lib\model.js:359:7)
But the data is getting saved in the database & doesn't return the
res.json({success: true, token: 'JWT '+token, user: {
id: result._id,
linkedin_id: result.linkedin_id,
name: result.name,
username: result.username,
email: result.email,
lkprofilePic: result.profilePic
}, msg: 'User added ' });
The issue is with the way you signed your token
The user you are using is a returned user from mongoose so you will need to use YOUR_USER.toJSON. if the user is not coming from mongoose use JSON.stringify(YOUR_USER) instead
change your code to either
const token = jwt.sign({linkedinUser}, config.secret, {expiresIn: 604800});
//if you want to set expiration on the token
OR
const token = jwt.sign(linkedinUser.toJSON(), config.secret);
//if you just want to sign the token without setting the expiration
const token=jsonwebtoken.sign(user.toJSON(),config.secret,{expiresIn:30});
add .toJSON() with your object then it will be ok

Sails JS passport http 401

I'm trying to secure my sails js rest api with the help of the passport http package but at the moment I can't figure out where the error is in my code.
I used this repo and this tutorial to get an idea of how this should work. My problem is that my code always returns a 401.
I don't really know where to look for the error. If you need more information about my code just comment.
Bruno
EDIT:
I found the source of the problem (With the help of #Viktor). I just didn't really understood how HTTP-Basic authentication works. Now the problem is how do I send my auth-credentials and my data? If I just test the routes with auth(...), they work... But how do I add the data? Or do I have to authenticate me first and send the data in the second request?
passport.js
var passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy;
var bcrypt = require('bcrypt');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findOne({
id: id
}, function(err, user) {
done(err, user);
});
});
passport.use('user-authentication', new BasicStrategy(
function(mail, password, done) {
sails.log.error("hallo");
User.findOne({
mail: mail
}, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Incorrect email.'
});
}
// Make sure the password is correct
bcrypt.compare(password, user.password, function(err, isMatch) {
if (err) {
return done(err);
}
// Password did not match
if (!isMatch) {
return done(null, false, {
message: 'Invalid Password'
});
}
// Success
return done(null, user);
});
});
}
));
isAuthenticated.js
var passport = require("passport");
module.exports = function (req, res, ok) {
passport.authenticate("user-authentication", {
session: false
}, function (err, user, info) {
if (err || !user) {
res.set("WWW-Authenticate", "Basic realm=\"Restricted\"");
return res.send("You are not permitted to perform this action", 401);
}
req.session.user = user;
return ok(null, user);
})(req, res, ok);
};
policies.js
module.exports.policies = {
'*': true,
'UserController': {
update: 'isAuthenticated'
}
}
UserController.test.js
var request = require('supertest');
var async = require('async');
describe('UserController', function() {
describe('#new()', function() {
it('...', function (done) {
request(sails.hooks.http.app)
.post('/user/new')
.send({ own_number: '654122', password: 'test', mail: 'test#test.com', device_id: '1234', numbers: [1234567] })
.expect(200)
.end(done);
});
});
describe('#update()', function(){
it('...', function (done) {
async.series([
function(callback){
request(sails.hooks.http.app)
.post('/contact/update')
.send({ number: 1234, mail: "test#test.com", password: "test" })
.expect(200)
.end(callback);
},
function(callback){
request(sails.hooks.http.app)
.post('/user/update')
.send({ numbers: [1234], mail: "tet#test.com", password: "test" })
.expect(200)
.end(callback);
}
], done);
});
});
});
Works for me – I'm able to authenticate as well as access and manage the user data once there's a valid user in the database. With an empty user database, you would get 401 all the time, of course, as these policies don't allow you to create even the first user to authenticate as. Temporarily disabling the UserController policies in config/policies.js gives you the opportunity to create the first user.
Assuming you have at least one valid user in the database, let's narrow down the problem. What output do you get from logging err and user in isAuthenticated.js? If you get null and false, what happens in the different steps in passport.js - are you able to find the user by e-mail address in the database and does the password match?
Do you have custom routes and controller actions or do you use the blueprints?
EDIT: Your update test would look like this with HTTP Basic Authentication:
describe('#update()', function(){
it('...', function (done) {
async.series([
function(callback){
request(sails.hooks.http.app)
.post('/contact/update')
.auth('test#test.com', 'test')
.send({ number: 1234, mail: "test#test.com", password: "test" })
.expect(200)
.end(callback);
},
function(callback){
request(sails.hooks.http.app)
.post('/user/update')
.auth('test#test.com', 'test')
.send({ numbers: [1234], mail: "tet#test.com", password: "test" })
.expect(200)
.end(callback);
}
], done);
});
});
From the documentation here it shows you need userid and password. So looking at your code you have mail in place of userid and that is why you are getting 401 or at least where you can start looking. If you need to verify this you can look at passport-http basic strategy here.
passport.use(new BasicStrategy(
function(userid, password, done) {
User.findOne({ mail: userid, password: password }, function (err, user) {
done(err, user);
});
}
));

Compare passwords BcryptJS

So I'm trying to build a very basic user login. I'm trying to create a user, then login with those credentials and get back a JSON Web Token. Where I'm stuck is trying to compare the passwords then send a response.
Steps:
Create User:
enter email and password
salt/hash user password
store user into database
return success
Login
find user by request email value
if found compare passwords
passwords good send JSON Web Token
User Model
email:{
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
User Routes
var express = require('express');
var router = express.Router();
var jwt = require('jsonwebtoken');
var bcrypt = require('bcryptjs');
// Create User
...
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash("superSecret", salt, function(err, hash) {
user.password = hash;
user.save();
res.json({success: true, message: 'Create user successful'});
});
});
...
// Login
...
bcrypt.compare(req.body.password, 'superSecret', function(err, res) {
if(req.body.password != user.password){
res.json({success: false, message: 'passwords do not match'});
} else {
// Send JWT
}
});
So the two problems here is that, I can't send a response nor can I compare the password. Just completely stuck on this, any help would be greatly appreciated.
As described in the doc, you should use bcrypt.compare like that:
bcrypt.compare(req.body.password, user.password, function(err, res) {
if (err){
// handle error
}
if (res) {
// Send JWT
} else {
// response is OutgoingMessage object that server response http request
return response.json({success: false, message: 'passwords do not match'});
}
});
And here is a nice post about Password Authentication with Mongoose (Part 1): bcrypt
//required files
const express = require('express')
const router = express.Router();
//bcryptjs
const bcrypt = require('bcryptjs')
//User modal of mongoDB
const User = require('../../models/User')
//Post request for login
router.post('/', (req, res) => {
//email and password
const email = req.body.email
const password = req.body.password
//find user exist or not
User.findOne({ email })
.then(user => {
//if user not exist than return status 400
if (!user) return res.status(400).json({ msg: "User not exist" })
//if user exist than compare password
//password comes from the user
//user.password comes from the database
bcrypt.compare(password, user.password, (err, data) => {
//if error than throw error
if (err) throw err
//if both match than you can do anything
if (data) {
return res.status(200).json({ msg: "Login success" })
} else {
return res.status(401).json({ msg: "Invalid credencial" })
}
})
})
})
module.exports = router
If we you to use bcryptjs in browser(HTML) then you can add bcryptjs CDN to do this.
CDN - https://cdn.jsdelivr.net/npm/bcryptjs#2.4.3/dist/bcrypt.js
Example-
HTML- (Add above CDN in tag)
JS-
var bcrypt = dcodeIO.bcrypt;
/** One way, can't decrypt but can compare */
var salt = bcrypt.genSaltSync(10);
/** Encrypt password */
bcrypt.hash('anypassword', salt, (err, res) => {
console.log('hash', res)
hash = res
compare(hash)
});
/** Compare stored password with new encrypted password */
function compare(encrypted) {
bcrypt.compare('aboveusedpassword', encrypted, (err, res) => {
// res == true or res == false
console.log('Compared result', res, hash)
})
}
If you want to do same in Nodejs
/** Import lib like below and use same functions as written above */
var bcrypt = require('bcryptjs')
From what I can see your logic is correct.
If you are using mongoose I suggest you to use the pre 'save' hook.
User Schema
userSchema.pre('save', function(next) {
// only hash the password if it has been modified (or is new)
if (!this.isModified('password')) {
return next();
}
// generate a salt
return bcrypt.genSalt(10, function(error, salt) {
if (error) {
return next(error);
}
// hash the password using the new salt
return bcrypt.hash(this.password, salt, function(error, hash) {
if (error) {
return next(error);
}
// override the cleartext password with the hashed one
this.password = hash;
return next();
});
});
});
userSchema.methods.comparePassword = function(passw, cb) {
bcrypt.compare(passw, this.password, function(err, isMatch) {
if (err) {
return cb(err, false);
}
return cb(null, isMatch);
});
};
And in your routes:
Login
...
return user.comparePassword(password, function(error, isMatch) {
var payload = {
iat: Math.round(Date.now() / 1000),
exp: Math.round((Date.now() / 1000) + 30 * 24 * 60),
iss: 'Whatever the issuer is example: localhost:3000',
email: user.email
};
var token = jwt.encode(payload, 'secret');
if (isMatch && !error) {
// if user is found and password is right create a token
return res.json({
success: true,
token: `JWT ${token}`,
user: user,
msg: 'Authentication was succesful'
});
}
return next({code: 401, msg: 'Password is incorrect'});
});
});
Create user
// Pre hook will take care of password creation
return user.save()
.then(function(user) {
var payload = {
iat: Math.round(Date.now() / 1000),
exp: Math.round((Date.now() / 1000) + 30 * 24 * 60),
iss: 'Whatever the issuer is example: localhost:3000',
email: user.email
};
var token = jwt.encode(payload, 'secret');
return res.status(201).json({user, token: `JWT ${token}`, msg: 'User was succesfully created'});
})
.catch((err) => next(err));
bcrypt.compare(req.body.password, user.password, function(err, results){
if(err){
throw new Error(err)
}
if (results) {
return res.status(200).json({ msg: "Login success" })
} else {
return res.status(401).json({ msg: "Invalid credencial" })
}
})
const bcrypt = require("bcryptjs");
const salt = bcrypt.genSaltSync(10);
const hashPassword = (password) => bcrypt.hashSync(password, salt);
const comparePassword = (password, hashedPassword) =>
bcrypt.compareSync(password, hashedPassword);
bcrypt.compare(req.body.password, user.password)
.then(valid => {
if (!valid) {
return res.status(401).json({ message: 'Paire login/mot de passe incorrecte' });
}
res.status(200).json({
userId: user._id,
token:jwt.sign(
{userId: user._id},
process.env.ACCESS_TOKEN_SECRET_KEY,
{expiresIn:'24h'}
),
message: 'connected'
});
})
.catch(error => res.status(500).json({ error }));
enter code here

Categories

Resources