how does passport.js check cookies? - node.js

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

Related

Having trouble logging in users using Passport.js, express-session and Youtube oauth

To capture the issue - I am using youtube's data API OAuth for authenticating users and authorizing my app. to keep track of users and their tokens I set up a mongo dB to store sessions as well as app data (users metadata and tokens included). the problem I have is that once a user had authenticated the app, I can see the session data properly stored in the session store, including user id, but when I make consecutive calls to my backend - the user is not logged in.
I'll describe the flow of what I did and then add some code snippets to make it a bit more clear (hopefully):
The client initiates the OAuth flow by redirecting to the https://www.googleapis.com/auth/youtube with all required parameters. once the user had authorized the app - the redirection URI (specified in google API dashboard) redirect the result to my backend.
on my backend, I use express-session and passport js with the passport-youtube-v3 strategy. as mentioned, the login/signup part seems to be working fine. I can see the a new user is registered in my users' collection and the session stored includes that user id. I can also see the session is stored in a cookie on the client. To the best of my understanding - once a consecutive API call had been initiated - the session cookie should be included in the request and passport should authenticate the user. however, when I guard any endpoint with passport.authenticate('youtube') or a simple req.isAuthenticted() check - the request stops there and the next middleware or function is not invoked (meaning the user is not logged in I assume). if I remove these guards (for the sake of debugging) what I am getting on the request object seems like a whole new session altogether. it has a different expiry timestamp, missing user data (or the cookie property).
here is a bit of code to illustrate what I am doing, I am surely doing something wrong - I just can't figure out what...
server.js
const mongoStore = require("connect-mongo")(session);
const sessionStore = new mongoStore({
mongooseConnection: mongoose.connection,
collection: "sessions",
});
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
maxAge: 1000 * 60 * 60 * 24, // 1 day
},
})
);
app.use(passport.initialize());
app.use(passport.session());
// set up routes ...
passport.js
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
console.log("deserializeUser", id);
userModel
.findById(id)
.then((user) => {
done(null, user);
})
.catch((err) => {
done(err);
});
});
passport.use(
new YoutubeV3Strategy(
{
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/youtube/redirect",
scope: ["https://www.googleapis.com/auth/youtube"],
authorizationParams: {
access_type: "offline",
},
},
async function (accessToken, refreshToken, profile, done) {
// console.log("passport verify cb", profile);
let user = await getUser(profile.id);
// console.log("user", user);
if (!user) {
user = await addUser({
youtubeId: profile.id,
name: profile.displayName,
accessToken: accessToken,
refreshToken: refreshToken,
});
}
return done(null, user);
}
)
);
an example for a route called after user authorized the app and is registered in db (both in user collection and a session created in session store):
router.get("/someendpoint", (req, res) => {
console.log("isAuthenticated", req.isAuthenticated()); // false
console.log("session", req.session); // a session object that does not correspond to anything in my session store.
});
As mentioned, if I guard this endpoint with passport.authenticate('youtube') or a simple req.isAuthenticated() check the code doesn't get anywhere past that guard.
some additional info that helps you help me:
my client and server run on localhost but on different ports
the 'some endpoint' endpoint is on a different path than the endpoints I use for managing the OAuth flow.
Your help is much appreciated...

Express creates different session when auth header is changed

I am new to express and I am trying to create session when user logs in to my app.I am using passport for authentication. To login user I am creating basic strategy and on success of basic strategy I am creating a JWT token which I am storing at client side in cookie and I use JWT strategy for subsequent requests.
But I notice that express-session is creating one session when I am logging in which has Basic header and another session for subsequent calls which has JWT header.
Here is my code where I am saving session on login
signin = function(req, res, next) {
passport.authenticate('basic', function(err, user, info) {
if (err) { return next(err) }
if (!user) {
return res.status(401).json({ error: 'message' });
}
var token = jwt.encode({ user: user}, config.db.secret);
res.cookie('token', token, { maxAge: 900000, httpOnly: true, path: '\/', Secure: true });
res.status(200).json({ success: true, firstName: user.firstName });
delete user.password;
req.session.user = user;
req.session.save();
})(req, res, next);
};
But when I debug this code it shows one sessionID in req.sessionID and it show different sessionID in req.sessionID in the following code which as JWT authentication
listProducts = function(req, res) {
debugger;
//here req.session.user is undefined which I have saved at login. and sessionID is also different
res.json({ demo: 'response' });
};
I am expecting it to be same sessionID throughout the life cycle till user logs out of my app.
Why is this exactly happening? What is the solution to it?
You are sending the response before saving the session.
Try saving the session, then sending the response instead.
express-session modifies res.end to make it perform express-session specific tasks introducing the sequential coupling you were victim of: https://github.com/expressjs/session/blob/master/index.js#L249

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);
}));

In passportjs deserialization, store sesssion data in browser cookie

In passportjs serialization and deserialization, how can I access browser cookie (I am storing session data on persistent browser cookie ) instead of storing in database.
If you have configured the passport middleware correctly the passport session data will be passed as parameter to the method passport.deserializeUser.
Be sure that passport has been set up correctly in express:
app.use(express.cookieParser());
app.use(express.session({ secret: 'your secret phrase' }));
app.use(passport.initialize());
app.use(passport.session());
Implement passport.serializeUser and passport.deserializeUser:
// user contains the user data returned by the authentication strategy
passport.serializeUser(function(user, done) {
done(null, user);
});
// obj contains the passport session data
passport.deserializeUser(function(obj, done) {
// Use obj to get user info or include it directly into the request object
done(null, obj);
});
User data will be available in req.user.

Resources