I'm writing an API with NodeJS and Express for a schoolproject and I'm struggling with the following:
The function getAuthUserId decodes the JWT token and gets the Id from user in the mongoDB server.
I call this function in a REST call "/user/authTest". But when I call this, the server responds before the database can return the Id, and the variable UId is undefined. As you can see, the Id is actually found. Any ideas on how i can fix this?
The API call code:
apiRoutes.post('/user/authTestID', function(req, res) {
var UId = getAuthUserId(req, res);
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
The function:
function getAuthUserId(req, res) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
return false
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
return user._id
}
});
} else {
res.status(403).send({success: false, msg: 'No token provided.'});
return '';
}
}
The output of the terminal:
[nodemon] restarting due to changes...
[nodemon] starting `node server.js`
Connected to MongoDB
undefined
::ffff:192.168.0.111 - POST /user/authTestID HTTP/1.1 400 11 - 175.006 ms
Auth for test 58f8954c3602b80552b6f1fb
Thanks in advance!
You need to make it a promise, like this.
API
apiRoutes.post('/user/authTestID', function(req, res) {
getAuthUserId(req, res).then(function (UId) => {
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
}, function(err) {
console.log(err.msg)
res.status(err.status).send(err.msg);
});
Function
function getAuthUserId(req, res) {
return new Promise(function(resolve, reject){
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
reject({status: 403, msg: 'Authentication failed. User not found.'});
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
resolve(user._id)
}
});
} else {
reject({status: 403, msg: 'No token provided.'});
}
})
}
getAuthUserId get's the value in a CALLBACK !!! . You can't expect it to return values from it. As quick thing you can do something as below.
apiRoutes.post('/user/authTestID', function (req, res) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function (err, user) {
if (err) throw err;
if (!user) {
return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
return res.send(user._id)
}
});
} else {
return res.status(403).send({success: false, msg: 'No token provided.'});
// return '';
}
});
Try using Promise library like Bluebird
James' comment looks like a good, thorough resource on async calls. As others have mentioned, you cannot return values within a callback. You can use a Promise library, or you can change your getAuthUserId function to take a callback and have your console.log logic in there:
Example:
API call code:
apiRoutes.post('/user/authTestID', function(req, res) {
getAuthUserId(req, res, function(UId) {
// we're in a your new callback
console.log(UId);
if (UId) {
res.sendStatus(200);
}else{
res.sendStatus(400);
}
});
});
Function Code
// note new callback param
function getAuthUserId(req, res, callback) {
var user = new User();
var token = user.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, config.secret);
User.findOne({
name: decoded.name
}, function(err, user) {
if (err) throw err;
if (!user) {
res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
callback(false) // no more return, call callback with value
} else {
console.log('Auth for ' + user.name + ' ' + user._id);
callback(user._id) // no more return, call callback with value
}
});
} else {
res.status(403).send({success: false, msg: 'No token provided.'});
callback(''); // no more return, call callback with value
}
}
Related
I want to make a middleware to check the user. I'm using JWT and cookies for this. I retrieved the cookie and decrypted it (it has been encrypted in the login controller function). Then I used jwt.verify(). But I've received this error message : JsonWebTokenError: jwt malformed.
I've seen that it may mean that the token wasn't "the right formated" one. But I can't figure it out.
checkUser function :
exports.checkUser = async(req, res, next) => {
const cryptedToken = req.cookies.snToken;
console.log("cryptedToken01", cryptedToken); //displays a string consists of 3 parts, separated by /
const token = cryptojs.AES.decrypt(cryptedToken, process.env.COOKIE_KEY).toString();
console.log("token01", token); // displays a longer monolithic string
if (token) {
jwt.verify(token, process.env.COOKIE_KEY, async(err, verifiedJwt) => {
if (err) {
console.log("err inside jwt verify", err); // displays an error mesassage (JsonWebTokenError: jwt malformed)
console.log("res.locals", res.locals); //displays res.locals [Object: null prototype] {}
res.locals.user = null;
res.cookie("snToken", "", { maxAge: 1 });
next();
} else {
let user = await User.findByPk(verifiedJwt.userId);
res.locals.user = user;
next();
}
});
} else {
res.locals.user = null;
next();
}
};
my login function :
exports.login = async(req, res) => {
try {
const user = await User.findOne({ where: { email: req.body.email } });
if (!user) {
return res.status(403).send({ error: 'The login information (email) is incorrect!' });
}
bcrypt
.compare(req.body.password, user.password)
.then((isPasswordValid) => {
if (!isPasswordValid) {
return res.status(403).send({ error: 'The login information (pwd) is incorrect!' });
} else {
const newToken = jwt.sign(
{ userId: user.id },
process.env.COOKIE_KEY, { expiresIn: "24h" }
);
const newCookie = { token: newToken, userId: user.id };
const cryptedToken = cryptojs.AES.encrypt(JSON.stringify(newCookie), process.env.COOKIE_KEY).toString();
res.cookie('snToken', cryptedToken, {
httpOnly: true,
maxAge: 86400000
});
//res.status(200).send({ message: 'The user is successfully connected!', data: user });
res.status(200).send({ message: 'The user is successfully connected!', data: user, cryptedToken: cryptedToken });
}
});
} catch (error) {
res.send({ error: 'An error has occured while trying to log in!' });
}
}
The call of these middlwares in my app.js:
app.get('*', checkUser);
In your current code, you get a Hex encoded ASCII string after decryption
7b22746f6b656e223a2265794a68624763694f694a49557a49314e694973496e523563434936496b705856434a392e65794a3163325679535751694f6a45314c434a70595851694f6a45324e4445314e6a45324d545173496d5634634349364d5459304d5459304f4441784e48302e693670564f486443473456445154362d3749644545536f326251467765394d4b34554a316f363676564334222c22757365724964223a31357d,
which contains your cookie as a stringified JSON.
Instead of toString() after decrypting, which causes the hex encoded output, call toString(cryptojs.enc.Utf8) to get a JSON String and then parse it to an object:
const bytes = cryptojs.AES.decrypt(cryptedToken, process.env.COOKIE_KEY);
const cookie = JSON.parse(bytes.toString(cryptojs.enc.Utf8));
console.log("token", cookie.token);
the result is a correct JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjE1LCJpYXQiOjE2NDE1NjE2MTQsImV4cCI6MTY0MTY0ODAxNH0.i6pVOHdCG4VDQT6-7IdEESo2bQFwe9MK4UJ1o66vVC4
I am new to NodeJS and I have been following a tutorial. I made some changes to the tutorial code in order to allow users to login by typing in their username or email instead of just username.
passport.use(
new LocalStrategy(
(username_or_email, password, done) => {
User.findOne({ email: username_or_email }, (err, user) => {
if (err) {
return done(err);
console.log(err);
}
if (!user) {
User.findOne({ username: username_or_email }, (err, user) => {
//If there is an error
if (err) {
return done(err);
console.log(err);
}
//If there is no user
if (!user) {
return done(null, false);
} else {
user.comparePassword(password, done);
}
});
} else {
user.comparePassword(password, done);
}
});
}
)
The code above works well and allows users to type in username OR password to login.
Now when I follow the tutorial for how to logout, their method doesn't work for me.
I have a route like this, it is supposed to log a user out.
userRouter.get(
'/logout',
passport.authenticate('jwt', { session: false }),
(req, res) => {
res.clearCookie('access_token');
res.json({ user: { username: '', role: '' }, success: true });
}
);
When I go to it in Postman it says "Unauthorized" and does not return anything else.
I believe it could be something to do with my 'jwt' set up, shown below.
passport.use(
new JwtStrategy(
{
jwtFromRequest: cookieExtractor,
secretOrKey: 'NoobCoder',
},
(payload, done) => {
console.log(payload);
User.findById({ _id: payload.sub }, (err, user) => {
if (err) {
return done(err, false);
console.log('1');
}
if (user) {
return done(null, user);
console.log('2');
} else {
return done(null, false);
console.log('3');
}
});
}
)
);
This is the cookieExtractor function that I use for jwtFromRequest
const cookieExtractor = (req) => {
let token = null;
console.log(token);
if (req && req.cookies) {
token = req.cookies['access_token'];
}
return token;
};
The only console output I get is the console.log in the cookieExtractor.
Which makes me believe that that must be the point of failure. It is a "null" output as expected, and if I console.log the token, I get the current logged in users token. I believe the jwtFromRequest calls the cookieExtractor function but fails at some point soon after.
I have made simple signup, signin and article using MEAN.JS with jsonwebtoken.
In signup page after user entering all values i am passing values to server through signup api. The server side I am creating jsonwebtoken and am passing to client side
exports.create = function (req, res, next) {
var newUser = new User(req.body);
newUser.provider = 'local';
newUser.role = 'user';
newUser.save(function(err, user) {
if (err) return validationError(res, err);
var token = jwt.sign({
_id: user._id
}, config.secrets.session, {
expiresInMinutes: 60 * 5
});
res.json({
token: token
});
});
};
After getting that token client calling some 'me' api (I did not understand what is that me is passing)
client side signup controller:
$scope.register = function(form) {
Auth.createUser({
username: $scope.user.name,
useremail: $scope.user.email,
password: $scope.user.password
})
};
auth.service:
createUser: function(user, callback) {
var cb = callback || angular.noop;
return User.save(user,
function(data) {
$cookieStore.put('token', data.token);
currentUser = User.get();
return cb(user);
},
function(err) {
this.logout();
return cb(err);
}.bind(this)).$promise;
}
user.service :
.factory('User', function ($resource) {
return $resource('/api/users/:id/:controller', {
id: '#_id'
},
{
changePassword: {
method: 'PUT',
params: {
controller:'password'
}
},
get: {
method: 'GET',
params: {
id:'me'
}
}
});
});
After signup:
get: {
method: 'GET',
params: {
id:'me'
}
}
I did not understand this. In server side 'me' api looking like this
route:
router.get('/me', auth.isAuthenticated(), controller.me);
controller :
exports.me = function(req, res, next) {
var userId = req.user._id;
User.findOne({
_id: userId
}, '-salt -hashedPassword', function(err, user) {
if (err) return next(err);
if (!user) return res.status(401).send('Unauthorized');
res.json(user);
});
};
auth.service:
var validateJwt = expressJwt({ secret: config.secrets.session });
/**
* Attaches the user object to the request if authenticated
* Otherwise returns 403
*/
function isAuthenticated() {
return compose()
// Validate jwt
.use(function(req, res, next) {
// allow access_token to be passed through query parameter as well
if(req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.query.access_token;
}
validateJwt(req, res, next);
})
// Attach user to request
.use(function(req, res, next) {
User.findById(req.user._id, function (err, user) {
if (err) return next(err);
if (!user) return res.status(401).send('Unauthorized');
req.user = user;
next();
});
}).use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') {
var e = [];
e.push(err);
return res.status(401).send(e);
}
});
}
I want to know what they are passing in the 'me' api and how I'm getting 'req.user._id' in exports.me function. If I want to make the 'me' api (my own), how can I pass this my token?
The server side console I'm getting this: GET /api/users/me 200 876ms - 339b.
This question is related to Function is undefined, Bookshelf.js model function is not being recognized as a function
I am using Bookshelf.js to handle user register/login API-endpoints, built with NodeJS and ExpressJS. However, when POSTing to the login route, Bookshelf throws the following error:
Unhandled rejection TypeError: undefined is not a function
at \routes\index.js:217:36
at Strategy.strategy.success (\node_modules\passport\lib\middleware\authenticate.js:194:18)
at verified (\node_modules\passport-local\lib\strategy.js:83:10)
at null.<anonymous> (\config\passport.js:17:20)
...
The following is routes/index.js
var User = require('./models/User');
router.post('/login', function(req, res, next){
if(!req.body.username || !req.body.password){
return res.status(400).json({message: 'Please fill out all fields'});
}
passport.authenticate('local', function(err, user, info){
if(err){ return next(err); }
if(user){
return res.json({token: user.generateJWT()});
} else {
return res.status(401).json(info);
}
})(req, res, next);
});
The following is models/Users.js
var jwt = require('jsonwebtoken');
var bcrypt = require('bcrypt');
var bookshelf = require('../config/bookshelf');
var User = bookshelf.Model.extend({
tableName: 'users',
validPassword: function(password, encryptedPass) {
return new Promise(function (resolve, reject) {
bcrypt.compare(password, encryptedPass, function (err, match) {
if(err) return reject(err);
resolve(match);
});
});
},
generateJWT: function() {
// set expiration to 60 days
var today = new Date();
var exp = new Date(today);
exp.setDate(today.getDate() + 60);
return jwt.sign({
_id: this._id,
email: this.email,
exp: parseInt(exp.getTime() / 1000),
}, 'SECRET');
}
}, {
createPassword: function (password) {
return new Promise(function (resolve, reject) {
bcrypt.genSalt(10, function (err, salt) {
if (err) return reject(err);
bcrypt.hash(password, salt, function (err, hash) {
if (err) return reject(err);
resolve(hash);
});
});
});
}
});
module.exports = User;
and here is config/passport.js
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var User = require('../models/Users');
passport.use(new LocalStrategy(
function(email, password, done) {
new User({email: email}).fetch().then(function(data) {
var user = data;
var curr_user = this;
if(user === null) {
return done(null, false, {message: 'Invalid username or password'});
} else {
user = data.toJSON();
if (!curr_user.validPassword(password)) {
return done(null, false, {message: 'Invalid username or password'});
} else {
return done(null, user);
}
}
});
}
));
Is this an async/promise issue? Or have I gone wrong somewhere else?
Without seeing what is
at \routes\index.js:217:36, it's very difficult to begin to attempt to answer this question.
Edit: However, at Strategy.strategy.success does suggest that it is an issue with a Promise not being returned. In other words, you are probably doing something like
ThinkingYouAreReturningAPromiseInThisFunction
.success(function(result) {
// stuff
});
but are not in fact returning a Promise object in ThinkingYouAreReturningAPromiseInThisFunction(). In other words, success() is not a function. Generally, a Promise object would have a method like success(). But if no such object is being returned, then undefined is not a function. I would begin troubleshooting there.
Could somebody please tell me why I can't seem to get past the login page here?
app.configure(function() {
this.engine('ejs', require('ejs-locals'));
this.set('views', path.join(__dirname, 'views'));
this.set('view engine', 'ejs');
this.use(express.static(path.join(__dirname, '/public')));
this.use(express.static(path.join(__dirname, '/views/login')));
this.use(express.static(path.join(__dirname, '/views/el')));
// Allow parsing cookies from request headers
this.use(express.cookieParser());
this.use(express.session({
"secret": sessionSecret,
"store": sessionStore
}));
// Allow parsing form data
this.use(express.bodyParser());
});
app.configure('development', function() {
this.use(express.errorHandler({
dumpExceptions: true,
showStack: true
}));
});
app.configure('production', function() {
this.use(express.errorHandler());
});
function authenticate(name, password, fn) {
if (!module.parent) console.log('authenticating %s:%s', name, password);
User.findOne({
username: name
},
function(err, user) {
if (user) {
if (err) return fn(new Error('cannot find user'));
hash(password, user.salt, function(err, hash) {
if (err) return fn(err);
if (hash == user.hash) return fn(null, user);
fn(new Error('invalid password'));
});
} else {
return fn(new Error('cannot find user'));
}
});
}
function requiredAuthentication(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
function userExist(req, res, next) {
User.count({
username: req.body.username
}, function(err, count) {
if (count === 0) {
next();
} else {
req.session.error = "User Exists"
res.redirect("/signup");
}
});
}
//Routes
app.get("/", function(req, res) {
if (req.session.user) {
res.send("Welcome " + req.session.user.username + "<br>" + "<a href='/logout'>logout</a>");
} else {
res.render("./login/login");
}
});
app.get("/signup", function(req, res) {
if (req.session.user) {
res.redirect("/");
} else {
res.render("./login/Signup");
}
});
app.post("/signup", userExist, function(req, res) {
var password = req.body.password;
var username = req.body.username;
hash(pass, function(err, salt, hash) {
if (err) throw err;
var user = new User({
username: username,
salt: salt,
hash: hash,
}).save(function(err, newUser) {
if (err) throw err;
authenticate(newUser.username, password, function(err, user) {
if (user) {
req.session.regenerate(function() {
req.session.user = user;
req.session.success = 'Authenticated as ' + user.username + ' click to logout. ' + ' You may now access /.';
res.redirect('/');
});
}
});
});
});
});
app.get("/login", function(req, res) {
res.render("./login/login");
});
app.post("/login", function(req, res) {
authenticate(req.body.username, req.body.password, function(err, user) {
if (user) {
req.session.regenerate(function() {
req.session.user = user;
req.session.success = 'Authenticated as ' + user.username + ' click to logout. ' + ' You may now access /restricted.';
res.redirect('/');
});
} else {
req.session.error = 'Authentication failed, please check your ' + ' username and password.';
res.redirect('/login');
}
});
});
app.get('/logout', function(req, res) {
req.session.destroy(function() {
res.redirect('/');
});
});
app.get('/profile', requiredAuthentication, function(req, res) {
res.send('Profile page of ' + req.session.user.username + '<br>' + ' click to logout');
});
When I compile my server.js file with node and go to localhost, the login page appears. When I enter the username and password that I had already inserted in my Mongo DB, I get this message:
Unable to connect
Firefox can't establish a connection to the server at localhost.
The site could be temporarily unavailable or too busy. Try again in a few moments.
If you are unable to load any pages, check your computer's network connection.
If your computer or network is protected by a firewall or proxy, make sure that Firefox is permitted to access the Web.
With my Node prompt displaying the following:
/home/ghanem/server/www/server.js:210
hash(password, user.salt, function (err, hash) {
^ TypeError: string is not a function
at /home/ghanem/server/www/server.js:210:12
at /home/ghanem/server/www/node_modules/mongoose/lib/query.js:1170:16
at /home/ghanem/server/www/node_modules/mongoose/node_modules/kareem/index.js:103:16
at process._tickCallback (node.js:415:13)