I am using angularjs and jade templating for my client side. And node and express on the server side. I recently added passport authentication and have the local strategy working fine when the login is attempted with valid credentials. It's the error cases that I'm having trouble with. I have implemented the custom callback to get my error messages to bubble back to the client. Now I get the error messages but the status gets set to 200 somewhere, instead of the 401 I expect.
// login route
app.post("/api/login", function(req, res, next) {
passport.authenticate("local", function(err, user, info) {
if (err) {
return next(err);
}
if (user === false) {
res.status = 401;
res.send(info.message);
} else {
res.json({success:"User logged in"});
}
})(req, res, next);
});
and this is the angular controller that submits the login request:
var loginObj = {email: $scope.login.email,
password:$scope.login.password};
$http.post("/api/login", loginObj)
.success(function(data, status) {
console.log("successful login");
console.log("data = " + JSON.stringify(data));
console.log("status = " + status);
})
.error(function(error) {
console.log("error logging in.");
console.log("error = " + JSON.stringify(error));
$scope.error = error;
});
So, suppose I have a password mismatch... the error in my validation routine sets my error message to "Invalid password, please try again." But what I see on the console is:
successful login
data = "Invalid password, please try again."
status = 200
I'm not resetting the status anywhere else. How is it getting set to 200? What am I doing wrong? Sorry, I'm a relative newbie to node and angular, so any help would really be appreciated. I know I'm not understanding some key point, I just can't figure out what. Thanks!
It's res.status(401), not res.status = 401.
res.status() is a function.
Related
I'm currently designing a oauth login system and I've encountered the following problem. I'm trying to redirect users back to the homepage once they have been logged in and session data has been set, but the res.redirect('/') throws NodeError: Cannot set headers after they are sent to the client. I cannot seem to get it to work. Below is the code which is causing the fault:
app.post(
"/auth/openid/return",
passport.authenticate("azuread-openidconnect", {
failureRedirect: "/login"
}),
function(req, res) {
let userProperties = req.user;
new Promise((resolve, reject) => {
MongoClient.connect(url, function(err, db) {
if (err) next(err);
let dbo = db.db("Lektier");
let query = {
oid: req.user.oid
};
dbo.collection("users").findOne(query, function(err, result) {
db.close();
if (result) {
let type = result.type;
resolve(type);
}
});
});
}).then(type => {
req.session.regenerate(function() {
req.session.user = userProperties.upn;
if (type == "teacher") {
req.session.teacheruser = userProperties.upn;
}
let names = userProperties.displayName.toString().split(" ");
req.session.FirstName = names[0];
req.session.LastName = names[1];
res.redirect("/");
});
});
}
);
Some help on the matter would be appreciated.
Whenever you see a message like this Cannot set headers after they are sent to the client, it means that the logic of your endpoint tried to send a response to the client and failed, because it actually already responded. res.redirect() of express actually sends some 3xx http status to the client and if you are saying that this method throws the error you're facing, something before it already sent a response.
I didn't find anything that could respond in the snippet you provided (besides the very res.redirect()), so I suggest you to look into your middleware. For example into passport authentication, since it is mentioned here.
I'm having trouble authenticating a small passport.js example project when I switch over to mongodb instead of a local array.
MongoDB is able to connect and add users from a form on the home page. On login however, there are problems.
Not sure if this matters but on the /login page onload I see 2 red errors in the browser console:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
login:1
Unchecked runtime.lastError: The message port closed before a response was received.
I put a log statement in the relevant route handler. It does not reach or log to the console.
app.post('/login', function sendUserRecordToAxiosOrRedirect(req, res, next) {
passport.authenticate('local', function controlErrorsOrSuccess(error, userRecord, info) {
console.log('inside pp.auth callback');
if (error) return next(error);
if (userRecord == false) return res.json ({ "error" : "Invalid Username" });
if (userRecord) {
console.log('---- User Found: INFO --------');
console.log(info);
res.redirect('/')
}
})
});
From the above I suspected something is awry with the Strategy I give to passport. The log statement in this one doesn't fire either:
passport.use(new Strategy(
function(username, password, done) {
console.log('inside Strategy');
db.users.findInDatabaseByUsername(username, function (error, userRecord) {
if (error) return done(error);
if (userRecord == false) return done(null, false); // no error, no userRecord
if (userRecord.password !== password) return done(null, false);
return done(null, userRecord);
})
}
));
Since I altered my serialization functions to search the database instead of an array I suspect the problem may lie here. Neither of the log statements here fire either:
const passUserIdOnlyToACallback = function(userRecord, callbackFunction) {
console.log("serializing...");
callbackFunction(null, userRecord.id);
};
passport.serializeUser(passUserIdOnlyToACallback);
function passUserRecordToCallbackById(userId, verifyCallback) {
db.users.findInDatabaseById(userId, function(error, userRecord) {
console.log("deserializing...");
if (error) return verifyCallback(error);
verifyCallback(null, userRecord)
})
}
passport.deserializeUser(passUserRecordToCallbackById);
Everything was working fine initially with the local array code, so my alterations broke something. Does anyone see the error?
I am experimenting with the softlayer-client api wrapper in my Node Express application. My goal is to update the VPN password of a User_Customer by calling the updateVpnPassword method on a specific user.
I can construct a call to achieve a VPN password update using request, but I'm not sure it's the best way to achieve the desired result.
Can the softlayer-client module be used to make an similar call to this:
function updateVpnPassword(req, res, next) {
// Construct URL to update VPN
myURL = 'https://' + <userIDAdmin> + ':' + <apiKeyAdmin> + '#api.softlayer.com/rest/v3/SoftLayer_User_Customer/' + <softLayerID> + '/updateVpnPassword/' + <newPassword> + '.json';
request.get({url: myURL}, function (error, response, body) {
console.log('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('body:', body);
});
next();
}
My initial attempts have been to try variations on this:
function updateVpnPassword(req, res, next) {
// Assuming var client = new SoftLayer();
client
.auth(<userIDAdmin>, <apiKeyAdmin>)
.path('User_Customer', <softLayerID>,'updateVpnPassword')
.parameters(<newPassword>)
.put(function(err,result){
console.log(result);
if (err) {
next(err); // Pass errors to Express.
}
else {
// update successful
}
});
next();
}
But the console log gives an error response like
{ message: { error: 'Internal Error', code: 'SoftLayer_Exception_Public' } }.
I expect a TRUE or FALSE response, to indicate the whether the update is successful.
A similar python client can be found here but I require an implementation in JS.
I'm not familiar with nodejs but I installed the package softlayer-node and run your second code and it worked.
I also created the following script and I got TRUE
var username = 'set me';
var apikey = 'set me';
var userId = 1111111;
var SoftLayer = require('softlayer-node');
var client = new SoftLayer();
client
.auth(username, apikey)
.path('User_Custome', userId, 'updateVpnPassword')
.parameters('P#ssword123')
.put()
.then(function(result) {
console.log(result);
}, function(error) {
console.log(error);
});
node command:
$ node updateVpnPassword.js
true
Did you tried by sending that request using curl or any other REST client like postman?
If you get the same error then I recommend you submit a ticket and provide information like the id of users you are trying to update the vpn password and the user with which you are sending the request.
I am getting this error when making a get request to a route that uses middleware. The route verifies the user has a valid token and displays a message and some basic user information.
The information is sent to the end-user just fine, however, I keep seeing these "can't set header errors" in the node console. I believe this has to do with a misusage of the next() function.
Here is my code:
user.js (router)
router.get('/me', VerifyToken, userController.me_get);
VerifyToken.js (middleware)
module.exports = (req, res, next) => {
var token = req.headers['authorization'];
//Check if token used is undefined
if(typeof token !== 'undefined') {
jwt.verify(token, 'secretkey', (err, authData) => {
if(err) {
return next(res.json({ message : 'You are not authorized to be here'}));
} else {
var usertoken = {
id : authData.user._id,
username : authData.user.username
}
res.locals.usertoken = usertoken;
return next();
}
});
return next();
} else {
//Forbidden
return next(res.sendStatus(403));
}
}
users.js (controller)
module.exports.me_get = (req, res, next) => {
return res.json({ message : 'You have arrived!', usertoken : res.locals.usertoken });
}
I just want to get rid of that "Error: Can't set headers after they are sent." in the console.
Any help is appreciated!
You shouldn't call .next if you're ending the request. Otherwise it will go to the next middleware, which will end the request again, and you will get Error: Can't set headers after they are sent error, because you can't end a request twice.
Replace:
return next(res.sendStatus(403));
With
return res.sendStatus(403);
And
return next(res.json({ message : 'You are not authorized to be here'}));
With
return res.json({ message : 'You are not authorized to be here'});
If you send a response with res.json() (or any other method that sends the response), don't call next(). next() will continue processing to other request handlers, some of which may try to send another response. Since you can only send one response per request, you get the error you see.
Once you've sent the response, just return. All processing is done at that point.
For example, replace this:
return next(res.json({ message : 'You are not authorized to be here'}));
with this:
res.json({ message : 'You are not authorized to be here'});
return;
Turns out I had an extra return next() at the bottom of my if block in the middleware.
My setup is as follows:
posting to /register will take the arguments and register a user via passport and mongoose. If this returns an UserExistsError the server sends this info to the client (via http error handling).
However the server also displays a 500 server error which should not occur.
This is because of the next() which as far as I understand routes the client to /register. /register itself does not exists as a page (only as the postadress as stated in the code)
So my question is: How to handle the response to not be an error or supress it? Can I use something else instead of next() to stop the redirect to /register? I just want the server to stop doing anything/going out of that function at that point.
Code:
app.post('/register', function(req, res, next) {
console.log('server registering user');
User.register(new User({username: req.body.username}), req.body.password, function(err) {
let tempstring = ""+err;
if(tempstring.indexOf("UserExistsError") !== -1){
return next(err); //get out of the function and into normal operation under '/'
}
});
});
This topic is bugging me and I might just missunderstand something trivial.
Even if /register is a post only route you still need to send a response. If you don't send a response of some kind, the request will hang and eventually timeout in the browser. I would suggest sending a json response like so.
app.post('/register', function(req, res, next) {
console.log('server registering user');
User.register(new User({username: req.body.username}), req.body.password, function(err) {
let tempstring = ""+err;
if(tempstring.indexOf("UserExistsError") !== -1){
return next(err); //get out of the function and into normal operation under '/'
}
res.json({message: 'message here'});
});
});
This will send a 200 OK reponse with some json in the body.
If you just want to pass the request down the line you need to call next without an err object like so.
app.post('/register', function(req, res, next) {
console.log('server registering user');
User.register(new User({username: req.body.username}), req.body.password, function(err) {
let tempstring = ""+err;
if(tempstring.indexOf("UserExistsError") !== -1){
return next(err); //get out of the function and into normal operation under '/'
}
//call next without an error
next();
});
});
I am not sure if this is what you are trying to achieve, but if there is no route that matches it will just go to an error 500.