I have a router like this:
/* get user info */
router.get('/info', authenticate(), function (req, res) {
models.User.getUserById(req.user.id, function (result) {
if (result) {
var user = result.dataValues;
res.json({
success: true,
data: user,
message: 'Get user info success!'
})
} else {
res.json({
success: false,
message: 'User does not exist!'
})
}
})
});
the authenticate() function is like this:
/* ensure authentication */
var authenticate = function () {
return passport.authenticate('jwt', {
session: false
});
}
If user logged in, the router would go to function(req, res). But what if user has not logged in yet ? How can I return to client a message if user has not logged in ? Can I put a callback to passport.authenticate() ? If I can, what is the parameters of this callback ?
According to Docs, you can add a failure redirect URL and a failure flash message like this :
var authenticate = function () {
return passport.authenticate('jwt', {
session: false,
failureRedirect: '/login',
failureFlash: 'Invalid username or password.'
});
}
Or if you want a custom callback :
var authenticate = function (req, res, next) {
return passport.authenticate('jwt', function (err, user, info) {
if (err) { return next(err); }
if (!user) {
// code if failed here
return res.redirect('/login')
}
req.logIn(user, function (err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username)
})
})
}
Related
If I want to handle errors from "incorrect" logins on this route how to collect (err) and where?
// POST LOGIN
usersRouter
.route('/login')
.post(passport.authenticate('local'), (req, res, next) => {
// for example, to declare an if here to catch err and call next with err...
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({ success: true, status: 'You are successfully logged in!' });
});
You can use like this
usersRouter.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) {
return res.send(401,{ success : false, message : 'authentication failed' });
}
return res.send({ success : true, message : 'authentication succeeded' });
})(req, res, next);
});
After I have successfully authenticated a user and the user gets logged in,
all requests made to my API get the Pending status.
My tests conclude that this only happens if and after req.login has run.
req.login(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
return res.send({ success : true, message : 'authentication succeeded' });
})
Full Login Passport code
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (! user) {
return res.send({ success : false, message : info.message });
}
req.login(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
return res.send({ success : true, message : 'authentication succeeded' });
})
})(req, res, next);
});
From what I can understand, there must be something blocking all requests made after.
Client code
let config = {
headers: {
'Content-type' : 'application/x-www-form-urlencoded'
}
}
const user = `username=${username}&password=${password}`;
await axios.post(`/auth/signup`, user, config)
.then(res => {
// USER IS LOGGED IN
if(res.data.success) {
//this.closePopup(); // POPUP gets closed.
}
})
What could be the cause of this? Thank you
The problem was with app.use(passport.session());
It needed to be placed under the route.
I have read a lot about this issue but any answer doesn't work for me. I am working with React, Express and Passport to manage my authenticate routine. The authentication routine is fine, and it makes the redirect that I want. But, when I refresh any route, it says me that I am not authenticate. It seems that Passport doesn't save the session. Here my code:
Server.js
const lisaApp = express();
lisaApp.use(bodyParser.json())
lisaApp.use(bodyParser.urlencoded({ extended: false }))
lisaApp.use(cookieParser())
lisaApp.use(session({
secret: config.secret,
resave: false,
saveUnitialized: false
}))
lisaApp.use(passport.initialize())
lisaApp.use(passport.session())
passport.use(auth.localStrategy);
passport.serializeUser(auth.serializeUser);
passport.deserializeUser(auth.deserializeUser);
lisaApp.post('/login', (req, res, next) => {
const validationResult = validateLoginForm(req.body);
if (!validationResult.success) {
return res.status(400).json({
success: false,
message: validationResult.message,
errors: validationResult.errors
});
}
return passport.authenticate('local', (err, userData) => {
if (err) {
if (err.response.statusText === 'Unauthorized') {
return res.status(400).json({
success: false,
message: 'The password is not right'
});
}
return res.status(500).json({
success: false,
message: 'Wrong data'
});
}
console.log('is authenticated?: ' + req.isAuthenticated()) // Here always is false
return res.json({
success: true,
token,
message: 'Successful Login',
user: userData.data
});
})(req, res, next);
});
// I am using this function as a middleware to check if the user is authenticated, always is false. No matter if I put right data in the login form
function ensureAuth (req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).send({ error: 'not authenticated' })
}
auth/index.js(passport routine)
var LocalStrategy = require('passport-local').Strategy;
var LisaClient = require('pos_lisa-client');
var config = require('../config');
var ClientApi = LisaClient.createClient(config.lisaClient);
exports.localStrategy = new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
session: false,
passReqToCallback: true
}, (req, username, password, done) => {
var authData = {
username,
password
}
// Here is a custom API, I pass the data that I need. This works fine
ClientApi.authenticate(authData, (err, token) => {
if (err) {
return done(err)
}
var token = token.data
ClientApi.getClient(username, (err, user) => {
if (err) {
return done(err)
}
user.token = token
return done(null, user)
})
})
})
exports.serializeUser = function (user, done) {
// The app never enters here
done(null, {
username: user.username,
token: user.token
})
}
exports.deserializeUser = function (user, done) {
// The app never enters here
ClientApi.getClient(user.username, (err, usr) => {
if (err) {
return done(null, err)
} else {
usr.token = user.token
done(null, usr)
}
})
}
Where I am wrong?
If you're using a custom authentication callback, you have to call req.logIn() to establish a session (or you can create one manually):
// Add this where you are console.log'ing `req.isAuthenticated()`
req.logIn(userData, function(err) {
if (err) return next(err);
console.log('is authenticated?: ' + req.isAuthenticated());
return res.json({
success: true,
token,
message: 'Successful Login',
user: userData.data
});
});
This is documented here (scroll down to "Custom Callback"):
Note that when using a custom callback, it becomes the application's responsibility to establish a session (by calling req.login()) and send a response.
I have struggled so much with Passport because the custom callback feature simply does not work. Here's how I initialize passport:
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var bodyParser = require('body-parser');
// Set up passport
passport.use('local', new LocalStrategy({
usernameField: 'userId',
passwordField: 'password'
}, function (userId, password, cb) {
users.findByUserId(userId, function (err, user) {
if (err) {
return cb(err);
}
if (!user) {
return cb(null, false);
} else {
if (user.password !== password) {
return cb(null, false);
}
return cb(null, user);
}
});
}));
passport.serializeUser(function (user, cb) {
cb(null, user.userId);
});
passport.deserializeUser(function (id, cb) {
users.findByUserId(id, function (err, user) {
if (err) { return cb(err); }
cb(null, user);
});
});
Then, this is how it's SUPPOSED to log a user in when a user posts to '/login':
exports.tryLogin = function (req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return res.status(500).json({ success : false, message : 'Internal server error: ' + err.message }); }
if (!user) { return res.status(500).json({ success : false, message : 'user not found'}); }
req.logIn(user, function(err) {
if (err) { return res.status(500).json({ success : false, message : 'Internal server error: ' + err.message }); }
return res.status(200).json({ success : true });
});
})(req, res, next);
}
Here's how I have to do it because it never detects the user. The 'user' in the above method is always undefined. I have to construct it from the request myself to get it to work:
exports.tryLogin = function (req, res, next) {
var user = {
userId: req.body.userId,
password: req.body.password
}
req.logIn(user, function (err) {
if (err) {
return res.status(500).json({ success : false, message : 'Internal server error: ' + err.message });
}
return res.status(200).json({ success : true, message : 'authentication succeeded' });
});
}
This works, but feels wrong because I'm never calling passport.authenticate. Is this ok or should I be banging my head against the wall trying to get the custom callback to work as defined in the documentation?
Sam
Yes this is wrong approach.
This code means:
exports.tryLogin = function (req, res, next) {
var user = {
userId: req.body.userId,
password: req.body.password
}
req.logIn(user, function (err) {
if (err) {
return res.status(500).json({ success : false, message : 'Internal server error: ' + err.message });
}
return res.status(200).json({ success : true, message : 'authentication succeeded' });
});
}
that You do not check permissions, You practically login anybody without password check.
I've looked at how error handling should work in node via this question Error handling principles for Node.js + Express.js applications?, but I'm not sure what passport's doing when it fails authentication. I have the following LocalStrategy:
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, next) {
User.find({email: UemOrUnm}, function(err, user){
if (err) { console.log('Error > some err'); return next(err); }
if (!user) { console.log('Error > no user'); return next('Incorrect login or password'); }
if (password != user.password) {
return next(Incorrect login or password);
}
return next(null, user);
});
}
));
After I see 'Error > some err' console printout, nothing else happens. I would think it should continue on the the next path with an error parameter, but it doesn't seem to do that. What's going on?
The strategy-implementation works in conjunction with passport.authenticate to both authenticate a request, and handle success/failure.
Say you're using this route (which is passed an e-mail address and a password):
app.post('/login', passport.authenticate('local', {
successRedirect: '/loggedin',
failureRedirect: '/login', // see text
failureFlash: true // optional, see text as well
});
This will call the code in the strategy, where one of three conditions can happen:
An internal error occurred trying to fetch the users' information (say the database connection is gone); this error would be passed on: next(err); this will be handled by Express and generate an HTTP 500 response;
The provided credentials are invalid (there is no user with the supplied e-mail address, or the password is a mismatch); in that case, you don't generate an error, but you pass a false as the user object: next(null, false); this will trigger the failureRedirect (if you don't define one, a HTTP 401 Unauthorized response will be generated);
Everything checks out, you have a valid user object, so you pass it along: next(null, user); this will trigger the successRedirect;
In case of an invalid authentication (but not an internal error), you can pass an extra message along with the callback:
next(null, false, { message : 'invalid e-mail address or password' });
If you have used failureFlash and installed the connect-flash middleware, the supplied message is stored in the session and can be accessed easily to, for example, be used in a template.
EDIT: it's also possible to completely handle the result of the authentication process yourself (instead of Passport sending a redirect or 401):
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (! user) {
return res.send({ success : false, message : 'authentication failed' });
}
// ***********************************************************************
// "Note that when using a custom callback, it becomes the application's
// responsibility to establish a session (by calling req.login()) and send
// a response."
// Source: http://passportjs.org/docs
// ***********************************************************************
req.login(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
});
What Christian was saying was you need to add the function
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({success:true});
});
So the whole route would be:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (! user) {
return res.send(401,{ success : false, message : 'authentication failed' });
}
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
});
source: http://passportjs.org/guide/login/
You need to add req.logIn(function (err) { }); and do the success redirect inside the callback function
Some time has passed and now the most right code will be:
passport.authenticate('local', (err, user, info) => {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (!user) {
return res.status(401).send({ error: 'Authentication failed' });
}
req.login(user, (err) => {
if (err) {
return next(err);
}
return res.status(202).send({ error: 'Authentication succeeded' });
});
});
I found this thread very useful!
https://github.com/jaredhanson/passport-local/issues/2
You could use this to return error and render it in form.
app.post('/login',
passport.authenticate('local', { successRedirect: '/home', failWithError: true }),
function(err, req, res, next) {
// handle error
return res.render('login-form');
}
);
This is what I got after console.log(req) at the failler route.
const localStrategy = new LocalStrategy({ usernameField: "email" }, verifyUser);
passport.use(localStrategy);
const authenticateWithCredentials = passport.authenticate("local", {
failureRedirect: "/api/auth/login-fail",
failureMessage: true,
});
validation method find your user from db and throw error to the cb if there is any
const verifyUser = async (email, password, cb) => {
const user = await User.findOne({ email });
if (!user) return cb(null, false, { message: "email/password incorrect!" });
const isMatched = await user.comparePassword(password);
if (!isMatched)
return cb(null, false, { message: "email/password incorrect!" });
cb(null, {
id: user._id,
email,
name: user.name,
});
};
now setup your route
router.post("/sign-in", authenticateWithCredentials,(req, res) => {
res.json({user: req.user})
});
router.get("/login-fail", (req, res) => {
let message = "Invalid login request!";
// if you are using typescript cast the sessionStore to any
const sessions = req.sessionStore.sessions || {};
for (let key in sessions) {
const messages = JSON.parse(sessions[key])?.messages;
if (messages.length) {
message = messages[0];
break;
}
}
res.status(401).json({ error: message });
});