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
})
Related
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.
Have a problem when trying to verify the token (it was working fine before i added some data to it before generating it) .. but now it does not seem to be working !
This is how i generate the token when user send a POST request (login)
require('dotenv')
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs')
const Role = require('../models/Role');
const Section = require('../models/Section');
const User = require('../models/User');
// Login !
router.post('/', async (req, res) => {
let sections_fetched = [];
// Validate data
// Check username
const user = await User.findOne({username: req.body.username });
if(!user) return res.status(400).send('Wrong user login credentials !');
// Check password
const is_pass_valid = await bcrypt.compare(req.body.password , user.password);
if (!is_pass_valid) return res.status(400).send('Wrong user login credentials !');
// Get role Object:
const _role = await Role.findOne({_id:user.role , is_deleted:false});
if (!_role) res.json("Failed fetching role !");
// loop through sections
for (let index = 0; index < _role.sections.length; index++) {
const tmpRole = await Section.find({_id: _role.sections[index], is_deleted:false});
sections_fetched.push({access:tmpRole[0].access , name:tmpRole[0].name});
}
// create jwt token
const token = jwt.sign({username:user.username, role:{name:_role.name, sections:sections_fetched}}, 'secret', {expiresIn : '24h'}, process.env.JWT_TOKEN_SECRET);
res.json({token:token});
});
this is my JWT verification middleare :
require('dotenv')
const jwt = require('jsonwebtoken');
module.exports = function (req, res, next) {
const token = req.header('auth-token');
if (!token) return res.status(401).send('Access Denied !');
console.log(process.env.JWT_TOKEN_SECRET);
console.log(token);
try
{
const verified = jwt.verify(token, process.env.JWT_TOKEN_SECRET);
req.user = verified;
next();
}
catch (error)
{
res.status(400).send('Invalid token !');
}
}
and this is a simple example of listing users (using the JWT verification middleware ! ) :
const verifyToken = require('../middlewares/verifyToken'); // my jwt middleware to verify !
// Listing All users
router.get('/', verifyToken, async (req, res) =>
{
try
{
const users = await User.find({is_deleted:false});
res.json(users);
}
catch (error)
{
console.log("err ->\n"+error);
res.json({message: error});
}
});
what is 'secret in the below line? seems you are adding a secret key twice, replace the hardcoded word 'secret' with token from env
const token = jwt.sign({username:user.username, role:{name:_role.name, sections:sections_fetched}}, 'secret', {expiresIn : '24h'}, process.env.JWT_TOKEN_SECRET);
send a bearer token and your middleware should like this
require('dotenv')
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1]; // Authorization: 'Bearer TOKEN'
if (!token) {
throw new Error('Authentication failed!');
}
const verified = jwt.verify(token, process.env.JWT_TOKEN_SECRET);
req.user = verified;
next();
} catch (err) {
res.status(400).send('Invalid token !');
}
};
Please make sure then when generating the token you pass a valid algorithm. I stored the algorithm in an environment variable but used none which is not a valid algorithm. Thus even though the token got created, I couldn't verify it using the same secret. Spent hours trying to fix this. I hope this was helpful :D
sign(payload, JWT_SECRET, {
algorithm: JWT_ALGORITHM,
expiresIn: '1d'
});
You described that you have changed the contents.
A signature represents the integrity of the contents, to ensure the content that was signed has not been modified, and you are modifying the contents making the signature invalid. This error you have is accurate for what you have done.
You must create a new signature when the signed contents are changed.
For Sign:
jwt.sign(
{
username:user.username,
role:{
name: _role.name,
sections: sections_fetched
}
}, 'secret', {
expiresIn : '24h'
}, process.env.JWT_TOKEN_SECRET,
function(err,res) {
if (err) {
callback(res);
} else {
callback(res);
}
}
);
To Verify:
jwt.verify(req,'SecretKey', function (err, res) {
if (err) {
callback(res);
} else {
callback(res);
}
});
Hlo
this error occur when secretkey does't match with the key thats provided during when you verifing token
you can check you token on jwt website thats help you to get error main reason
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) => {
Spent a lot of time banging on this JWT error and finally discovered what was causing it, but I don't understand why.
My user.js files (model and routes) generate a token when a user logs in.
router.post('/login', async (req, res) => {
try {
console.log(req.body.email);
console.log(req.body.password);
const { email, password } = req.body;
const user = await User.findByCredentials(email, password)
if (!user) {
return res.status(401).send({error: 'Login failed! Check authentication credentials'})
}
const token = await user.generateAuthToken()
res.send({ user, token })
} catch (error) {
console.log(error)
res.status(400).send(error)
}
})
userSchema.methods.generateAuthToken = async function() {
// Generate an auth token for the user
const user = this
const token = jwt.sign({
email: user.email,
_id: user._id
},
'badabaloozagoodoo',
{
expiresIn:'1h'
}
)
console.log('Generating token')
console.log(token)
user.tokens = user.tokens.concat({token})
await user.save()
return token
}
It outputs a token like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpzbWl0aEBnbWFpbC5jb20iLCJfaWQiOiI1ZGIyNGRmYzc5NzUyZTYxOGI3OTk1NDYiLCJpYXQiOjE1NzIwMzkxOTcsImV4cCI6MTU3MjA0Mjc5N30.ctdg8vkne1gvD3-Lo6j-T5BQEMVKBoKBsDGddtuQBUE
Then, on a subsequent call for a different route, the following middleware checks to token to make sure the user is authorized. It kept throwing a JsonWebTokenError: invalid token error and I couldn't understand why. Then I printed the token from the request header to string and noticed that it had a JWT prefix for some reason.
JWTeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpzbWl0aEBnbWFpbC5jb20iLCJfaWQiOiI1ZGIyNGRmYzc5NzUyZTYxOGI3OTk1NDYiLCJpYXQiOjE1NzIwMzkxOTcsImV4cCI6MTU3MjA0Mjc5N30.ctdg8vkne1gvD3-Lo6j-T5BQEMVKBoKBsDGddtuQBUE
So, I added coded to remove the JWT prefix and now the code runs without any error. Could someone help me understand what is going on?
const jwt = require('jsonwebtoken')
const User = require('../../models/user')
const checkAuth = async(req, res, next) => {
console.log("Check auth")
try {
console.log(req.header('Authorization'))
var token = req.header('Authorization').replace('Bearer ', '')
token = token.replace('JWT', '')
console.log(token)
const data = jwt.verify(token, 'badabaloozagoodoo')
const user = await User.findOne({ _id: data._id, 'tokens.token': token })
if (!user) {
throw new Error('User not found')
}
req.user = user
req.token = token
next()
} catch (error) {
console.log('Auth failed')
console.log(error)
res.status(401).send({ error: 'Not authorized to access this resource.' })
}
}
module.exports = checkAuth
Well. I found my error. I had set up an interceptor to verify the token and it was adding the 'JWT' prefix
req = req.clone({headers:req.headers.set('Authorization', 'JWT'+token)}); //passing request
return next.handle(req)
I am new in backend Node.js and till now I am able to complete registration and login with authentication.
When login I am getting token in response by using jwt token
Now I want to have the registration details to be shown to users after login. After login the details must of be of particular user's only whos is logging in.
And if admin is logging in, then he will get the entire database user's fields.
This is my index.route:-
const express = require ('express');
const router = express.Router();
const mongoose = require ('mongoose');
const User = mongoose.model('User');
const ctrlUser = require ('../controllers/user.controller.js');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const passport = require('passport');
// routing functions
//registartion user signup
router.post('/register' , ctrlUser.register);
//login user
router.post('/login' , (req, res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length < 1) {
return res.status(401).json({
message: "Auth failed. User not found."
})
}
bcrypt.compare(req.body.password, user[0].password, (err, result) =>{
if (err) {
return res.status(401).json({
message: "Auth failed. Check email and password"
});
}
if (result){
const adminEmail = "rohit#metapercept.com";
const role = user[0].email===adminEmail? "admin" : "user"; //check user id as admin or user
const token = jwt.sign(
{
email: user[0].email,
userId: user[0]._id,
role
},
process.env.JWT_KEY,
{
expiresIn : "1h"
});
return res.status(200).json({
message: "Auth Successful",
token : token
});
res.redirect('/profile');
}
});
})
.catch(err =>{
if (err.code == 500)
res.status(500).send(["Something went wrong in login"]);
else
return next(err);
});
});
router.get('/profile', function(req, res, next){
//something todo here ...
});
//delete user
router.delete('/:userId' , (req, res, next) =>{
User.deleteMany({_id: req.params.userId})
.exec()
.then(result => {
res.status(200).send(["Deleted"]);
})
.catch(err =>{
if (err.code == 500)
res.status(500).send(["Didn't get deleted"]);
else
return next(err);
});
});
module.exports = router;
How can I access user's details in profile url API?
Get JWT from request header then decode
jwt.verify(token, getKey, options, function(err, decoded) {
console.log(decoded.email)
});
jwt.verify - jwt doc
Create new middleware ( above other routes)
// route middleware to verify a token
router.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) { if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' }); } else {
// if everything is good, save to request for use in other routes
req.decoded = decoded; next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
Help : jwt - decode and save in req
In the code, After return your redirect never work. so There're 2 options:
You don't need to return a token to client, just use res.redirect('/profile') after your verification. (in this way, your server and client are in one)
You just return the token to client (Don't use res.redirect('/profile') anymore) then client will use that token to redirect to the profile. (in this way, your server and client are separate each other).