I need some help with setting up Passport Consumer Strategy and integrating it into "locals" (Right now, we have the local strategy working just fine). We have tried several approaches but with no luck on it working 100%. The below code is not the complete code, we have taken out some of it so this post doesn't get too long. Any help with this would greatly be appreciate. There could be compensation as well if someone can get us over this hurdle.
So one question is, if the user is authenticated by the consumer key and secret, how does Passport store the session variables so they are used throughout the site?
Second question, how do we handle the user after it passes the authentication process?
Both local and consumer need to be working.
Consumer key and secret using a POST by a Provider <- I can show some of the post if needed.
This needs to be OAuth1 Only, as of right now, OAuth2 isn't an option.
This is for a single sign-on authentication.
I can supply a consumer session output if needed.
Ultimately, we would like the local strategy and the consumer strategy working with the same "locals" global variables. As far as I can tell, we can authenticate the consumer, retrieve the user from our DB, create a session and can tell if user is "ensureAuthenticated".
Here is what we have working right now.
Local strategy is authenticating correctly.
We render the pages with these local variables:
"Omitted most of the code to save time."
//=================================================================
// The Authentication Module will bind to POST /login route
//=================================================================
authentication.initLocalStrategyRoutes(app);
passport.authenticate('local', {successReturnToOrRedirect: '/', failureRedirect: '/login'});
...
function renderPage(req, res, pageName, pageTitle){
res.render(pageName, {
pageName: pageTitle,
username: req.user ? req.user.username : '',
...
Consumer strategy is authenticating by a POST request from a "Provider"
We have tried adding the Consumer strategy to the authentication.
server.js
//=================================================================
// The Authentication Module will bind to POST /login route
//=================================================================
authentication.initLocalStrategyRoutes(app);
ADDED -> authentication.initConsumerStrategyRoutes(app);
passport.authenticate('local', {successReturnToOrRedirect: '/', failureRedirect: '/login'});
ADDED -> passport.authenticate('consumer', {successReturnToOrRedirect: '/', failureRedirect: '/login'});
authentication.js (omitted code)
module.exports = function(siteConfig, defaultRedirectPage, server, sessionStore, log) {
var passport = require('passport')
...
, ConsumerStrategy = require('passport-http-oauth').ConsumerStrategy
, TokenStrategy = require('passport-http-oauth').TokenStrategy
, LocalStrategy = require('passport-local').Strategy;
var auth = {};
var authenticationRedirects = { successRedirect: '/', failureRedirect: '/login' };
passport.serializeUser(function(user, done) {done(null, user);});
passport.deserializeUser(function(obj, done) {done(null, obj);});
auth.authenticate = function(email, password, callback) {
email = email.toLowerCase();
userController.findUserByUsernameWithPermissions(email,
function(err, user) {
if (err) return callback(err);
if (!user) return callback(null, null, 'Incorrect username.');
bcrypt.compare(password, user.password_hash, function(err, res) {
if(err){return callback(err);
} else if (!res) {return callback(null, null, 'Incorrect password.');
} else {if (user.account_state>0) {callback(null, user);} else {return callback(null, null, '/reset?rand='+user._id);}}
});
}
);
}
auth.initLocalStrategyRoutes = function(app){
passport.use(new LocalStrategy(auth.authenticate));
app.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});
req.logIn(user, function(err) {
if (err) { return next(err); }
res.send(req.user);
});
}) (req, res, next);
});
}
auth.initConsumerStrategyRoutes = function(app){
// passport.use(new LocalStrategy(auth.authenticate));
console.log('app: ', app)
passport.use('consumer', new ConsumerStrategy(
function(key, done) { console.log('starting ConsumerStrategy');
dbConsumerKey.findByConsumerKey({consumerKey: key}, function(err, consumerKey) {
if (err) { return done(err); }
if (!consumerKey) {
var errCode = dbError.find({name:'no_resource_link_id'}, function(err, errorCodes) {
console.log('statusText: ', errorCodes[0]["statusText"]);
return errorCodes[0]["statusText"];
});
return done(null, errCode);
} else {
if (!consumerKey[0]["consumerKey"]) { return done(err); }
if (!consumerKey[0]["consumerSecret"]) { return done(err); }
// return done(null, consumerKey[0]["consumerKey"], consumerKey[0]["consumerSecret"]);
return done(null, consumerKey[0], consumerKey[0]["consumerSecret"]);
}
});
},
function(requestToken, done) {
dbRequestTokens.find(requestToken, function(err, token) {
console.log('inside requestToken');
if (err) { return done(err); }
var info = { verifier: token.verifier,
clientID: token.clientID,
userID: token.userID,
approved: token.approved
}
done(null, token.secret, info);
});
},
function(timestamp, nonce, done) {
done(null, true)
}
));
};
auth.initTokenStrategyRoutes = function(app){}
auth.addUser = function(username, email, password, callback){auth.authenticate(username, "pass", callback);}
return auth;
};
The authentication.js strategy does validate the consumer key and secret. but it doesn't create the session variable we are wanting. We would like the consumer strategy code to be in the authentication.js file.
Now here is another approach, we created a separate files called consumerkey.js
This direction works to a point. We can output the passport session either on the screen or on the command line.
var passport = require('passport')
exports.launchLti = [
passport.authenticate('consumer', { session: false/true [tried both] }),
function(req, res) {
db.findByStudentUserId({lis_person_contact_email_primary:
req.body.lis_person_contact_email_primary}, function(err, user) {
req.logIn(user, function(err) {
req.user.username = user[0].lis_person_contact_email_primary;
...
// req.session.save(function(){
// res.redirect('/classes');
res.redirect(200,'/');
// });
});
})
// res.render('launch', {launch: launch});
}
}]
I solved this issue by changing some of my code structure.
app.pst('/launch/lti/:id', function(req, res, next) {
passport.authenticate('consumer', {failureRedirect: '/login'}),
dbConsumerKey.findByStudentUserId({},
function(err, user) {
if (err) console.log(err, user);
req.logIn(user, function(err) {
if (err) return err;
ADDED -> req.session.valid = true;
ADDED -> res.redirect('/');
});
}
});
});
and modifying the render page function to adapt to the incoming information.
function renderPage(req, res, pageName, pageTitle){
...
this is where the locals are created
...
this allowed me to use my current local strategy as is and adding a totally different strategy route but making the session correctly.
Related
I'm building a Node application in which the users must register or login, then when they drag and drop some elements (the front end is all working) I store on the database their action with their corresponding userId.
My understanding is that once they are registered/logged in, I can use the req.user to access their id and correctly store their actions, however it isn't working.
Here is the section of my server.js file that deals with Passport. Also, I'm using Sequelize as an ORM, but everything dealing with the database works perfect without the req.user part.
app.use(cookieParser());
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
/****** Passport functions ******/
passport.serializeUser(function (user, done) {
console.log('serialized');
done(null, user.idUser);
});
passport.deserializeUser(function (id, done) {
console.log("start of deserialize");
db.user.findOne( { where : { idUser : id } } ).success(function (user) {
console.log("deserialize");
console.log(user);
done(null, user);
}).error(function (err) {
done(err, null);
});
});
//Facebook
passport.use(new FacebookStrategy({
//Information stored on config/auth.js
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL,
profileFields: ['id', 'emails', 'displayName', 'name', 'gender']
}, function (accessToken, refreshToken, profile, done) {
//Using next tick to take advantage of async properties
process.nextTick(function () {
db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
if(err) {
return done(err);
}
if(user) {
return done(null, user);
} else {
//Create the user
db.user.create({
idUser : profile.id,
token : accessToken,
nameUser : profile.displayName,
email : profile.emails[0].value,
sex : profile.gender
});
//Find the user (therefore checking if it was indeed created) and return it
db.user.findOne( { where : { idUser : profile.id } }).then(function (user, err) {
if(user) {
return done(null, user);
} else {
return done(err);
}
});
}
});
});
}));
/* FACEBOOK STRATEGY */
// Redirect the user to Facebook for authentication. When complete,
// Facebook will redirect the user back to the application at
// /auth/facebook/callback//
app.get('/auth/facebook', passport.authenticate('facebook', { scope : ['email']}));
/* FACEBOOK STRATEGY */
// Facebook will redirect the user to this URL after approval. Finish the
// authentication process by attempting to obtain an access token. If
// access was granted, the user will be logged in. Otherwise,
// authentication has failed.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/' }),
function (req, res) {
// Successful authentication, redirect home.
res.redirect('../../app.html');
});
app.get('/', function (req, res) {
res.redirect('/');
});
app.get('/app', isLoggedIn, function (req, res) {
res.redirect('app.html');
});
app.post('/meal', function (req, res) {
//Testing Logs
/*console.log(req.body.foodId);
console.log(req.body.quantity);
console.log(req.body.period);
console.log(req.body);
*/
//Check whether or not this is the first food a user drops on the diet
var dietId = -1;
db.diet.findOne( { where : { userIdUser : req.user.idUser } } ).then(function (diet, err) {
if(err) {
return done(err);
}
if(diet) {
dietId = diet.idDiet;
} else {
db.diet.create( { userIdUser : req.user.idUser }).then(function (diet) {
dietId = diet.idDiet;
});
}
});
db.meal.create({
foodId : req.body.foodId,
quantity : req.body.quantity,
period : req.body.period
}).then(function (meal) {
console.log(meal.mealId);
res.json({ mealId : meal.mealId});
});
});
From what I read on the documentation for Passport, the deserializeUser function that I implemented should be called whenever I use req.user, however, with my console.logs(), I found out that serializeUser is called after logging in, therefore it is storing my session, but deserializeUser is never called! Ever.
Any idea on how to get around this? Any help is appreciated, thank you!
You need the express session middleware before calling passport.session(). Read the passportjs configuration section on documentation for more info.
Make sure to set cookieParser and express-session middlewares, before setting passport.session middleware:
const cookieParser = require('cookie-parser')
const session = require('express-session')
app.use(cookieParser());
app.use(session({ secret: 'secret' }));
app.use(passport.initialize());
app.use(passport.session());
To test if passport session is working or not, use:
console.log(req.session.passport.user)
(put in on a middleware for example)
In my case, i was using LocalStrategy and i was thinking i can protect and endpoint with simple username and password as form parameters, and i though passport will only use form parameters when it can't find user in session. but it was wrong assumption. in passport localStrategy, you should have separate endpoints for login and protected endpoint.
So Make sure you're using right middlewares for each endpoints. in my case:
wrong:
Protected endpoint:
app.get('/onlyformembers', passport.authenticate('local'), (req, res) => {
res.send({"res": "private content here!"})
})
correct :
Login:
app.post('/login', passport.authenticate('local'), (req, res) => {
res.send('ok')
})
Protected endpoint:
var auth = function (req, res, next) {
if (req.isAuthenticated())
return next();
res.status(401).json("not authenticated!");
}
app.get('/onlyformembers', auth, (req, res) => {
res.send({"res": "private content here!"})
})
I am trying to send the info message that gets set in my verify callback:
Here is the example from passport docs:
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
However if I write my route as:
app.post('/login',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
That function does not get called with the info message. At least not that I know of. I know passport shoves the user into the req, as I can access it from req.user. Is there a way to access the info message like this. Or do I need to specify a custom callback?
Which they outline as:
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);
});
The part that is confusing is that they outline using a 3rd parameter in the done callback (from verify) that is a message, yet you can only access that if you write a custom callback. Is that true.
The adopted convention in the Passport framework is that the optional info object will be available through req.authInfo.
This however depends on the used strategy which is responsible for passing it to the Passport framework. For instance, the passport-http strategy does not forward the info object to Passport while the passport-local does.
Here is how you can then access it in a controller protected by the local strategy :
app.post('/login',
passport.authenticate('local'),
function (req, res) {
if (req.authInfo) {
// req.authInfo.message will contain your actual message.
}
});
I'm using passport local strategy like this:
Config:
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, done) {
UserPersistenceModel.findOne({ email: email }, function (err, user) {
if (err) return done(err);
if (!user) return done(null, false);
user.comparePassword(password, function(err, isMatch) {
if(err) return done(err);
if(!isMatch) return done(null, false);
return done(null, user);
});
});
}
));
My router definition looks like this:
var TokenRouter = function(app, passport, tokenSecret) {
//Create
app.post('/', function(req, res, next) {
console.log(req.body);
passport.authenticate('local', function(err, user, info) {
if (err) return next(err);
if (!user) {
console.log('Unsuccessful login!');
return res.status(401).json({ error: 'Login failed!' });
}
req.logIn(user, function(err) {
if (err) return next(err);
console.log('Successful login!');
//user has authenticated correctly thus we create a JWT token
var token = jwt.encode(user, tokenSecret);
res.json({ output : token });
});
})(req, res, next);
});
};
For some reasons I don't fully understand the passport "mechanism". The documentation doesn't look very detailled to me. One question is where the request object (this contains the body which contains the email and the password) is passed to my strategy. How does the strategy gets the email and the password (what's the "source object")?
The reason asking this is that I'd like to use the following inputModel instead of the request being passed to the strategy:
var TokenCreateInputModel = function(req) {
this.email = req.body.email;
this.password = req.body.password;
this.validate();
};
TokenCreateInputModel.prototype = Object.create(InputModel);
TokenCreateInputModel.prototype.validate = function() {
if(!this.email) throw new Error('Email is required!');
if(this.email.indexOf('#') == -1) throw new Error('Emailsyntax is wrong!');
if(!this.password) throw new Error('Password is required!');
};
module.exports = TokenCreateInputModel;
This inputmodel is converting the request and validating the data. I want to use this input model because it fits better to my architecture (I'm using such inputModels in all other cases ... just not with passport (because of the lack of my understanding), which seems to be inconsistent to me.
I'm authenticating my nodeJs app using passport local strategy. Everything is working fine. But how can I show the user appropriate message that he has entered invalid login credentials. My present code is just sending 401 unauthorized error on screen.
Here is my code
passport.use(new LocalStrategy(function(username, password, callback) {
User.findOne({
username : username
}, function(err, user) {
if (err) {
return callback(err);
}
// No user found with that username
if (!user) {
return callback(null, false);
}
// Make sure the password is correct
user.verifyPassword(password, function(err, isMatch) {
if (err) {
return callback(err);
}
// Password did not match
if (!isMatch) {
return callback(null, false);
}
// Success
return callback(null, user);
});
});
}));
exports.isLocalAuthenticated = passport.authenticate('local', {
session : true
});
router.post('/', authController.isLocalAuthenticated, function(req, res) {
//here I want to show the error message to user
});
The documentation has clearly described your case under Custom Callback section.
You need to add custom callback like this:
exports.isLocalAuthenticated = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); } //error exception
// user will be set to false, if not authenticated
if (!user) {
res.status(401).json(info); //info contains the error message
} else {
// if user authenticated maintain the session
req.logIn(user, function() {
// do whatever here on successful login
})
}
})(req, res, next);
}
You don't need to specify the latter callback.
I am using express 4 with passport js to handle authentication of users.
The front end is Angular JS.
I am basically facing two problems:
1- sign in is lengthy, it takes up to 15 seconds to sign in.
2- once logged in, if i restart the node js server and refresh the page I am back to the signin page even though a cookie is set in the browser.
This is what i have in the backend
passport.serializeUser(function (user, done) {
done(null, user._id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
user.password = undefined;
done(err, user);
});
});
passport.use(new LocalStrategy(function (username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: messages['116']});
}
user.comparePassword(password, function (err, isMatch) {
if (err) {
return done(err);
}
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: messages['116']});
}
});
});
}));
app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser('a very hard to guess string'));
app.use(session({
secret: 'a very hard to guess string'
}
));
app.use(passport.initialize());
app.use(passport.session());
The following is the login route:
router.post('/signin', function (req, res, next) {
var result = _.cloneDeep(SwissArmyKnife.resultObjSkel);
var username = req.body.username;
var password = req.body.password;
if (_.isEmpty(username)) {
result.error.reasons.push(SwissArmyKnife.messages['118']);
}
if (_.isEmpty(password)) {
result.error.reasons.push(SwissArmyKnife.messages['119']);
}
if (!_.isEmpty(result.error.reasons)) {
return res.json(200, result);
}
passport.authenticate('local', function (err, user, info) {
if (err) {
return next(err);
}
if (user) {
req.login(user, function (err) {
if (err) {
return next(err);
}
result.result = true;
return res.json(200, result);
})
}
if (info) {
result.error.reasons.push(info.message);
return res.json(200, result);
}
})(req, res, next);
});
when debugging the above code i notice a huge delay when the code reaches
passport.authenticate('local', function (err, user, info) {
what could be the problem???
what am i doing wrong?
Thanks in advance.
The reason you're seeing the sign-in page after restarting your server is because you are using the memory store for your sessions. As you've noticed this only has so much usefulness and you should use some persistent store instead (e.g. redis).