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.
Related
I'm writing an Express application that uses passport.js for authentication and I'd like to utilise express-session so that the user stays logged in if I call another endpoint. passport.authenticate and req.logIn both work, but when I call another endpoint, req.isAuthenticated() returns false and req.user is undefined
Here's my setup code for passport:
passport.use(new LocalStrategy(
function(username, password, done) {
db.all("SELECT * from users WHERE username='" + username +"'", function(err, rows) {
if (err != null) {
return done(err);
}
if (rows.length == 0) {
return done(null, false, {message: "No user found"});
}
var row = rows[0]
bcrypt.compare(password, row.password, function(err, res) {
if (err) {
return done(err)
}
if (res) {
return done(null, username)
} else {
return done(null, false, {message: "Incorrect password"})
}
})
})
}
))
passport.serializeUser((username, done) => {
done(null, username);
});
passport.deserializeUser(function(id, done) {
db.all("SELECT * from users WHERE username='" + id + "'", function(err, rows){
if (err != null) {
return done(err)
}
if (rows.length == 0) {
return done(null, false)
}
return done(null, id)
})
})
// add & configure middleware
app.use(session({
store: new SQLiteStore,
secret: my-secret
resave: false,
saveUninitialized: true
}))
app.use(passport.initialize());
app.use(passport.session());
This is my login endpoint
app.post('/login', function(req, res) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return res.status(500).json({ err })
}
if (!user) {
return res.status(400).json({
err: info.message
})
}
req.logIn(user, function(err) {
if (err) {
console.log(err)
return res.status(500).json({ err })
}
res.status(200).json({
msg: 'Success'
})
});
})(req, res)
})
Everything is fine at that point. It outputs the correct user ID.
I then call this endpoint:
app.post('/check-log-in', function(req, res, next) {
console.log(req.user)
console.log(req.isAuthenticated())
if (req.user) {
res.status(200).json({
loggedIn: true
})
} else {
res.status(200).json({
loggedIn: false
})
}
})
req.user is undefined and req.isAuthenicated() returns false.
I'm sure I've set up passport somewhere incorrectly but I've looked over examples and even some of code from another project that has worked but nothing seems to be doing the trick. Any advice?
This answer fixed my issue. It was a problem with fetch, so I switched to axios
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.
So, I have everything working but it is not showing it is an authenticate user even though it arrives at the proper places...
javascript code from the page to validate login
var UserManager = {
validateLogin : function (username, password) {
var userData = {
username: username,
password: password
}
return new Promise(function(resolve, reject) {
$.ajax({
url: "/musicplayer/users/api/login",
dataType: "json",
data: userData,
type: "POST",
success: function loginSuccess(result, status, xhr) {
resolve(null);
},
error: function loginError(xhr, status, result) {
reject(new Error(result));
},
});
});
}
}
function userLogin(){
UserManager.validateLogin($('#loginEmail').val(), $('#loginPassword').val()).then(function(response) {
window.location = '/musicplayer/library'
},
function(error){
$("#msgBox").messageBox({"messages" : error.message, "title" : "Warning", boxtype: 4 });
$("#msgBox").messageBox("show");
});
return false;
}
local.strategy.js
var passport = require('passport');
var localStrategy = require('passport-local').Strategy;
var userLibrary = require('../../classes/music/userlibrary.js');
module.exports = function () {
passport.use(new localStrategy(
{
usernameField: 'username',
passwordField: 'password'
},
function(username, password, done) {
//validating user here
var userManager = new userLibrary.UserManager();
userManager.login(username, password).then(
function (user){
done(null, user);
},
function (reason){
if (reason.err) {
done(err, false, info);
}
else {
done(null, false, {message: reason.message});
}
}
);
})
);
};
Router
/******* validate the user login ********/
usersRouter.post('/api/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
console.log("Login Failed", err.message + " - " + err.stack);
if (req.xhr){
res.status(500).send({ error: 'Internal Error' });
}
else {
next(err);
}
}
else if (!err && !user){
err = new Error();
err.message = info.message;
err.status = 401;
console.log("Invalid Data", err.message);
if (req.xhr){
res.status(401).send({ error: err.message });
}
else {
next(err);
}
}
else if (user){
console.log("Successful Login:", user);
res.status(200).send({message: "successful"});
}
}
)(req, res, next);
});
passport.js file which has my Middleware...
var passport = require("passport");
module.exports = function (app) {
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done){
done(null, user);
});
passport.deserializeUser(function(user, done){
done(null, user);
});
require('./strategies/local.strategy')();
app.all('/musicplayer/*', function (req, res, next){
// logged in
//need function for exceptions
if (req.user || req.url === '/musicplayer/users/api/login' || req.url === '/musicplayer/users/signin') {
next();
}
// not logged in
else {
// 401 Not Authorized
var err = new Error("Not Authorized");
err.status = 401;
next(err);
}
});
}
Userlibrary/UserManager
I am using promises to be able to utilize the creation of a library and to deal with sync versus async issues that I ran into early on...
var sqlite3 = require('sqlite3').verbose();
function User() {
this.email = "";
this.password = "";
this.userid = "";
};
function UserManager () {
this.user = new User();
};
UserManager.prototype.login = function (email, password) {
var db = new sqlite3.Database('./data/MusicPlayer.db');
params = {
$email: email,
$password: password
}
var self = this;
return new Promise(function(resolve, reject){
db.serialize(function () {
db.get("SELECT * FROM users WHERE email = $email and password = $password", params, function (err, row) {
db.close();
if (!err && row) {
//log in passed
self.user.userid = row.userid;
self.user.email = row.email;
self.user.password = row.password;
resolve(self.user);
}
else if (!err) {
//log in failed log event
reject({
err: err,
message: null
});
}
else {
//error happened through out an event to log the error
reject({
message : "Email and/or Password combination was not found",
err : null
});
}
});
});
});
};
module.exports = {
User : User,
UserManager : UserManager
}
Now, I have debugged this and it is for sure getting to "successful Login"
Returns to the browser with success, the browser says okay let me redirect you to the library page (which is really just a blank page). When it goes to my library page I get a 401 unauthorized.
So if I debug inside the middleware to ensure authentication. I look at req.user and it is undefined and I try req.isAuthenticated() it returns a false.
I think I must be missing something...
What I want is a global authentication saying hey is this person logged in. And then I will set up the route/route basis say okay do they have permission for this page or web service call.
Right now I am sticking with session for everything as it is not useful to me to learn web tokens at this point and time.
Any help would be appreciated... I been around and around on this looking at examples out there. But the examples I find are the "basic" examples no one calling a library to validate from database or they are not trying to set up the authorization globally but rather on a route by route basis.
Upon searching I found this article
https://github.com/jaredhanson/passport/issues/255
then I found this in documentation
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);
});
and that worked for me... I basically forgot to do the req.logIn method itself when using the custom callback.... I knew it was something simple... Hope this helps someone in the future.
I am building app using MEAN STACK. I want to use passport-local authentication for my login form. But a the time of form submission i am getting POST http://localhost/login 404 (Not Found) please have a look of my code below This is my controller:
lyfee.controller('loginCtrl', ['$scope', '$http', function($scope, $http) {
$scope.user = {};
$scope.login = function() {
// var data = {User: $scope.user }
//console.log($scope.user);
console.log("login function call");
$http.post('/login', $scope.user);
console.log("login request send");
}
}]);
and this is my server.js :
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.getUserByusername(username, function(err, user) {
if (err) throw err;
if (!user) {
return done(null, false, {
message: 'Unknown USER'
});
}
User.comparePassword(password, user.password, function(err, isMatch) {
if () throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {
message: 'Invalid password'
});
}
});
});
}));
app.post('/login',
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}),
function(req, res) {
res.redirect('/');
});
in which file should i write getUserByusername and comparePassword function ? and what is mistake i am doing please correct it and give me some suggestion.
In your model suppose that User.js write functions like this:
/**
* Check the user's password
*/
dbSchema.methods.comparePassword = function(candidatePassword, cb) {
var status = this.password.localeCompare(candidatePassword.trim());
if (status != 0) {
return cb(err);
}
cb(null, true);
};
then use the function like this
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({
username: username
}, function(err, user) {
if (err) throw err;
if (!user) {
return done(null, false, {
message: 'Unknown USER'
});
}
/**
* Check the user's password
*/
User.comparePassword(password, user.password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {
message: 'Invalid password'
});
}
});
});
}));
I'm developing a mean application with passport, and I'm running through this issue:
I have a LocalStrategy to log on the user based on the application database. I need, however to login the user simultaneously on another service with possible multiple accounts. The thing is, once I route to authorize these logins, and set the variables to req.account, I cannot access them in other routes. Note that I can get the data I want, I just want to access it from somewhere other than this route, like req.user. I will post some of my code to clarify the situation.
Local Login route
app.post('/login', function (req, res, next) {
passport.authenticate('local-login', function (err, user) {
if (err)
return next(err);
if (!user)
return res.status(400).json({status: 'Invalid Username'});
req.login(user, function (err) {
if (err)
return next(err);
res.status(200).json({status: 'User successfully authenticated'});
});
})(req, res, next);
});
Local login passport config
passport.use('local-login', new LocalStrategy(function (user, pswd, done) {
User.findOne({'username': user}, function (err, user) {
if (err)
return done(err);
if (!user || !user.validPassword(pswd))
return done(null, false);
return done(null, user);
});
}));
The other service passport config
passport.use('other-login', new OtherStrategy(function (docs, done) {
if (docs.length === 0)
return done(null, false);
var accounts = [];
var user, pswd, data;
var counter = docs.length;
for (var i = 0; i < docs.length; i++) {
user = docs[i]._id;
pswd = docs[i].password;
request.post(<serviceurl>, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: qs.stringify({
grant_type: 'password',
username: user,
password: pswd,
client_id: process.env.API_KEY
})
}, function (err, res, body) {
if (err)
return done(err);
data = JSON.parse(body);
data.username = docs[docs.length - counter]._id;
accounts.push(data);
counter--;
if (counter === 0)
return done(null, accounts);
});
}
}));
Other Service route
router.get('/otherservice', passport.authorize('other-login', {}) , function (req, res) {
console.log(req.account);
res.sendStatus(200);
});
Other Service authentication (from custom Strategy)
ServiceStrategy.prototype.authenticate = function (req) {
var self = this;
var id = req.user.master_id || req.user.id;
Service.find({master_id: id}, function (err, docs){
if (err)
return self.error(err);
function verified(err, data, info) {
if (err) { return self.error(err); }
if (!data) { return self.fail(info); }
self.success(data, info);
}
try {
if (self._passReqToCallback) {
self._verify(req, docs, verified);
} else {
self._verify(docs, verified);
}
} catch (ex) {
return self.error(ex);
}
});};
I found the solution! On the User Model, I added an accounts property to store the data returned on the authorization. Then, on the authorization route, I updated the user with this info, and saved. It wasn't that hard at all.
app.post('/api/login', function (req, res, next) {
passport.authenticate('local-login', function (err, user) {
if (err)
return next(err);
if (!user)
return res.status(400).json({status: 'Invalid Username'});
req.login(user, function (err) {
if (err)
return next(err);
var id = req.user.master_id || req.user.id;
Service.findOne({master_id: id}, function (err, doc) {
if (doc == null)
res.status(200).json({
status: 'User successfully authenticated',
accounts: false
});
else
return next();
});
});
})(req, res, next);
}, passport.authorize('other-login', {}), function (req, res) {
var accounts = req.account;
var user = req.user;
user.accounts = accounts;
user.save(function (err, newUser) {
if (err)
throw err;
res.status(200).json({
status: 'User sucessfully authenticated',
accounts: true
});
})
});