Unexpected behavior in JWT authentication using cookies - node.js

I am learning Node.js and making my first project. I am using JWT for user authentication on the project. I am using cookies to send JWT and on the server-side I am able to extract the cookie and correctly obtain the expected payload. However I am facing an unexpected behavior as in the authentication function I am always getting signed up by one particular user.
login route:
usersRouter.route('/login')
.post(passport.authenticate('local'), (req,res) => {
console.log('Generating token for: ', req.user._id);
var token = authenticate.getToken({_id: req.user._id});
res.statusCode = 200;
res.cookie('jwt', token, {signed: true});
res.redirect('/');
})
authentication code:
exports.getToken = function(user){
return jwt.sign(user, config.secretKey, {
expiresIn: 3600
});
};
var extractFromCookie = function (req)
{
var token = null;
if(req && req.signedCookies)
token = req.signedCookies.jwt;
return token;
}
var opts = {};
opts.jwtFromRequest = extractFromCookie;
opts.secretOrKey = config.secretKey;
exports.jwtPassport = passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
console.log("Jwt Payload: ", jwt_payload);
User.findOne({id: jwt_payload.sub}, (err,user) => {
if(err){
return done(err,false);
}
else if(user){
console.log('Found user is: ', user);
return done(null, user);
}
else
return done(null,false);
});
}));
exports.verifyUser = passport.authenticate('jwt', {session:false});
Printing in the console
Jwt Payload: { _id: '5f49430180e4092eac7962be', iat: 1599066593, exp: 1599070193 }
Found user is:
{
_id: 5f4904d106efe130084808a4,
.
.
. other details
.
}
How is the search resulting in a user with different id? I am not able to figure out why is this happening. Please help!
Edit: getToken is a function to generate token from the id of the user sent from login router.

It was just some small mistakes after all.
The only thing that was needed to be changed is as follows:
User.findOne({id: jwt_payload.sub}, (err,user) =>
As #Michael pointed out, there's no field name sub in jwt_payload, we need to put _id instead. Also, in the User schema, it's _id field and not id field.
Made these changes, and viola!

You accidentally access to undefined property sub in jwt_payload so you actually send undefined as value to the findOne function.
This should work.
User.findById(jwt_payload._id, (err,user) => {

Related

Get username object without requesting it after authentication? (Node & express.js)

I'm making a Node backend using express (also using bcrypt for password hashing and jsonwebtoken for authorization). My requests are in separate files and they all use module.exports. I have this login request that is in a file called login.js:
router.post("/account/login", async (req, res) => {
// User object
const user = {
username: req.body.username,
password: req.body.password
};
// Get user name only
const username = {
username: req.body.username
};
// Create a new JWT token
const token = jwt.sign(username, secret);
// Get username as a string
const usernameString = JSON.stringify(user.username);
// Get hashed password from the collection
const hashedPassword = await logincollection.findOne({ username: req.body.username });
// Search for matching login credentials
await logincollection.findOne(username, (err, result) => {
// If username was not found, return code 400
if (result == null) {
res.status(400).send();
}
// Proceed if username was found
else {
// If no token was given, return code 401
if (!token) {
res.status(401).send();
}
// Verify the given JWT token
jwt.verify(token, secret, (err, decoded) => {
// If verification failed, return code 500
if (err) {
res.status(500).send();
}
})
// Use bcrypt to compare the passwords and authenticate login
bcrypt.compare(req.body.password, hashedPassword.password).then(match => {
// If the credentials match
if (match) {
// Insert the token into a cookie
res.cookie("token", token, { httpOnly: true });
// Return code 200
res.status(200).send({ auth: true, token: token });
// Log to console when user logs in
console.log("User " + usernameString + " logged in");
}
// If the credentials do not match
else {
// Return code 404
res.status(400).send();
}
// If comparing fails
}).catch(error => {
// Return code 500
res.status(500).send();
})
}
})
})
Every user has their own collection in a MongoDB database that is created, when the account is created. These collections are named after the username, since usernames are unique here. In this load.js file I have a request that loads all the objects in the collection:
router.post("/note/load", async (req, res) => {
// User object
const username = {
username: req.body.username
};
// Get usercollection depending on username
const usercollection = userdb.collection(JSON.stringify(username));
// Load the notes
const loadNote = await usercollection.find().toArray();
// Return code 200 and sendObject
res.status(200).send(loadNote);
})
Obviously, the code above will result in an error, because the username is not given in the request. Is there a way to pass the username to any request, when needed? Or can I use JWT for this, and if so, how? I have tried storing usernames in a variable, but that doesn't work, since the variable gets overwritten, when two users are logged in at the same time.
PS. The title is poor, because I don't know how to put this shortly. If you have any suggestions or corrections regarding the question or the title, please comment below.

Getting a 401 status when trying to reach protected route using passport-jwt

I am using JWT working with passport.js for authentication. I have a local strategy that signs up and another that logs in a user. Currently, I have it return the token by doing:
req.login(user, {session: false}, (err) => {
if (err) {
res.send(err);
}
var jwtUser = {
_id: user.id,
email: user.email
};
const token = jwt.sign(jwtUser, config.passport.jwtSecret);
return res.json({jwtUser, token});
});
This returns an object like:
{"jwtUser":
{
"_id":"5c55f0be9ddcf71a704d92b2",
"email":"john.doe#example.com"
},
"token":"<token>" // redacted this because it contains my personal email
}
So I am getting a token back that is correct (I've checked using an online decoder, it gives the correct user ID and the correct email).
When I use this route to try and test whether the JWT strategy is working or not I get a 401 unauthorized access message.
Here is my JWT strategy
var opts = {};
opts.jwtFromRequest = ExtractJWT.fromAuthHeaderAsBearerToken();
opts.secretOrKey = config.passport.jwtSecret;
passport.use('jwt', new JWTstrategy(opts, function(jwt_payload, done) {
console.log('payload received: ', jwt_payload);
User.findOne({id: jwt_payload.id}, function(err, user) {
console.log('User: ', user);
if (user) {
console.log(user);
return done(null, user);
}
if (err) {
return done(err, false);
}
});
}));
My route
router.get('/auth/test', passport.authenticate('jwt', { session: false }), (req, res) => {
res.send('You reached a private route.');
});
I have my local strategy working using the same method of calling passport.
I think I have included everything of value, if something is missing please say and I will update the OP with it.
I should note I am using postman to test this and using the authorization method of bearer token.
Thanks in advance!

Unauthorized after login with passport-jwt

I used passport-jwt for login. it find the user and the user can login but when after logging in, I want to redirect to the user dashboard nothing happens and it shows Unauthorized the in browser when it redirect to localhost:8080/my/
can you find out what the problem is:
p.s: when I remove passport.authenticate('jwt', {session: false}) from my.js it works and it redirects to /my/. so I think the problem is with this part of code or JWTStrategy in auth.js. I don't know how to fix it!
here's my code : (sorry it's to much code)
auth.js:
//Create a passport middleware to handle User login
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async (username, password, done) => {
try {
//Find the user associated with the email provided by the user
await User.findOne({
username: username
}, async function (err, user) {
if (!user) {
//If the user isn't found in the database, return a message
return done(null, false, {
message: 'User not found'
});
}
//Validate password and make sure it matches with the corresponding hash stored in the database
//If the passwords match, it returns a value of true.
let validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {
message: 'Wrong Password'
});
}
//Send the user information to the next middleware
return done(null, user, {
message: 'Logged in Successfully'
});
});
} catch (error) {
return done(error);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest : ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'secret_token'
}, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
});
}));
authenticate.js :
router.post('/login', async (req, res, next) => {
passport.authenticate('local', async (err, user, info) => {
try {
if (err || !user) {
const error = new Error('An Error occured')
return next(error);
}
req.login(user, {
session: false
}, async (error) => {
if (error) return next(error)
//We don't want to store the sensitive information such as the
//user password in the token so we pick only the email and id
const body = {
_id: user._id,
username: user.username
};
//Sign the JWT token and populate the payload with the user email and id
const token = jwt.sign({
user: body
}, 'top_secret');
//Send back the token to the user
// return res.json({
// token
// });
res.redirect('/my/?token='+token);
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
module.exports = router;
user.js
const mongoose = require('mongoose');
const Role = require('./role');
const bcrypt = require('bcrypt');
let Schema = mongoose.Schema;
let userSchema = new Schema({
.
.
.
});
userSchema.pre('save', async function(next){
//'this' refers to the current document about to be saved
const user = this;
//Hash the password with a salt round of 10, the higher the rounds the more secure, but the slower
//your application becomes.
const hash = await bcrypt.hash(this.password, 10);
//Replace the plain text password with the hash and then store it
this.password = hash;
//Indicates we're done and moves on to the next middleware
next();
});
userSchema.methods.isValidPassword = async function(password){
const user = this;
//Hashes the password sent by the user for login and checks if the hashed password stored in the
//database matches the one sent. Returns true if it does else false.
let compare= await bcrypt.compare(password , user.password);
return compare;
}
module.exports = mongoose.model('User' , userSchema);
my.js
//secure routes that only users with verified tokens can access.
//Lets say the route below is very sensitive and we want only authorized users to have access
//Displays information tailored according to the logged in user
router.get('/',passport.authenticate('jwt', {session: false}), (req, res) => {
//We'll just send back the user details and the token
res.json({
message : 'You made it to the secure route',
token : req.query.token
});
})
module.exports = router;
This problem usually occurs because of an invalid JWT. To authenticate a JWT with passport-jwt it has to be of the following format:
"JWT xxxx.xxxx.xxxx"
So maybe try changing the following line in your Authenticate.js file:
res.redirect('/my/?token='+token);
// CHANGE IT TO:
res.redirect('/my/?token=' + 'JWT ' + token);
I hope this solves your problem.

Node.js: Send new request with token(JWT) in URL after logging in

After the user has logged in and generated a token, I want to send it automatically in the header or something similar.
So far I managed to generate the token, and check if it exists and if it's valid, it seems to work fine as long as I copy paste it the url as "?token = generated token".
I wasn't able to understand how to send it without writing it myself in the URL of Postman.
I'm using these modules:
Express
Body-parser
Mongoose
JsonWebToken
So I'm curious if it's ok that I choose to generate the token only at the login of if I need to add it in the user's Schema.
I don't want to use Passport for now because I want to understand the basics first.
After searching for a while (the jwt documentation included) I didn't really managed to find something that I can understand and implement.
So here I am, if someone could guide me in the right direction, that'd be great.
Sorry for the bad indentation and thanks in advance.
Here is some code:
jwt-middleware.js
var jwt = require('jsonwebtoken');
var secret = 'mySecret';
module.exports = function (req, res, next) {
var token = req.body.token || req.headers['x-access-token'] || req.query.token;
if(!token) {
return res.status(404).send({error:'No token found'});
} else {
jwt.verify(token, secret, function(err, decode) {
if(err) {
return res.status(500).send({error:'Invalid token'});
} else {
// req.decode = decode;
decode = jwt.decode(token, {complete:true});
//console.log(req.headers);
// req.headers.authorization = token;
// console.log(req.headers.authorization);
console.log(decode.header);
console.log(decode.payload);
next();
}
});
}
}
routes/user.js
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var jwt = require('jsonwebtoken');
var expressJwt = require('express-jwt');
var verifyToken = require('../config/jwt-middleware');
var secret = 'mySecret';
//Import models
var User = require('../models/users');
router.get('/', verifyToken, function (req, res) {
User.find({}, function (err, storedUsers) {
if (err) {
return res.status(500).send({ error: 'No users found' });
} else {
return res.status(200).send(storedUsers);
}
});
});
router.post('/login', function (req, res) {
User.find().lean().exec(function (err, doc) {
for (var i = 0; i < doc.length; i++) {
if (req.body.username == doc[i].username && req.body.password == doc[i].password) {
var token = jwt.sign({username:req.body.username}, secret, {expiresIn:'1h'});
return res.status(200).send('You\'re now logged in ' + 'with the username: ' + doc[i].username + ' and password: ' + doc[i].password + ' \nJSON token: \n' + token);
}
}
return res.status(404).send({ error: 'Invalid username or password: ' + req.body.username });
});
});
Some screenshots:
No token
Login
Manually inserted token
OK, so I'll try and answer your question even though I'm not 100% sure I understand what you're asking. The basic flow of a JWT is that the user logs in, and you issue it. You don't store it because the whole point of a JWT is that there's no overhead on the server for storing it (allowing for a more distributed approach to user management). The exception is if you want to do a logout feature, but that doesn't look like it's one of your requirements.
From the standpoint of responsibilities, you should have a Login function or module which is responsible for verifying a user's credentials and issuing a token. You should have a Verification function or module that validates the token and places the decoded token on the request object for later use (no need to repeatedly decode). And you may (or may not) have an Authorization module that validates that a given user is allowed to perform a given task.
So, from the top. Note that you can let the DB do the query work rather than doing your own loop. I'm also assuming that your User schema will include a verifyPassword method that takes care of comparing salted and hashed passwords.
// login
router.post('/login', function (req, res, next) {
// note I didn't use lean() because I want access to User methods. Performance is less of an issue in my version, b/c the DB is only retrieving one user at most.
User.findOne({ username: req.body.username }).exec(function (err, user) {
if(err) return next(err);
if(!user) return res.status(401).send();
if (user.verifyPassword(req.body.password)) {
// maybe add more info about the user, like display name
var token = jwt.sign({username:user.username}, secret, {expiresIn:'1h'});
return res.status(200).send({message: 'You are now signed in', token: token});
}
}
return res.status(404).send({ error: 'Invalid username or password: ' + req.body.username });
});
});
Now the client will have access to the token more easily, and can send it on further requests.
// verify
module.exports = function (req, res, next) {
// this is fine, though I think the standard is that the token should be sent in the Authorization header with the format Bearer {token}
var token = req.body.token || req.headers['x-access-token'] || req.query.token;
if(!token) {
return next(); // this middleware just is responsible for decoding, other middleware checks authorization
} else {
jwt.verify(token, secret, function(err, decode) {
if(err) {
return next(); // again, a bad token doesn't necessarily mean anything for some application pages, put the logic elsewhere.
} else {
req.user = decode; // this will be { username } based on above
req.token = token; // generally you don't need it but just in case.
next();
}
});
}
}
Ok, so now further middleware will include a req.user that you can use to check if a given user should be allowed to see a resource or not. For example:
function userRequired(req, res, next) {
if (!req.user) return res.status(401).send({message: 'You must be logged in to view this page' });
return next();
}
This scales well to other checks, you could have one for various roles, etc.

Node.js JWT, get user_id from token

strong textI am building node.js + mongodb rest api. I use jwt user auth and I have a problem. I need to get details of authenticated user (user_id, name), think they can be obtained from token, but I dont know how to do this. How is it possible to do ?
UPDATED
I am doing a post request
router.route('/articles')
.post(function (req, res) {
var article= new Article();
article.user_id = ???; // here needs user_id
article.imdb_id = req.body.imdb_id;
article.title = req.body.title;
article.thumb = req.body.thumb;
article.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Added' });
});
});
I need to insert into articles collection authors id (user_id), but I dont know how to get the authenticated user_id.
Tried to do this:
var token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
req.decoded = decoded;
console.log(decoded);
next();
}
});
decoded returns all info about user (name, password, _id). Is it possible to get only user_id and name from here?
When you sign a JSON web token you can pass it a user object. Here you can store whatever user data you need. This object is then signed and encoded and set as the token. When you send a request to your API passing the JWT in the auth header your validation function should return this user object back to you if the JWT is valid.
I like to use the Hapi framework for creating my Restful APIs so I will give an example using Hapi.
In your server.js file you need to register the hapi-auth-jwt2 package:
server.register(require('hapi-auth-jwt2'), (err) => {
if (err) {
throw err;
}
server.auth.strategy('jwt', 'jwt', {
key: config.jwt.secret,
validateFunc: auth.validate,
verifyOptions: { algorithms: ['HS256'] }
});
server.auth.default('jwt');
});
Your validation function:
export default {
validate: (tokenObject, req, callback) => {
validateToken(tokenObject.user_id, (err, user) => {
if (err) {
callback(Boom.unauthorized('User is unauthorized.'), false);
} else {
req.user = user;
callback(null, true);
}
});
}
};
The validateToken function should take the user id that you got from the token and query for the user. If a user is found then you know the token is valid and you can return and store the rest of the user information.
To create a token I use "jsonwebtoken" package:
generateToken: (user_id, name, callback) => {
'use strict';
callback(null, JWT.sign({
user_id: user_id,
name: name
}, config.JWT.SECRET, {
expiresIn: 86400
}));
}
Let's say you need to verify if the token sent from user In the headers already In your Database or not (we're going to call it protect)
const {promisify} = require('util');
const jwt = require('jsonwebtoken');
const User = require('./../models/userModel');
...
exports.protect = catchAsync(async(req, res, next) => {
// 1) Getting token and check if it's there in headers
let token;
//authorization is the name of the header token
if (req.headers.authorization) {
token = req.headers.authorization;
}
if (!token) {
return next(new AppError('You are not logged in! Please Login To get Access.', 401));
}
// 2) Verification Token is a valid token
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
// WE CAN GET THE USER ID FROM DECODED
// 3) Check if user still exists not deleted
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next(new AppError('the user does not exist.', 401));
}else{
// WHAT EVER YOU WANT TO DO AFTER CHECKING USER FOUND IN DATABASE
})

Resources