The below custom call back for passport.js doesn't seems to work, no mater what i do.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, users, info) {
console.log(users);
if (user === false) {
console.log('Failed!');
} else {
res.redirect('/');
}
})(req, res, next);
});
The same if i change it to like below all works as expected.
app.post("/login"
,passport.authenticate('local',{
successRedirect : "/",
failureRedirect : "/login",
})
);
Also I've noticed when using custom callback even the passport.serializeUser and passport.deserializeUser also not getting invoked by passport.js.
Is this any sort of a bug or am i doing something wrong here ??
My Local-Strategy:
passport.use('local-sigin',new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
console.log('Passport Strategy Sign in:');
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'email' : email }, function(err, user) {
// if there are any errors, return the error before anything else
if (err)
return done({status:'ERROR',message:'Something went wrong!'});
// if no user is found, return the message
if (!user)
return done({status:'ERROR',message:'No user found.'}, false);
// if the user is found but the password is wrong
if (!user.validPassword(password))
return done({status:'ERROR',message:'Oops! Wrong password.'}, false);
// all is well, return successful user
return done({status:'OK',message:'Login success.'}, user);
});
}));
I am guessing that by 'doesn't work' you mean to say that the user is never being logged in.
Firstly, your local strategy is named 'local-sigin' however on a POST to '/login' you are invoking the 'local' strategy, which presumably doesn't exist:
passport.use('local', new LocalStrategy({
Change the name of your strategy to be consistent (or vice versa!):
passport.authenticate('local'
Secondly, your 'local' authentication callback has a parameter users (plural) but you are trying to access user (singular) within its body, meaning user is undefined and user === false is false under strict equality:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
// ^^^^
console.log(user);
if (!user) {
console.log('Failed!');
} else {
res.redirect('/');
}
})(req, res, next);
});
And finally, you are never logging the user in when authentication is successful. Creating a session for a user is not automatic, you must call req#login:
Passport exposes a login() function on req (also aliased as logIn()) that can be used to establish a login session.
Let's add that to your authentication callback:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
console.log(user);
if (!user) {
console.log('Failed!');
} else {
req.login(user, function (err) {
if(err) {
console.log(err);
return;
}
res.redirect('/');
});
}
})(req, res, next);
});
Take a look at the Passport docs, they explain in a good amount of detail how these processes work and how to implement them.
Related
I am currently experiencing an issue with passportJS. I am using a template to render the pages (EJS). The login and signup is always one step behind as I am having a different navigation bar when the user is logged in. Once I hit the submit button with the correct credentials on the login page, it does not update the navbar the user being redirected. I need to either refresh the page or click on another link within the app.
passport.use(
new LocalStrategy(
{
usernameField: "email",
passwordField: "password"
},
function(email, password, done) {
knex
.select("*")
.from("users")
.where({ email })
.first()
.then(user => {
if (!user) return done(null, false);
const hash = user.password;
bcrypt.compare(password, hash, function(err, res) {
if (res === true) {
return done(null, user);
} else {
return done(null, false);
}
});
})
.catch(err => {
return done(err);
});
}
)
);
app.post('/login', passport.authenticate(
'local'), function(req, res, next) {
if(req.user.id) {
res.locals.isAuthenticated = req.isAuthenticated();
console.log("===========> working", res.locals.isAuthenticated)
res.locals.user = req.user;
console.log("===========> working", res.locals.user)
res.redirect('/')
} else {
res.redirect('/login')
}
}
);
app.use(function(req, res, next) {
res.locals.isAuthenticated = req.isAuthenticated();
res.locals.user = req.user;
console.log(res.locals.isAuthenticated, res.locals.user)
next();
});
Above is the code I have with local strategy. The two console.logs with "======> working" show "true" and the user object with the right credentials and does the proper redirect.
However the second function shows "false", undefined until I refresh.
Any thoughts about where the problem could be? Many thanks!
Started learning Node today and I am almost positive my user is not being logged out correctly and I do not understand why. To login the user I've written the following code in my routes file
app.post('/api/signup', passport.authenticate('local-signup', function(err, res, req) {
console.log(res);
}));
then in another file this code to handle the logic of logging in my user
passport.serializeUser(function(user, done) {
console.log("serializeUser");
console.log(user);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializeUser");
console.log(id);
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use('local-signup', new LocalStrategy(
{
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true
},
function(req, username, password, done) {
console.log(req.sessionID);
process.nextTick(function() {
User.findOne({ 'local.username': username }, function(err, user) {
if (err) {
return done(err);
}
if (user) {
return done(null, false, req.flash('signupMessage', "Username already taken"));
} else {
let newUser = new User();
newUser.local.username = username;
newUser.local.password = newUser.generateHash(password);
newUser.save( function(saveErr) {
if (saveErr) {
throw saveErr;
}
return done(null, newUser);
});
}
});
});
})
);
Then to logout my user I wrote the following code in my routes file
app.get('/api/session', function(req, res) {
console.log(req.sessionID);
req.logOut();
console.log(req.sessionID);
});
A few things confuse me about this code.
To my understanding serializeUser is suppose to change the user.id to bits and store it in the cookies but the console.log never prints which leads me to think that this code never runs, why? I read that req.login() is implicitly called so I should not need to worry about it.
The console.log which is inside the callback of LocalStrategy prints a sessionID. When is this set if I haven't created my new user yet or the console.log mentioned in point 1 never runs?
Inside of logout I call req.logOut() with a console.log(req.sessionID) before and after req.logOut() and these both print the same thing. I'm assuming these sessionID's are what the cookies use to determine a session is valid, if so, does this mean the user never logs out as the sessionID is never changed?
Thanks
When configuring passport using Express and NodeJS, I am throwing an error if the user has an invalid email address. After this error I would like to redirect to a failure page giving them instructions on how to log in correctly. Is there are a better way to do this? If not, how would I go about catching the error in some fashion and redirecting to a new page.
passport.use(new GoogleStrategy({
clientID : auth.googleAuth.clientID,
/* Settings redacted for brevity */
},
function(token, refreshToken, profile, done) {
User.findOne(
{
"google.id" : profile.id
},
function(err, user) {
if (err) return done(err)
if (user) return done(null, user)
else {
if (email.indexOf("lsmsa.edu") > -1) {
// Code redacted for brevity
} else {
done(new Error("Invalid email address"))
}
}
}
)
}))
I think you can use this:
Redirects
A redirect is commonly issued after authenticating a request.
app.post('/login',
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
In this case, the redirect options override the default behavior. Upon
successful authentication, the user will be redirected to the home
page. If authentication fails, the user will be redirected back to the
login page for another attempt.
Or this:
Custom Callback
If the built-in options are not sufficient for handling an
authentication request, a custom callback can be provided to allow the
application to handle success or failure.
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
Please read the document:
https://www.passportjs.org/concepts/authentication/downloads/html/#middleware
Note: I quite like BlackMamba's answer as well, adding the custom callback / redirect is a perfectly acceptable option.
Simply add your own error handling middleware to Express:
passport.use(new GoogleStrategy({
clientID : auth.googleAuth.clientID,
/* Settings redacted for brevity */
},
function(token, refreshToken, profile, done) {
User.findOne({
"google.id" : profile.id
},
function(err, user) {
if (err) return done(err)
if (user) return done(null, user)
else {
if (email.indexOf("lsmsa.edu") > -1) {
} else {
// Throw a new error with identifier:
done(throw {
type: "invalid_email_address",
code: 401,
profileId: profile.id
}));
}
}
}
)
}));
// The error handling middleware:
app.use(function(e, req, res, next) {
if (e.type === "invalid_email_address") {
res.status(e.code).json({
error: {
msg: "Unauthorized access",
user: e.profileId
}
});
}
});
You'll notice I modified this answer a little bit with a more robust error composition. I've defined the code property of the error to match the applicable HTTP status code -- in this case, 401:
// callback
done(throw {
// just a custom object with whatever properties you want/need
type: "invalid_email_address",
code: 401,
profileId: profile.id
}));
In the error handling, we simply check the type is invalid_email_address (you can make it whatever you want, but it should be consistent across your app) and then write the error out using the "code" as the HTTP status code:
// e is the error object, and code is the custom property we defined
res.status(e.code).json({
error: {
msg: "Unauthorized access",
user: e.profileId
}
});
Here's a self-contained working example with a redirect:
var express = require('express');
var app = express();
app.all('*', function(req, res) {
throw {type: "unauthorized", code: 401}
})
app.use(function(e, req, res, next) {
console.log(e);
if (e.code === 401) {
res.redirect("/login")
} else {
res.status(500).json({error: e.type});
}
});
app.listen(9000);
I'm using the standard passport local strategy (with express.js) for the signup form on my website. When the failureRedirect is invoked, it redirects back to my signup form correctly, but all the values of my form are wiped blank. I get why this is happening, because of the redirect, but ... This is incredibly annoying for the user if they've made a simple mistake like forgetting a checkbox or their username is already taken. (Also, I know the password should not be sent back to the view). Is there a way to persist the users entered values even after the redirect with passport?
//my route
.post('', passport.authenticate('local-signup', {
failureRedirect: '/account/signup', // redirect back to the signup page if there is an error
failureFlash: true // allow flash messages
}), function(req, res) {
...
});
passport code
passport.use('local-signup', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
}, function(req, username, password, done) {
process.nextTick(function() {
if(password != params.password2) {
return done(null, false, req.flash('error', 'Passwords do not match.'));
}
User.findOne({
'username': username
}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('error', 'That username is already taken.'));
} else {
... create a new user ...
}
});
});
}));
function(req, username, password, done) {
What if you wrap passport in your route logic. For example,
app.post('/login', function(req, res, next) {
// Do something here with the username & password, like
// checking if the username is available.
if (!username || !password) {
// Render the login form, and pass in the username and password.
res.render('/login', {username: username, password: password});
} else {
// If all is validated, attempt the login:
passport.authenticate('local-signup', {
failureRedirect: '/account/signup',
failureFlash: true
}), function(req, res) {
...handle response here...
}
}
});
I'm not sure if all of that is syntatically correct, but the idea is to do whatever application-specific validation you have to do before you attempt to authenticate with Passport.
I've got a node app that operates in isolation. Without js on in the client it just operates synchronously, setting cookies with passport.js. When the client is js enabled then the auth is done via a rest route.
All seems fine, except that if I have authenticated (and have cookies set) asynchronously, but then refresh the page (or navigate synchronously to a new one [for whatever reason]), the server sends the response with new cookies that overwrite the old ones and setting the user back to being unauthenticated.
// =============================================================================
// AUTHENTICATE (FIRST LOGIN) ==================================================
// =============================================================================
// process the login form
app.post('/login', passport.authenticate('local-login', {
successRedirect: '/profile', // redirect to the secure profile section
failureRedirect: '/browse?p=0', // redirect back to the signup page if there is an error
failureFlash: true // allow flash messages
}));
app.post('/async/login', function(req, res, next) {
passport.authenticate('local-login', function(err, user, info, status) {
if (err) {
return res.send(err);
}
if (user) {
user.local = null;
return res.send(user);
} else {
return res.send(info);
}
})(req, res, next);
});
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass in the req from our route (lets us check if a user is logged in or not)
},
function(req, email, password, done) {
// asynchronous
process.nextTick(function() {
User.findOne({
'local.email': email
}).populate({
path: 'spots comments currentLocation'
}).exec(function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// if no user is found, return the message
if (!user)
return done(null, false, req.flash('loginMessage', 'No user found.'));
if (!user.validPassword(password))
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
// all is well, return user
else
User.populate(user, {
path: 'spots.bird',
model: 'Bird'
}, function(err, user) {
if (err)
return done(err);
else
User.populate(user, {
path: 'spots.location',
model: 'Location'
}, function(err, user) {
if (err)
return done(err);
else
console.log(util.inspect(user, showHidden = false, depth = 5, colorize = true));
return done(null, user);
});
});
});
});
}));
Node App:
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cookieParser());
sessionVars = {
keys: ['<removed>'],
//secureProxy: true, // if you do SSL outside of node
maxAge: 2592000000
};
app.use(session(sessionVars));
app.use(flash());
require('./../modules/auth/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
Does anyone know why?
UPDATE: The async route doesn't seem to call the serializeUser function (which explains everything!). Does anyone know how to force serialization?
If I try a direct call: passport.serializeUser(user, done) then I need the done parameter, which isn't available in the routes file (although I could pass it through);
Does anybody know if serializeUser is meant to be called as a matter of course with passport strategies?
So I found that if you use the custom form of the authenticate function:
app.get('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
it doesn't seem to pass it through the serializeUser function that you provide. The cookies for sessions stay in your browser and change all the time, even when you're logged out as they probably still 'tracking' your behaviour.
If you use the 'black box' version of the function:
app.post('/login',
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login' }));
that works because it calls serializeUser automatically.