I am trying to make get request to my protected routes using bearer token and it returns unautorized, even after sending token through header.
I am using bearer token on nodejs, expressjs app using mlab remote database
I registered new user, then I logged with that email and it sent me back a token(as expected).
When I sent this token through header of other route without login it returns Unautorized.
my steps are
1) registered with new email
2) login request successful
3) failed get request to route localhost:5000/api/users/current, and returns Unautorized.
user.js file has
// users.js for authentication and authorization
const express = require("express");
const router = express.Router();
const gravatar = require("gravatar");
const bcrypt = require("bcryptjs");
const keys = require("../../config/keys");
const jwt = require("jsonwebtoken");
const passport = require("passport");
// Load User Model to check existing email is used for registration or not?
const User = require("../../models/User");
// #route GET request to api/users/test
// #description Tests users route
// #access Public, without login
router.get("/test", (req, res) => res.json({ msg: "Users Works" }));
// #route GET request to api/users/register
// #description new registration of user.
// #access Public, without login first register
router.post("/register", (req, res) => {
User.findOne({ email: req.body.email }).then(user => {
if (user) {
return res.status(400).json({ email: "Email value exists already." });
} else {
console.log("no user found of this email in DB");
const avatar = gravatar.url(req.body.email, {
s: "200", //Size of gravatar in pixels
r: "pg", //rating,
d: "mm" //default value= 'mm'
});
// create user
const newUser = new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
});
// gensalt(noOfSalts_of_Iterations,(err,salt_result)=>{})
bcrypt.genSalt(10, (err, salt) => {
// hash(plaintext,salt,callback(err,resultant ciphertext))
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) {
console.log("error in bcrypt.hash()");
throw err;
}
//assign salted hash to password
newUser.password = hash;
// Save new password in datebase, overriding plaintext;
newUser
.save()
.then(user => res.json(user)) // if yes,then send it as argument in brackets.
.catch(err =>
console.log("Error occured in saving hash password in DB\n")
);
});
});
}
});
});
// #route GET request to api/users/login
// #description Login/signing-in registered user. return JWT token
// #access Public
router.post("/login", (req, res) => {
const email = req.body.email;
const password = req.body.password;
// find user to match it's password
User.findOne({ email: req.body.email }).then(user => {
//check if no user
if (!user) {
return res.status(404).json({ email: "User's email found." });
}
// else if do this..
// if user's email-id is found then match it's password-hash with local-database
bcrypt.compare(password, user.password).then(isMatch => {
if (isMatch) {
// user pswd matched => then return JWT token back for authentication
// res.json({ msg: "Success" });
const payload = { it: user.id, name: user.name, avatar: user.avatar };
// created JWT token
// now sign token
// jwt.sign(payload, secretKey, expire-time, callback );
// jwt.sign
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "bearer " + token
});
}
);
} else {
// pswd doesn't matched
return res.status(400).json({ password: "Password didn't match" });
}
});
});
});
// #route GET request to api/users/current - current user with token
// #description Return current user
// #access Private, can't go without login
router.get(
"/current",
passport.authenticate("jwt", { session: false }),
(req, res) => {
res.json({ msg: "Success" });
}
);
module.exports = router;
data is stored succesfully at remote db mlab, but I can't figureout what's problem.
my Github repo of this project is this
Please Look at the Line of your code Users.js Line 88 the payload id is stored in 'it'
const payload = { it: user.id, name: user.name, avatar: user.avatar };
But in your passport.js
User.findById(jwt_payload.id)
you are extracting it by id so if you use here jwt_payload.it the you will get message success
Related
I am quite new to Node.js / Express and development of web apps. I try to do a simple user registration where I hash the password with bcrypt before saving the hash to mongodb. The login form, which should allow a user to login, does subsequently lookup a user in the db and then compares the two passwords.
Certain routes in my web app I do want to protect so that only authenticated user have access to them. So when successfully login in I do send a Json Web Token (jwt) along the response header which should then be used - when redirected to the protected '/lobby' route - to authenticate the user and allow him / her to proceed to that route.
However, I always get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
So it looks like it already sends back a response to the client before trying to set the header which of course is then not possible anymore.
I would highly appreciate your help here!
I do use the following code:
Register function
async function register(req, res) {
//Check with user already exists
const emailExists = await User.findOne({email: req.body.email});
if(emailExists) {
return res.status(400).send('User already exists!');
};
//Hash the password and create new user from request data
bcrypt.hash(req.body.password, 10, async function (err, hashedPass){
if(err){
res.json({
error: err
});
}
let user = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: hashedPass,
password2: hashedPass
});
try {
await user.save();
}catch (err) {
res.status(400).send(err);
};
});
res.render('index');
};
Login function
async function login(req, res) {
const user = await User.findOne({email: req.body.email});
if(!user) {
return res.status(400).json({message: 'User not found!'}).render('index');
};
bcrypt.compare(req.body.password, user.password).then((result)=> {
if(result){
const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET);
res.setHeader('auth-token', token.toString());
res.redirect('/lobby');
}else {
return res.status(400).json({message: 'Passwords do not match!'}).render('index');
}
}).catch((err)=> {
console.log(err);
});
};
As a middleware to the '/lobby' route (i.e. when someone does a get request to '/lobby') I use a "verifyToken" function which should ensure correct authentication of the user via jwt.
verifyToken function
const jwt = require('jsonwebtoken');
module.exports = function(req, res, next) {
console.log('verify function started');
const token = req.header('auth-token');
console.log(token);
if(!token) {
res.status(401).json({
message: 'Access denied!'
});
};
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
}catch (err) {
res.status(400).json({
message: 'Invalid token!'
});
};
};
As said, I would very much appreciate your help here! I assume the problem is much simpler than I think it is :-).
Cheers
You forgot to return the response in few cases. So it continues to execute other code aswell, that's where server trying to send the response again, which is why you're getting that error.
Change your response like the following.
verifyToken function
const jwt = require('jsonwebtoken');
module.exports = function(req, res, next) {
console.log('verify function started');
const token = req.header('auth-token');
console.log(token);
if(!token) {
return res.status(401).json({ // <-- here you need to `return`
message: 'Access denied!'
});
};
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
}catch (err) {
return res.status(400).json({
message: 'Invalid token!'
});
};
};
Register function
async function register(req, res) {
//Check with user already exists
const emailExists = await User.findOne({email: req.body.email});
if(emailExists) {
return res.status(400).send('User already exists!');
};
//Hash the password and create new user from request data
bcrypt.hash(req.body.password, 10, async function (err, hashedPass){
if(err) {
return res.json({ // <-- here as well
error: err
});
}
let user = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: hashedPass,
password2: hashedPass
});
try {
await user.save();
return res.render('index'); // <-- assuming this is your success response
}catch (err) {
return res.status(400).send(err); <-- here too
};
});
};
Looks like in the Login function the header gets set. I can see this via console.log(res.header('auth-token'));. Subsequently the redirect to "/lobby" gets called because the verifyToken function does start.
However, in the verifyToken function the respective header is then undefined. Because I always also get a 'Access denied!' message.
As said, I do call the verifyToken function as middleware when doing a get request to the /lobby route. The route for '/lobby' looks as follows:
const express = require('express');
const router = express.Router();
const lobbyCtrl = require('../controllers/lobby');
const verify = require('./verifyToken');
router.get('/', verify, lobbyCtrl.display);
module.exports = router;
I have this code for login. How do I use the current user's information from this code to another file using postman and node.js?
exports.loginUser = (req,res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length < 1) {
return res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err ,result) => {
if(err){
return res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
const token = jwt.sign({
email: user[0].email,
userId: user[0]._id
},
process.env.JWT_KEY ,
{
//options
expiresIn: "1h"
});
You should tell exactly what you want, what you said is confusing, but If you mean how to pass the logged in user to the next middleware, you gotto assign the user to req
exports.loginUser = async (req, res, next) => {
const user = await User.find({ email: req.body.email }).exec()
if (user.length < 1) {
return res.status(401).json({
message: 'Auth failed'
});
}
bcrypt.compare(req.body.password, user[0].password, (err, result) => {
if (err) {
return res.status(401).json({
message: 'Auth failed'
});
}
if (result) {
const token = jwt.sign({
email: user[0].email,
userId: user[0]._id
},
process.env.JWT_KEY, {
//options
expiresIn: "1h"
});
req.user = user[0];
return next();
}
})
}
Then in the next middleware you have access to logged in user, using req.user.
UPDATE:
To implement the functionality that you want, according to what you described in the comment:
Before anything import these packages:
const jwt = require("jsonwebtoken");
const { promisify } = require("util");
First you implement a route that checks for credentials and sends back a signed jwt:
exports.login = CatchAsync(async(req, res, next) => {
const { email, password } = req.body;
if (!email || !password) {
return next(new Error("Please provide email and password"));
}
const user = await UserModel.findOne({email});
if (!user) {
return next(new Error("There is no user with that email"));
}
if(!(await bcrypt.compare(password, user.password))) {
// actually the pass is not correct but for security reasons we don't say that
return next(new Error("Email or password is not correct");
}
// pass the user id to jwt so later can identify user
const token = jwt.sign({ id: user._id }, 'yourJwtSecret', {
expiresIn: '90d',
});
// httpOnly prevents access to token in client's browser, so it is safe
const cookieOptions = {
expires: new Date(
Date.now() + 90 * 24 * 60 * 60 * 1000
),
httpOnly: true,
};
res.cookie("jwt", token, cookieOptions);
res.status(200).json({
status: 'success',
message: 'logged in successfully'
});
});
Then for every route that needs to check for logged In user, use this middleware:
exports.isLoggedIn = CatchAsync(async(req, res, next) => {
// Check if there is a token
// if no token was provided it means user is not logged in
let token;
if (req.cookies.jwt) {
token = req.cookies.jwt;
} else {
return next();
}
// Verify token
// decoded now has access to id of user
let decoded;
try {
decoded = await promisify(jwt.verify)(token, 'yourJwtSecret');
} catch (err) {
// if token was modified or expired or not valid
return next();
}
// get the user
const user = await UserModel.findOne({
_id: decoded.id
});
// access granted, user is logged in
req.user = user; // you can access the logged in user in the next middleware
res.locals.user = user; // you can access the logged in user in template engines
next();
});
If the user is not logged in, req.user won't be assigned. therefore in next middlewares if req.user was undefined you know user is not logged in.
for more info jwt docs.
If you have never taken any NodeJs course, I'd recommend this course
I'm creating social network app with MERN. I have implemented followers and following, and now i'm trying to get the list of posts of only users that i'm following. So, if I console.log my req.user I only get the 'id' and 'iat', but i need more information of a user such as following array.
here is what i have:
auth.js middlware:
const config = require('config')
const jwt = require('jsonwebtoken')
function auth(req, res, next) {
const token = req.header('x-auth-token')
// check for token
if (!token) return res.status(401).json({ msg: 'Unauthorized token' })
try {
// verify token
const decoded = jwt.verify(token, config.get('jwtSecret'))
// add user from payload
req.user = decoded
console.log('---',decoded)
next()
} catch (e) {
return res.status(400).json({ msg: 'Token is not valid' })
}
}
module.exports = auth
auth.js
const express = require('express')
const router = express.Router()
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const config = require('config')
const User = require('../models/User')
const auth = require('../middleware/auth')
// #route POST /api/auth
// #desc Login user
// #access Public
router.post('/', (req, res) => {
const { email, password } = req.body
// filed validation
if (!email || !password) res.status(400).json({ msg: 'Fields cannot be empty.' })
// Check if user is registered
User
.findOne({ email })
.then(user => {
if (!user) res.status(400).json({ msg: `User doesn't exist.` })
//console.log(user)
// validate password
bcrypt
.compare(password, user.password)
.then(isMatch => {
if (!isMatch) return res.status(400).json({ msg: 'Invalid password' })
// if password matches, send token and user
jwt.sign(
{ id: user.id },
config.get('jwtSecret'),
(err, token) => {
if (err) throw err
res.json({
token,
user: {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
registration_date: user.registration_date,
profile_image: user.profile_image,
user_bio: user. user_bio,
followers: user.followers,
following: user.following
}
})
}
)
})
})
})
and this is my console log
Server started at port 5000
[0] --- { id: '5efccfb13224e1489439bcfd', iat: 1593626545 }
[0] * { id: '5efccfb13224e1489439bcfd', iat: 1593626545 }
EDIT:
// #route GET /api/posts/subscribedPost
// #desc get all subscribed post
// #access Private
router.get('/subscribedPost', auth, (req, res) => {
console.log('*',req.user) // not working correctly, have only id and iat
Post
.find({userID: req.user.id})
.populate('userID', 'first_name last_name profile_image _id')
.sort({ registration_date: -1 })
.then(post => res.json(post))
.catch(err => res.json(err))
})
This is because you are signing your JWT token with id only, so when u decode it you will only get id
jwt.sign({ id: user.id },config.get('jwtSecret'),(err, token) =>{})
Here you are just passing id, you can pass your whole user object to get all the data
jwt.sign(user.toJSON(),config.get('jwtSecret'),(err, token) =>{})
PS- YOU MIGHT NOT WANT TO PASS YOUR WHOLE USER, JUST DID IT FOR DEMO
You can customize to what values you want.
You need to get user data DB in auth middleware and then add it to req.user. So if there are any changes to the user you get updated user data.
eg:
function auth(req, res, next) {
const token = req.header('x-auth-token')
// check for token
if (!token) return res.status(401).json({ msg: 'Unauthorized token' })
try {
// verify token
const decoded = jwt.verify(token, config.get('jwtSecret'))
// get the user from DB
const user = User.findById(decoded.id) //only populate fields which are required
req.user = user
console.log('---',decoded)
next()
} catch (e) {
return res.status(400).json({ msg: 'Token is not valid' })
}
}
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.
Here is my code--------
const express = require("express");
const router = express.Router();
const gravatar = require("gravatar");
// Load User model
const User = require("../../models/User");
// #route GET api/users/test
// #desc Tests users route
// #access Public
router.get("/test", (req, res) => res.json({ msg: "Users Works" }));
// #route GET api/users/register
// #desc Register user
// #access Public
router.post("/register", (req, res) => {
User.findOne({ email: req.body.email }).then(user => {
if (user) {
return res.status(400).json({ email: "email already exists" });
} else {
const avatar = gravatar.url(req.body.email, {
s: "200", // Size
r: "pg", // Rating
d: "mm" // Default
});
const newUser = new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
});
}
});
});
module.exports = router;
When I attempt a postman test for the .post(/register ... ) route it stalls loading for some minutes before giving me the error of --
"Could not get any response
There was an error connecting to http://localhost:5000/api/users/register.
Why this might have happened:
The server couldn't send a response:
Ensure that the backend is working properly
Self-signed SSL certificates are being blocked:
Fix this by turning off 'SSL certificate verification' in Settings > General
Proxy configured incorrectly
Ensure that proxy is configured correctly in Settings > Proxy
Request timeout:
Change request timeout in Settings > General"
Tweaking the suggested options did nothing and I'm lead to think neither of these things are the cause, as I am able to successfully process the .get(/test) route.
**point of note, I add the key:values in the body of the postman request for name, email and password. The avatar is generated from the email.
This is the route I use(post) --- http://localhost:5000/api/users/register
and for reference this is the working route (get)
--- http://localhost:5000/api/users/test
Any help would be great!
There is no return statement in the else block. Try as following... It should work
// #route GET api/users/register
// #desc Register user
// #access Public
router.post("/register", (req, res) => {
User.findOne({ email: req.body.email }).then(user => {
if (user) {
return res.status(400).json({ email: "email already exists" });
} else {
const avatar = gravatar.url(req.body.email, {
s: "200", // Size
r: "pg", // Rating
d: "mm" // Default
});
return res.status(200).json(new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
}));
}
});
});
I guess you are trying to register a user and save it mongoDB and return the response.
What you are doing wrong is you are not responding the data back to user. Also you are not saving anything in the whole API call..
Here's how you can do it.
router.post("/register", (req, res, next) => {
User.findOne({ email: req.body.email }).then(user => {
if (user) {
return res.status(400).json({ email: "email already exists" });
} else {
const avatar = gravatar.url(req.body.email, {
s: "200", // Size
r: "pg", // Rating
d: "mm" // Default
});
const newUser = new User({
name: req.body.name,
email: req.body.email,
avatar,
password: req.body.password
});
newUser.save()
.then(user => {
res.status(201).json(user); // 201 is HTTP Code for "Created"
});
.catch(next); // we usually handle error in a common middleware, which is why, I am calling "next" here with error as 1st argument.
}
});
});