I'm stuck on an error with passport. I am building an api that is using restify. I am using client-session for session and passport with google oauth 2.
I am having trouble at the point it would serialise the user and create a session. I seem to be getting no errors however it is printing out large node objects to the console.
var restify = require('restify');
var massive = require('massive');
var passport = require('passport');
var GoogleStrategy = require('passport-google-oauth2').Strategy;
var _ = require('lodash');
var sessions = require("client-sessions");
var app = function(config, done) {
// declare DB and server
var server = restify.createServer(),
db;
// Set server settings
server.use(restify.bodyParser());
server.pre(restify.pre.sanitizePath());
server.use(restify.queryParser());
server.use(sessions({
// cookie name dictates the key name added to the request object
cookieName: 'Skyrail_session',
// should be a large unguessable string
secret: 'abc123yyighhcggfgucgdguhvgcydtfugjvhfguijkvhgcfgvcfg',
// how long the session will stay valid in ms
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5
}));
server.use(passport.initialize());
server.use(passport.session());
server.use(function logger(req, res, next) {
console.log(new Date(), req.method, req.url);
next();
});
server.on('uncaughtException', function(request, response, route, error) {
console.error(error.stack);
response.send(error);
});
// passport auth settings
passport.use(new GoogleStrategy({
clientID: ID,
clientSecret: SECRET,
callbackURL: 'http://127.0.0.1:8080/oauth2callback',
passReqToCallback: true
},
function(request, accessToken, refreshToken, profile, done) {
var isEmail = false;
for (var i = profile.emails.length - 1; i >= 0; i--) {
if (_.endsWith(profile.emails[i].value, 'test.co.uk')) {
isEmail = true;
}
};
if (isEmail) {
// do user stuff
done(null, profile);
} else {
done(null, profile);
}
}
));
passport.serializeUser(function(user, done) {
console.log('serializing user.');
done(null, user.id);
});
passport.deserializeUser(function(user, done) {
console.log('deserialize user.');
done(null, user.id);
});
// connect to the database
massive.connect({
connectionString: config.postgres.conString
}, function(err, massiveInstance) {
db = massiveInstance;
done();
});
var google = passport.authenticate('google', {
scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
});
var googleCallback = passport.authenticate('google');
var authenticate = passport.authenticate('google', {
successRedirect: '/success',
failureRedirect: '/fail'
})
// setup shceduler
var scheduler = require('node-schedule');
// return the appropriate method and objects.
return {
server: server,
db: db,
authenticate: authenticate,
google: google,
googleCallback: googleCallback,
scheduler: scheduler
}
};
module.exports = app
It all works fine and I end up at google accept and then when it gets to this point.
passport.serializeUser(function(user, done) {
console.log('serializing user.');
done(null, user.id);
});
it logs to the console and then starts throwing out node objects. I am pretty sure it has something to do with setting the cooking.
I am at a bit of loss as how to debug. There is not much information about using passport with restify.
Old question, but see my answer here for an example of how to use client-session with Passport.
TL;DR: set {cookieName: 'session'} (not "Skyrail_session") and serialize the entire user object, not just the ID. You may need to use JSON.stringify() and JSON.parse() if Passport doesn't do this for you.
Related
I'm using the passport library to handle user's login but am currently faced with a problem. When a user is logged in it doesn't take long before they are logged out again which is a bad UX.
Am I missing something to make user's login persistent?
Here is my code below:
Passport.js
// load all the things we need
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var TwitterStrategy = require('passport-twitter').Strategy;
// include the user model
var User = require('../app/model/user');
// include the auth credentials
var configAuth = require('./auth');
require('../app/model/upload');
module.exports = function(passport) {
// 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);
});
});
// code for login (use('local-login', new LocalStategy))
// code for signup (use('local-signup', new LocalStategy))
// code for facebook (use('facebook', new FacebookStrategy))
// =========================================================================
// TWITTER =================================================================
// =========================================================================
passport.use(new TwitterStrategy({
consumerKey: configAuth.twitterAuth.consumerKey,
consumerSecret: configAuth.twitterAuth.consumerSecret,
callbackURL: configAuth.twitterAuth.callbackURL,
userProfileURL: "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true",
includeEmail: true,
profileFields: ['emails']
},
function(token, tokenSecret, profile, done) {
// make the code asynchronous
// User.findOne won't fire until we have all our data back from Twitter
process.nextTick(function() {
User.findOne({
'member.id': profile.id
}, function(err, user) {
// if there is an error, stop everything and return that
// ie an error connecting to the database
if (err)
return done(err);
// if the user is found then log them in
if (user) {
return done(null, user); // user found, return that user
} else {
//declare emails as an array
//var emails = [];
// if there is no user, create them
var newUser = new User();
newUser.member.id = profile.id;
newUser.member.token = profile.token;
newUser.member.username = profile.username;
newUser.member.displayName = profile.displayName;
newUser.member.email = profile.emails[0].value;
newUser.member.medium = 'Twitter';
newUser.member.picture = profile._json.profile_image_url.replace('_original', '');
// save user credentials into the database
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
// res.cookie('username', newUser.twitter.username).send('Hello');
});
}
});
});
}));
};
Some tutorials said I should use passport session which I have used but nothing has changed:
Server.js
//read cookies (needed for auth)
app.use(cookieParser());
// get information from html forms
app.use(bodyParser());
//persistent login
app.use(session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
// persistent login sessions
app.use(passport.session());
There are two different things here.
If the User gets logged out after the server is restarted, you need a "store".
If the User gets logged automatically after some time, even though you didn't restart the server, you need to set an expiry date for the session or the cookie.
You can use the connect middleware:
http://www.senchalabs.org/connect/session.html
.use(connect.session({ secret: 'SUPER SECRET', cookie: { maxAge: 86400000 }}))
This should keep the session valid for 24 hours (set your own number here)
OR Theexpress-session middleware:
https://github.com/expressjs/session
Both have a store concept. In a nutshell, it specifies where the details will be stored. If its in memory (not recommended), you will loose the sessions once the server is restarted.
I've added nodejs passport login to my app and everything worked fine, until I committed changes to production. The issue is pretty wired: user is randomly changes sometimes when I reload the page.
Here is my app.js code:
var mysql = require('promise-mysql');
var passport = require('passport');
var app = express();
app.use(cookieParser())
app.use(session({
secret: 'keyboard cat',
maxAge: 60 * 5,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize());
app.use(passport.session());
mysql.createConnection(dbConfig.connection).then(
function (connection) {
require('./config/passport')(passport, connection); // pass passport for configuration
}
);
Here is what I have in configs/passport.js
var LocalStrategy = require('passport-local').Strategy;
var bcrypt = require('bcrypt-nodejs');
module.exports = function (passport, connection) {
passport.serializeUser(function (user, done) {
done(null, user.name);
});
// used to deserialize the user
passport.deserializeUser(function (name, done) {
connection.query("SELECT * FROM users WHERE name = ? ", [name])
.then(function (rows) {
done(null, rows[0]);
})
.catch(function (err) {
console.log("Error getting user form DB: ", err);
done(err);
});
});
passport.use(
'local-login',
new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function (req, username, password, done) { // callback with email and password from our form
connection.query("SELECT * FROM users WHERE username = ?", [username])
.then(function (rows) {
if (!rows.length) {
done(null, false); // req.flash is the way to set flashdata using connect-flash
}
// if the user is found but the password is wrong
else if (!bcrypt.compareSync(password, rows[0].password)) {
done(null, false); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
}
else {
done(null, rows[0]);
}
})
.catch(function (err) {
console.log("Login Failed: ", err.body);
done(err);
});
})
);
};
And this is what I have in every route file:
router.all('*', function (req, res, next) {
if (req.isAuthenticated()) {
user.init(req.user);
next(); // pass control to the next handler
}
else {
res.redirect('/');
}
});
Does anyone had similar issue? Seems like I've made some simple and stupid error, because google can't find similar issues.
Thanks!
You're executing two different queries:
// passport.deserializeUser()
connection.query("SELECT * FROM users WHERE name = ? ", [name])
// In the Passport verification handler
connection.query("SELECT * FROM users WHERE username = ?", [username])
My guess would be that name isn't unique, and that you want to use username everywhere.
As an aside: you're setting maxAge to 300 milliseconds.
Turns out to be issue with objects storing by node js in memory. I don't fully understand how this is happened, but this is what I found.
I stored req.user in userModel object, and if there are a lot of requests on server, this userModel object sometimes gets messed up with data from different users.
The solution was to directly user req.user everywhere.
I'm using passportjs for the authentication and session. I get the ussername from mysql and the input field from client side but when the done is called on verification, I get done is not a function.
The server.js :
var express = require('express');
var app = express();
var path = require('path');
var bodyParser = require('body-parser');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var cookieParser = require('cookie-parser');
// app.use(app.router);
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(express.static(__dirname+"/staticFolder"));
var mysql = require('mysql');
var connection = mysql.createConnection({
host:'127.0.0.1',
user:'root',
password:'sdf',
database:'abc'
});
connection.connect(function(err){
if(err){
throw err;
}
});
passport.serializeUser(function(user,done){
console.log("serializeUser" + user);
done(null,user.body.username);
})
passport.deserializeUser(function(id, done) {
done(null, user);
});
passport.use(new LocalStrategy({
passReqToCallback : true
},function(username, password, done) {
connection.query("select * from employeedetails where empid = "+username.body.username,function(err,user,w){
if(err)
{
console.log(err+"fml $$$$$$$$$$");
return done(err);
}
if(username.body.password == user[0].password){
console.log(user[0].empid+" login");
return done(null,user[0].empid);
}
else{
return done(null,false,{message: 'Incorrect password'});
console.log(user[0].empid+" fml");
}
});
}));
app.get('/',function(request,response){
response.sendFile(__dirname+"/staticFolder/view/");
})
app.post('/saveEmployeeDetails',function(request,response){
response.end();
})
app.get('/login',function(request,response){ //the file sent when /login is requested
response.sendFile(__dirname+"/staticFolder/view/login.html");
})
app.post('/loginCheck',passport.authenticate('local', {
successRedirect : '/',
failureRedirect : '/login',
failureFlash : true //
}),
function(req, res) {
console.log("hello");
res.send("valid");
res.redirect('/');
});
Can you please refer to the below link which talks about the same error
https://github.com/jaredhanson/passport/issues/421
It says when you remove the (passReqToCallBack: true) options the error does not occur
In your passport.js config file, passport.use(new LocalStrategy) callback
function depending on which strategy you are using you will need a certain number of arguments.I just had to add "req"
as the first argument in mine.
passport.use(new LocalStrategy({
passReqToCallback: true
},
function (req, apikey, done) {
//ADD REQ UP HERE
process.nextTick(function () {
findByApiKey(apikey, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Unknown apikey : ' + apikey
});
}
return done(null, user);
})
});
}));
It would be an issue of missing parameters in the function.
Here is an example source code for passport-openidconnect strategy:
// Using 'passport-openidconnect' strategy
var OpenidConnectStrategy = require('passport-openidconnect');
var strategy = new OpenidConnectStrategy(
{
issuer: process.env.OAUTH_ISSUER,
authorizationURL: process.env.OAUTH_AUTHORIZATION_URL,
tokenURL: process.env.OAUTH_TOKEN_URL,
userInfoURL: process.env.OAUTH_USERINFO_URL,
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_CLIENT_SECRET,
callbackURL: process.env.OAUTH_CALLBACK_URL,
},
// Parameters in the function should be appropriate with the strategy
function (issuer, sub, profile, accessToken, refreshToken, done) {
return done(null, profile);
}
);
I am having trouble understanding why my koa v2 app is 404ing when it receives the callback from my oauth2 provider. I see in the network tab that it is receiving a GET to /oauth/callback with a code query parameter. My routes definitely exist because if I open the page in the browser myself it 500s with an error:
TokenError: The provided authorization grant is invalid, expired,
revoked, does not match the redirection URI used in the authorization
request, or was issued to another client.
Here is my app so far, following the koa-passport-example:
const Koa = require('koa')
const app = new Koa()
// trust proxy
app.proxy = true
// sessions
const convert = require('koa-convert')
const session = require('koa-generic-session')
app.keys = ['your-session-secret']
app.use(convert(session()))
// body parser
const bodyParser = require('koa-bodyparser')
app.use(bodyParser())
// authentication
require('./authn')
const passport = require('koa-passport')
app.use(passport.initialize())
app.use(passport.session())
// routes
const fs = require('fs')
const route = require('koa-route')
app.use(route.get('/logout', function(ctx) {
ctx.logout()
ctx.redirect('/login')
}))
app.use(route.get('/login',
passport.authenticate('oauth2')
))
app.use(route.get('/oauth/callback',
passport.authenticate('oauth2', {
failureRedirect: '/login',
successRedirect: '/'
})
))
// Require authentication for now
app.use(function(ctx, next) {
console.log('auth check', ctx.isAuthenticated())
if (ctx.isAuthenticated()) {
return next()
} else {
ctx.redirect('/login')
}
})
app.use(route.get('/', function(ctx) {
ctx.type = 'html'
ctx.body = fs.createReadStream('views/app.html')
const { token } = ctx.state
const authed = ctx.isAuthenticated()
if (authed) {
console.log('token', token)
}
}))
// start server
const port = process.env.PORT || 3000
app.listen(port, () => console.log('Server listening on', port))
And the authn.js file:
import passport from 'koa-passport'
const user = { id: 1, username: 'dmarr#foo.com' }
passport.serializeUser(function(user, done) {
done(null, user.id)
})
passport.deserializeUser(function(id, done) {
done(null, user)
})
var OAuth2Strategy = require('passport-oauth2').Strategy
passport.use(new OAuth2Strategy({
authorizationURL: 'redacted',
tokenURL: 'https://redacted/token',
clientID: 'redacted',
clientSecret: 'redacted',
callbackURL: "http://localhost:8080/oauth/callback"
},
function(accessToken, refreshToken, profile, done) {
console.log('authed with oauth')
console.log('token', accessToken)
console.log('refresh token', refreshToken)
done(null, user)
// User.findOrCreate({ exampleId: profile.id }, function (err, user) {
// return done(err, user);
// });
// console.log(accessToken)
}
));
Thank you for any help
Well this seems to be working after all. I did have a typo which appears to have been the issue. Keeping the above in case anyone else needs help with oauth2 and koa and passport.
Edit: It turns out I can't seem to access the modified user from the authenticate callback. For example:
function(accessToken, refreshToken, profile, done) {
user.accessToken = accessToken
done(null, user)
})
and in my route handler
const { passport } = ctx.session
const user = passport.user
// user.accessToken is undefined because session.passport.user is the serialized one
I'm trying to set up sessions in a Node app using passport-twitter so I can persist user data, but from what I can tell, I cannot turn on the session support. Also, I made sure the callback function matches the application entry on Twitter Developers. The 500 error the program throws is:
500 Error: OAuthStrategy requires session support. Did you forget app.use(express.session(...))?**
at Strategy.OAuthStrategy.authenticate (/Users/xxxxxxxx/web/Node/on/node_modules/passport-twitter/node_modules/passport-oauth1/lib/strategy.js:120:41)
at Strategy.authenticate (/Users/xxxxxxxx/web/Node/on/node_modules/passport-twitter/lib/strategy.js:85:40)
....
When I check strategy.js at line 120, it says:
if (!req.session) { return this.error(new Error('OAuthStrategy requires session support. Did you forget app.use(express.session(...))?')); }
So, clearly req.session is undefined. However, I think I've done everything I need to to get the session support working. Here is most of my main server js file:
var express = require('express'),
app = express();
var http = require('http'),
https = require('https');
var mongodb = require('mongodb'),
mongoose = require('mongoose');
var passport = require('passport'),
TwitterStrategy = require('passport-twitter').Strategy,
imon = require('./imon'),
routes = require('./routes')(app),
user = require('./models/user'),
group = require('./models/group'),
song = require('./models/song'),
album = require('./models/album'),
activity_item = require('./models/activity_item');
app.set('env', 'development');
console.log("app.get('env') =", app.get('env'));
console.log(app.get('env') === 'development');
// development only
if (app.get('env') === 'development') {
app.set('views', __dirname + '/views');
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('secret'));
app.use(express.cookieSession());
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.errorHandler());
}
// production only
if (app.get('env') === 'production') {
// TODO
}
// all environments
mongoose.connect('mongodb://localhost/test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback () {
console.log('Connected to DB');
});
passport.use(new TwitterStrategy({
consumerKey: "0GOReNaXWXCTpf7OQgrg",
consumerSecret: "0wA8yUBrXz3ivpTHcuBbKp3vGN2ODnOF7iFM9DB48Y",
callbackURL: "http://127.0.0.1:8888/auth/twitter/callback"
},
function(token, tokenSecret, profile, done) {
console.log("THis gets called?");
function sanitizeImgURL(string){
return string.replace("_normal", "");
}
console.log("profile: ", profile);
process.nextTick(function(){
User.find({account: {id: profile.id}}, function(err, user) {
if(err){
return done(err);
}
if(!user){
return done(null, false, { message: 'Incorrect password.' });
}
else if(user){
done(null, user);
}
var newUser = new User(
{account:
{provider: profile.provider,
id: profile.id},
username: profile.username,
displayName: profile.displayName,
email: profile.email,
image: sanitizeImgURL(profile._json.profile_image_url)
});
return done(null, newUser);
});
});
}
));
passport.serializeUser(function(user, done) {
console.log("SERIALIZE: ", user);
done(null, user);
});
passport.deserializeUser(function(obj, done) {
console.log("DESERIALIZE: ", obj);
done(null, obj);
});
/**
* Routes
*/
// Redirect the user to Twitter for authentication. When complete, Twitter
// will redirect the user back to the application at
// /auth/twitter/callback
app.get('/auth/twitter', passport.authenticate('twitter'));
// Twitter 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/twitter/callback',
passport.authenticate('twitter', { successRedirect: '/static/index.html',
failureRedirect: '/static/login.html' }));
... some routes ...
app.listen(8888);
I figured it out; it was 2 things:
1.)connect-redis broke sessions, still don't know why, but removing it fixed my problem after I...
2.)... moved my require routes line, which takes the app object as an argument to after my app.use statements. It makes sense now, I was passing my app object to my routes before I configured it with things like, i dunno, SESSIONS.