Whenever I go this URL for the second time, I always get
can't set headers already sent
app.post('/auth/facebook', function(req, res, next) {
// What should I do with the access token?
User.findOne({ facebook: req.body.facebookId}, function(err, user) {
// if user does exist then simply send the token back to the client
if (user) {
return res.send({ token: createJWT(user) })
}
// if user doesnt exist
var user = new User();
user.email = req.body.email;
user.facebook = req.body.facebookId;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.save(function(err) {
return res.send({ token: createJWT(user)})
});
});
});
Is it because I send the token twice?
Yes, this error occurs because you are using the res.send to set response headers twice. The first time you set them, even if it is inside the if bracket, it is the normal use scenario (most times you will have a user returned) so, the second time you call it (this is always executed), you have already set them(when user exists - that is most of the times).
I think that happens with any response headers, such as res.json that KibGzr suggested. That as general advice. Concerning your problem, I would bracket the scenario that there is no user, and inside that I would execute the logic of creating the new user. Then outside the bracket, I would set the headers, as I would always want to sent the token. Like this :
app.post('/auth/facebook', function(req, res, next) {
// What should I do with the access token?
User.findOne({ facebook: req.body.facebookId}, function(err, user) {
// if user doesnt exist create one
if (!user) {
var user = new User();
user.email = req.body.email;
user.facebook = req.body.facebookId;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.save(function(err) {
if(err){
console.log(err);
}
});
//Then send the token as a user exists anyway
return res.send({ token: createJWT(user) });
}
});
So the general idea is, set the headers once per scenario - make sure no response is set twice per scenario. Hope I helped.
The user object can be either null or a user.Put the logic inside a if else block makes it easier to read the code.Always console log the user object and req.body while dealing with authentication and signing up logic.Makes your life easier.
app.post('/auth/facebook', function (req, res, next) {
// What should I do with the access token?
User.findOne({facebook: req.body.facebookId}, function (err, user) {
// if user does exist then simply send the token back to the client
console.log(user);
if (user != null) {
return res.send({token: createJWT(user)})
} else {
// if user doesnt exist
var user = new User();
user.email = req.body.email;
user.facebook = req.body.facebookId;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.save(function (err) {
return res.send({token: createJWT(user)})
});
}
});
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.
Whenever a user registers i am sending him an email which contains the link which user needs to click to get verified. I am passing a token in that link. When the user clicks the link he should get verified but i am not able to do this. I can just retrieve the token from the link but i am unable to find the user in the database and update the value.
Here is my code:
router.route('/verify')
.get(isNotAuthenticated, function(req, res){
var verifyToken = req.query.id;
var user = User.findOne({ 'secretToken': verifyToken });
if (!user) {
req.flash('error', 'No user found with this email id, please check your email id or incorrect link');
res.redirect('/users/register');
return;
}
user.active = true;
user.secretToken = '';
user.save();
req.flash('success', 'Thank you! Now you may login.');
res.redirect('/users/login');
res.redirect('login');
Try using promise to do this instead of assignment.
User.findOne({ 'secretToken': verifyToken })
.then(user => {
// do something with user
})
.catch(err => {
// do something with error
})
If you are using JWT to validate your routes you can:
1 - Generate the link verification with one "hash value"(token), save this token in the user document (user collection).
send the link e.g. "https://site/user/verify?token=3f0621f7e4c41ece51926a40cee4dae0be65ct7"
2 - Disable the security for this route:
app.use(jwt({secret: process.env.SECRET}).unless({path: ['/users/verify']}));
3 - Receive the request to verify the user:
router.put('/verify', bodyParser.json(), function (req, res, next) {
try {
const user = userController.verify(req.query.token);
res.status(200).json(user);
} catch (err) {
next(err);
}
});
4 - Find and update the user(as verified):
User.findOneAndUpdate(
{
token: token,
},{$set:{verified:true}})
.then((result) => {
return result;
}).catch((err) => {
//throw error
});
If you need to wait the update for execute other sttufs, you can use async/wait: Async/Await Tutorial
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.
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
})
I want to automatically generate user accounts by generating a random username and password, and then the user is logged in automatically (the user doesn't know his username/password, his browser just stores the session cookie).
Passport functions as middleware, so how can I authenticate the user I just generated? Or, would it be better to somehow redirect to my app.post('/login') route and send those variables? (But somehow sending those to the browser, just to be sent back to the server doesn't seem very secure or efficient).
app.get('/signup', function(req, res) {
if(req.isAuthenticated()) { res.redirect('/'); }
else {
var today = new Date();
var weekDate = new Date();
weekDate.setDate(today.getDate() + 7);
var key1 = Math.random().toString();
var key2 = Math.random().toString();
var hash1 = crypto.createHmac('sha1', key1).update(today.valueOf().toString()).digest('hex');
var hash2 = crypto.createHmac('sha1', key2).update(weekDate.valueOf().toString()).digest('hex');
var newUser = new models.User({
username: hash1,
password: hash2,
signupDate: today,
accountStatus: 0,
expirationDate: weekDate,
});
newUser.save(function(err) {
if(err) {}
console.log("New user created.");
//HOW CAN I PASS USERNAME AND PASSWORD ARGUMENTS???
passport.authenticate('local')();
res.redirect('/login');
})
}
});
Replace your call to passport.authenticate('local')(); with
req.logIn(user, function(err) {
if (err) { return next(err); }
//copied from the docs, you might want to send the user somewhere else ;)
return res.redirect('/users/' + user.username);
});
and let me know how that goes.
the answer by rdrey was very helpful. One detail that might be obvious to most but was not to me is that model .save () gets err and the record in the callback. So the pattern in its entirety is
newuser.save(function(err,user) {
req.logIn(user, function(err) {
if (err) { return next(err); }
//copied from the docs, you might want to send the user somewhere else ;)
return res.redirect('/users/' + user.username);
});