Passport's serializeUser doesn't respond - node.js

I can't quite figure out how to use Passport's serializeUser function in express/SQL.
Here are my questions:
How does passport.serializeUser know what "user" is? Where should I have specified it?
How does passport.serializeUser know which request object should be bound with a cookie?
My app configuration looks like this:
var passport = require('passport');
app.use(session({ secret: 'Secret',
saveUninitialized: true,
resave: true}));
app.use(passport.initialize());
app.use(passport.session());
I also expose the following code to the app in twitter_auth.js:
// twitter authentication and login
app.get('/auth/twitter', passport.authenticate('twitter'));
// handle callback after twitter has authenticated user
app.get('/auth/twitter/callback',passport.authenticate('twitter',{
successRedirect: '/',
failureRedirect: '/'
}));
// used to serialize user
passport.serializeUser(function(user,done){
done(null,user);
});
// used to deserialize the user
passport.deserializeUser(function(User_ID,done){
connection.query('SELECT * from UsersTable where User_ID = '+User_ID, function(err,rows){
done(err,rows[0]);
});
});
When I try spotting it:
// used to serialize user
passport.serializeUser(function(user,done){
console.log('spotted here!');
done(null,user);
});
I never see anything. What's wrong?

To answer your two questions:
You have to define what 'user' is inside the authentication code.
It should look like this:
passport.use(new TwitterStrategy({
consumerKey:*******,
consumerSecret:******,
callbackURL:'/auth/twitter/callback'
},
function(token, tokenSecret, profile,done){
var user = db.call(retrieve a user) ; //whatever you want it to be
done(null,user);
});
The authentication route was accessed via some request object to begin with, and it's the code there:
done(null,user);
that knows of your req object. From my understanding, that is.
A good source on this is: http://toon.io/understanding-passportjs-authentication-flow/

Related

how does passport.js check cookies?

I am having troubles understanding how passport.js authentication flow works.
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
done(null, id);
});
app.use(
cookieSession({
name: 'session',
maxAge: 60 * 60 * 1000,
keys: [config.COOKIE_KEY_1, config.COOKIE_KEY_2],
})
);
app.use(passport.initialize());
app.use(passport.session());
// this is a middleware for my protected routes
const checkLoggedIn = (req, res, next) => {
const isLoggedIn = req.isAuthenticated() && req.user;
if (!isLoggedIn) {
return res.status(401).json({
error: 'you muse log in!',
});
}
next();
};
I have read a few articles. They say the "user.id" in passport.serialize is stored in req.session & the "id" in passport.deserialize is the same as "user.id".
My questions are:
is "user.id" sent to the browser along with the cookie?
How does passport verify cookie when there is a request to the server to get the id out of it?
Does the client know about "req.session" & can the client access this data?
Thank you very much!
The contents of the session are only known to the server and are always available in req.session. A cookie is sent between server and client which ensures that req.session is always the session that belongs to the client that sent the request req. In other words: When the code on your server accesses req.session, it will always access the session contents of the user who made the current request.
But the session contents are not accessible by the client (and nor is the session cookie if it is configured as httpOnly).
So the answers to your questions are:
No
Don't know details, but passport can access req.session.user.id
No

Passport login and persisting session

Background
I have a MEAN application with CRUD capabilities fully tested with postman. I have been trying to persist login for quite some time now with no luck. I have read and tried the following
passport docs
toon io's blog about login
Express session
Scotch io, node auth made easy
Plus a buch of other light reading materials (Lots of SO questions)
But I have only been able to register and log a user in, not persist login with a session.
My App
Here is a link to the full github repo (if you are looking for the latest changes check develop branch)
My Understanding of Auth/Login
Here is my understanding of user login with code examples from my project and screenshot of postman results as well as console logs.
Passport setup
I have the following auth.js file, it configs passport
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
This gets called in the server file like
//code before
var user = require('./models/user.js');
var auth = require('./modules/auth.js')(app, user);
// code after
Routing for login
In my routes I have the login route as follows
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({
err: info
});
}
req.logIn(user, function(err) {
if (err) {
return res.status(500).json({
err: 'Could not log in user'
});
}
res.status(200).json({
status: 'Login successful!'
});
});
})(req, res, next);
});
This route works as tested with postman. I enter the details 'joe' and 'pass' and get the following response.
When this route is hit we can also see in the console that the user is serialized.
So what next?
This is where I get lost. I have a few questions.
Is the user now in a session on my server?
Should I send the req.session.passport.user back to the client?
Do I need the session ID on all future requests?
Testing the Session
I have a second route setup for testing the session it is as follows
router.get('/checkauth', passport.authenticate('local'), function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
The part passport.authenticate('local') (I thought) is there to test if the user session exists before giving access to the route but I never get a 200 response when I run this, even after a login.
Does this route expect a req.session.passport.user passed in the head or as a data argument on a http request that requires auth?
If I missed anything or am understanding something wrong please tell me, any input is appreciated. Thanks all.
Is the user now in a session on my server?
No, You need to use the express-session middleware before app.use(passport.session()); to actually store the session in memory/database. This middleware is responsible for setting cookies to browsers and converts the cookies sent by browsers into req.session object. PassportJS only uses that object to further deserialize the user.
Should I send the req.session.passport.user back to the client?
If your client expects a user resource upon login, then you should. Otherwise, I don't see any reason to send the user object to the client.
Do I need the session ID on all future requests?
Yes, for all future requests, the session id is required. But if your client is a browser, you don't need to send anything. Browser will store the session id as cookie and will send it for all subsequent requests until the cookie expires. express-session will read that cookie and attach the corresponding session object as req.session.
Testing the Session
passport.authenticate('local') is for authenticating user credentials from POST body. You should use this only for login route.
But to check if the user is authenticated in all other routes, you can check if req.user is defined.
function isAuthenticated = function(req,res,next){
if(req.user)
return next();
else
return res.status(401).json({
error: 'User not authenticated'
})
}
router.get('/checkauth', isAuthenticated, function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
As #hassansin says you need to use a middleware that implement session management. The passport.session() middleware is to connect the passport framework to the session management and do not implement session by itself. You can use the express-session middleware to implement session management. You need to modify your auth.js in the following way
var passport = require('passport');
var session = require('express-session');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(session({secret: 'some secret value, changeme'}));
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
Notice that in this case the session engine is using the in memory store and it didn't work if you scale your application and apply load balancing. When you reach this development state something like the connect-redis session store will be needed.
Also notice that you need to change the secret value used on the session midleware call and use the same value on all application instances.
As per the passport documentation, req.user will be set to the authenticated user. In order for this to work though, you will need the express-session module. You shouldn't need anything else beyond what you already have for passport to work.
As far as testing the session, you can have a middleware function that checks if req.user is set, if it is, we know the user is authenticated, and if it isn't, you can redirect the user.
You could for example have a middleware function that you can use on any routes you want authenticated.
authenticated.js
module.exports = function (req, res, next) {
// if user is authenticated in the session, carry on
if (req.user) {
next();
}
// if they aren't redirect them to the login page
else {
res.redirect('/login');
}
};
controller
var authenticated = require('./authenticated');
router.get('/protectedpage', authenticated, function(req, res, next) {
//Do something here
});
I don't know of a way to check all existing sessions, but Passport is handling the issuing of session ids. Try checking that you have req.user on your test endpoint after logging in

FacebookTokenError: This authorization code has been used

Ok, so this is a common error with many causes. I am trying to modify an existing Node-Passport-Facebook module to have local images from the desktop uploaded to a users Facebook account after they log in. That is my goal. This is the code module I am extending
https://github.com/passport/express-4.x-local-example
which in turn is based on
https://github.com/jaredhanson/passport-facebook
I never get past console.log('ERROR HERE... with an error of "This authorization code has been used."
What's confusing is that the auth code returned is ALWAYS DIFFERENT! so how could it already have been used when I try and exchange it for an access token?
Can anyone offer some suggestions, and or next steps I might try? My hunch is that there is something about Passport.js that is not implemented properly.
So my question is, how would I modify the code below (based on this passport facebook example) https://github.com/passport/express-4.x-facebook-example/blob/master/server.jsto upload an image after logging in?
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-facebook').Strategy;
var CLIENTSECRET ='<client secret>';
var APPID ='<app id>';
// Configure the Facebook strategy for use by Passport.
//
// OAuth 2.0-based strategies require a `verify` function which receives the
// credential (`accessToken`) for accessing the Facebook API on the user's
// behalf, along with the user's profile. The function must invoke `cb`
// with a user object, which will be set at `req.user` in route handlers after
// authentication.
passport.use(new Strategy({
clientID: APPID,
clientSecret: CLIENTSECRET,
callbackURL: 'http://localhost:3000/login/facebook/return',
enableProof: true
//callbackURL: 'http://localhost:3000/login/facebook/return'
},
function(accessToken, refreshToken, profile, cb) {
// In this example, the user's Facebook profile is supplied as the user
// record. In a production-quality application, the Facebook profile should
// be associated with a user record in the application's database, which
// allows for account linking and authentication with other identity
// providers.
cb(null, profile);
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. In a
// production-quality application, this would typically be as simple as
// supplying the user ID when serializing, and querying the user record by ID
// from the database when deserializing. However, due to the fact that this
// example does not have a database, the complete Twitter profile is serialized
// and deserialized.
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
console.log(" ");
console.log("ASSERT passport.deserializeUser being called");
console.log(" ");
cb(null, obj);
});
// Create a new Express application.
var app = express();
// Configure view engine to render EJS templates.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('morgan')('combined'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());
//app.use(passport.session());
// Define routes.
app.get('/',
function(req, res) {
res.render('home', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.get('/login/facebook',
passport.authenticate('facebook'));
app.get('/login/facebook/return',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
//my code changes start here!!
var code = req.query.code;
console.log("1 ASSERT after successful login! code="+code);
if(req.query.error) {
// user might have disallowed the app
return res.send('login-error ' + req.query.error_description);
} else if(!code) {
return res.redirect('/');
}
var options={
host:'graph.facebook.com',
path:'/oauth/access_token?client_id='+APPID+'&code='+code +'&client_secret='+CLIENTSECRET+'&redirect_uri=http://localhost:3000/login/faceboo k/return'
}
var https=require('https');
https.get(options,function(res){
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('ERROR HERE'+chunk);
});
});
console.log("2 ASSERT after successful login!")
//my code changes end here!!
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
res.render('profile', { user: req.user });
});
app.listen(3000);
You don't need to make a request to /oauth/access_token at all (well you do, but passport has already handled it for you). That endpoint is for getting an access token when you don't have one, but you already have an access token here:
passport.use(new Strategy({
clientID: APPID,
clientSecret: CLIENTSECRET,
callbackURL: 'http://localhost:3000/login/facebook/return',
enableProof: true
//callbackURL: 'http://localhost:3000/login/facebook/return'
},
function(accessToken, refreshToken, profile, cb) {
// You have the access token here!
cb(null, profile);
}));
You'll need to save that accessToken some way, so that you can use it later when you make requests to the Graph API. You'll probably want to save it to the user's session, but you can also use a strategy like this: https://stackoverflow.com/a/24474900/772035
If you want the user to grant permission to publish (which you will need them to do, to be able to post to their feeds) you also need to replace every call to passport.authenticate with:
passport.authenticate('facebook', { scope: ['publish_actions'] } );
So that the posting permission is requested when the user first adds your app. Then you'll be able to use the /user/photos endpoint to upload a photo, passing the accessToken that you saved earlier in the query string.
You need to encode your query params.
var qs = {
client_id: APPID,
redirect_uri: 'http://localhost:3000/login/facebook/return',
client_secret: CLIENTSECRET,
code: code,
};
options = {
host:'graph.facebook.com',
path:'/oauth/access_token?' + require('querystring').stringify(qs),
};
I think that's your problem. The code itself looks fine apart from that. You'll want the querystring module to parse the results too by the way.
I solved this error. Follow the progress.
Facebook Log in -> Settings -> Apps -> Logged in with Facebook -> Delete Your apps.
After deleting your apps, Try to login with Facebook button.
you need to define profileFields instead of enableProof: true
passport.use(new FacebookStrategy({
clientID:keys.FacebookAppID,
clientSecret:keys.FacebookAppSecret,
callbackURL:'http://localhost:3000/auth/facebook/callback',
profileFields:['email','name','displayName','photos']
},(accessToken,refreshToken,profile,done)=>{
console.log(profile);
}));

Use Passport.js Behind Corporate Firewall for Facebook Strategy

I have been able to use node.js and passport.js to connect to Facebook using the GitHub project available at: https://github.com/jaredhanson/passport-facebook/tree/master/examples/login.
Here is the what the app.js code is doing:
var express = require('express')
, passport = require('passport')
, util = require('util')
, FacebookStrategy = require('passport-facebook').Strategy
, logger = require('morgan')
, session = require('express-session')
, bodyParser = require("body-parser")
, cookieParser = require("cookie-parser")
, methodOverride = require('method-override');
var FACEBOOK_APP_ID = "--insert-facebook-app-id-here--"
var FACEBOOK_APP_SECRET = "--insert-facebook-app-secret-here--";
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing. However, since this example does not
// have a database of user records, the complete Facebook profile is serialized
// and deserialized.
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
// Use the FacebookStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Facebook
// profile), and invoke a callback with a user object.
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://localhost:3000/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Facebook profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Facebook account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}
));
var app = express();
// configure Express
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(logger());
app.use(cookieParser());
app.use(bodyParser());
app.use(methodOverride());
app.use(session({ secret: 'keyboard cat' }));
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
res.render('index', { user: req.user });
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { user: req.user });
});
// GET /auth/facebook
// Use passport.authenticate() as route middleware to authenticate the
// request. The first step in Facebook authentication will involve
// redirecting the user to facebook.com. After authorization, Facebook will
// redirect the user back to this application at /auth/facebook/callback
app.get('/auth/facebook',
passport.authenticate('facebook'),
function(req, res){
// The request will be redirected to Facebook for authentication, so this
// function will not be called.
});
// GET /auth/facebook/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
app.listen(3000);
// Simple route middleware to ensure user is authenticated.
// Use this route middleware on any resource that needs to be protected. If
// the request is authenticated (typically via a persistent login session),
// the request will proceed. Otherwise, the user will be redirected to the
// login page.
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/login')
}
The code works great if I am just using the internet with no proxy server but if I am behind a corporate firewall then I get the following error:
InternalOAuthError: Failed to obtain access token
at Strategy.OAuth2Strategy._createOAuthError (C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\lib\strategy.js:348:17)
at C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\lib\strategy.js:171:43
at C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:177:18
at ClientRequest.<anonymous> (C:\FacebookExample\passport-facebook\examples\login\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:148:5)
at emitOne (events.js:77:13)
at ClientRequest.emit (events.js:169:7)
at TLSSocket.socketErrorListener (_http_client.js:259:9)
at emitOne (events.js:77:13)
at TLSSocket.emit (events.js:169:7)
at emitErrorNT (net.js:1253:8)
Does anyone know how to setup the code above to go through a corporate proxy server for connectivity? I have tried setting the npm configuration properties of proxy, http-proxy and https-proxy but it does not appear to make a difference when I run this application. Any help you can offer would be greatly appreciated. Thank you.
Adding this code in /node_moduels/oauth/lib/oauth.js would fix the issue temporarily.
var HttpsProxyAgent = require('https-proxy-agent');
if (process.env['https_proxy']) {
httpsProxyAgent = new HttpsProxyAgent(process.env['https_proxy']);
}
Finally, set the httpsProxyAgent to the request options right before _executeRequest gets called like this:
options.agent = httpsProxyAgent
oauth2.js does not take in consideration the proxy environment variables when it performed the requests. Blocking the web app to get authenticated (receive the access token and the github user info)
So I tried with this workarround that let know to the oauth2.js that uses the proxy to communicate.
You usually will find the oauth2.js code that needs to adapt at: your_project/node_modules/oauth/lib/oauth2.js
var querystring= require('querystring'),
crypto= require('crypto'),
https= require('https'),
http= require('http'),
URL= require('url'),
OAuthUtils= require('./_utils');
// line codes to add
var HttpsProxyAgent = require('https-proxy-agent');
let httpsProxyAgent = null
if (process.env['https_proxy']) {
httpsProxyAgent = new HttpsProxyAgent("http://127.0.0.1:1087");
// fill in your proxy agent ip and port
}
....
// line codes to add
options.agent = httpsProxyAgent;
this._executeRequest( http_library, options, post_body, callback );
}
exports.OAuth2.prototype._request= function(method, url, headers, post_body, access_token, callback) {
...
Adding
options.agent = httpsProxyAgent
worked for me. But I added it in $workdir\node_modules\oauth\lib\oauth2.js as method _executeRequest exists only there.
If you have trouble finding _executeRequest I recommend do full search in node_modules\oauth as I found that method in oauth2.js
Also, don't forget that if you use third party API that you should consider them as well. I use vkAPI SDK, which is basically a wrapper over http_library with handy methods.
In that case I would suggest to wrap http_library calls and decorate them for your need.
Good way to start here Anyway to set proxy setting in passportjs?

Error: failed to find request token in session

I found a few issues on the main passport repo, however, I think this primarily pertains to this specific strategy as I'm able to successfully authenticate using the passport-google-oauth strategy.
Error: failed to find request token in session
at Strategy.OAuthStrategy.authenticate (/home/glug/application/node_modules/passport-dropbox/node_modules/passport-oauth/lib/passport-oauth/strategies/oauth.js:124:54)
at attempt (/home/glug/application/node_modules/passport/lib/passport/middleware/authenticate.js:243:16)
at Passport.authenticate (/home/glug/application/node_modules/passport/lib/passport/middleware/authenticate.js:244:7)
at callbacks (/home/glug/application/node_modules/express/lib/router/index.js:161:37)
at param (/home/glug/application/node_modules/express/lib/router/index.js:135:11)
at pass (/home/glug/application/node_modules/express/lib/router/index.js:142:5)
at Router._dispatch (/home/glug/application/node_modules/express/lib/router/index.js:170:5)
at Object.router (/home/glug/application/node_modules/express/lib/router/index.js:33:10)
at Context.next (/home/glug/application/node_modules/express/node_modules/connect/lib/proto.js:190:15)
at Context.actions.pass (/home/glug/application/node_modules/passport/lib/passport/context/http/actions.js:77:8)
I am using redis as the session store, however, even after eliminating that, it's still failing with the identical error message.
var DropboxStrategy = require('passport-dropbox').Strategy;
app.configure(function(){
app.set('port', config.express.port);
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({
// store: new RedisStore({ client: redis}),
secret: config.express.secret
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
passport.serializeUser(function(user, done) {
// console.log('Serializing: ' + JSON.stringify(user));
done(null, user);
});
passport.deserializeUser(function(obj, done) {
// console.log('Deserializing: ' + obj);
done(null, obj);
});
passport.use(new DropboxStrategy({
consumerKey: config.dropbox.key,
consumerSecret: config.dropbox.secret,
callbackURL: config.dropbox.callbackURL
},
function(token, tokenSecret, profile, done) {
// My storage function
return done(null, profile);
}
));
I'm happy to try anything, I've filed an issue on the repo, but I think it may be something I'm doing wrong rather than something wrong with the passport-dropbox repo.
... Sigh. I forgot I changed the subdomain. So, the cookie wasn't readable because the domain name was different.
Hey if someone is still having the issue I have another solution...
add this code :
app.use(passport.session({ secret: 'Shhh.. This is a secret', cookie: { secure: true } }));
just add cookie: { secure: true } and it will work just fine...
I too had this issue and above technique helped me solve this.
To solved this problem in my case, was necessary setup the host file to use custom domain instead localhost or 127.0.0.1..

Resources