Using passport in an express app. For reasons, the session tokens expire after one hour.
If the user is active when the session expired, the deserialize function "fails", i.e., user is undefined.
passport.deserializeUser(function (id, done) {
const user = sessionManager.userLookup(id);
done(null, user);
});
The trouble is that when user is undefined, then there is no req.user for subsequent middleware. So to the code it simply appears that the user is not signed in, with no breadcrumbs to indicate that the session just expired. The app simply redirects all request from unauthenticated users to /login.
For a user in the middle of a workflow, this experience is sub-optimal.
The expiration can be detected within passport.deserializeUser() like this:
passport.deserializeUser(function (id, done) {
const user = sessionManager.userLookup(id);
const errorInfo = ( expire logic check ) ? 'session has expired' : null;
done(errorInfo, user);
});
I can get the logic check right with the sessionManager. The trouble with this solution is that passport sends the user a 500 Internal Server Error, which is also sub-optimal.
What I would like is for the app to send a flash error saying the session has expired. But passport.deserialize() has no visibility to the req object for calling req.flash().
At this point the only way I can think to resolve the issue is to insert a middleware before passport, where the code would lookup the user in the session manager and call req.flash() if the session has expired. It seems like passport should provide a better way to handle such errors.
Answers would be extra-helpful if they include a link to documentation for passport.deserialize(). The only docs I have found here make no mention of how passport handles errors or if it is possible to configure or override the behavior.
UPDATE
After some reflection, flash is not the best mechanism for reporting the session expiration. The app should instead redirect to a "session expired" page. However, the main question still stands. The call changes from req.flash() to res.redirect(), but neither of these objects is available in passport.deserialize().
you can add req as a first parameter in the function, like this:
(also, recommend you use arrow function)
passport.deserializeUser(async (req, id, done) => {
req.flash('error', {});
(...)
});
Related
I am working on setting up logins with session using Express, express-session, and passport.
In a passport documentation as I go I see this example: http://www.passportjs.org/docs/profile/
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
Also, I was following this tutorial: https://github.com/bradtraversy/node_passport_login
Now according to these examples deserializeUser stores the user information in req.user. However, I see that the whole user is stored which puzzles me, as this also stores the passwords to the object.
Isn't that risky?
Or maybe it is not possible to access req.user from front end?
Assuming your backend server is not compromised, req.user will stay only in your backend, which should be a trusted environment, and will not be sent back to your client via res by default.
Also, anything stored within req will only be available in the request itself, another request will have its own req instance, so data is not shared and should not leak to another request unless purposely made to do so.
However, you should always be staying on the ball, keep in mind to test and make sure all data sent back to your client does not have any sensitive info contained within them (e.g. password, tokens).
If you are not comfortable with storing that in req.user, you can always add a layer of middleware to strip the sensitive info before reaching your controller. This way, routes that use the middleware will not have sensitive info exposed.
In my NodeJS application I use express-session for sessions, express-mysql-session to store the session to MariaDB, Passport for authentication and Sequelize for ORM.
The problem I have right now is that I do not know how to refresh a session of a user whose permissions have been changed by an admin of the application.
I tried something like req.logIn() but this refreshes only the session of the admin who is doing the permission changes.
My code looks like this:
editUser = function (req, res) {
var userData.id = req.body.id;
userdata.access = req.body.access;
models.User.update(userData, {where: {id: userData.id}})
.then(function (affectedRows) {
// User has been updated.
// Changes should be active without having the user to log out and log in again
});
);
}
Has anyone an idea how I can refresh the session of the user whose permissions have been changed by another user?
The express-mysql-session needs a table and few fields configured to store the sessions info.
One of those fields is called expires.
If you set that field to Date.now(), the user should have its session expired.
Update:
After reading your comment and looking through their code, we can see that the data stored in the session row in DB is a serialized JSON.
Since you're able to identify that user's session in the DB, you could also:
read the session's data,
JSON.parse() it,
update the .roles array (the property where you
keep user's role),
JSON.stringify() it and save it back to DB.
I have found a solution for my problem at the following thread PassportJS - Is it possible to change req.user for another user?
I updated my passport.deserializeUser() to make a db request and reload the user. The queried user is then commited to the callback. The function now looks like this:
passport.deserializeUser(function(user, done) {
models.User.findOne({where: {id: user.id}})
.then(function(updatedUser) {
done(null, updatedUser);
}, function(err) {
console.error(err);
done(null, user);
});
});
I am using multiple passport stratergy across my app.
Now, since I am using multiple passport strategy to connect (and not to just sign-in), I decided to Google things on how to do it.
This is where I stumbled upon this code
passport.authenticate('meetup', (err, user, info) => {
if (err) { return next(err); }
if (!user) { return res.redirect(process.env.CLIENT_ADDRESS); }
req.logIn(user, function(err) {
if (err) { return next(err); }
return res.redirect(process.env.CLIENT_ADDRESS);
});
Here I am unable to comprehend what is happening, like for first question, what is if (!user), Does it mean req.user
Second, there is req.logIn()
According to passport docs,
Passport exposes a login() function on req (also aliased as logIn())
that can be used to establish a login session.
and
When the login operation completes, user will be assigned to req.user.
Then what is the difference between using serializer/deserializer when compared with req.login?
Also in the callback, we can always do this
passReqToCallback: true
}, (req, accessToken, refreshToken, params, profile, cb) => {
to get req
To summarize can someone please help me comprehend the above code snippet?
At a high level Passport.js is a middleware that "serializes" a user identity in a request/response header (usually a session cookie). This serializing step means that it's taking the login information that identifies a user and produces a new object that represents the user. Think of this object as a key 🔑 card that only Passport will know how to interpret.
When a user makes additional API requests they pass that same identification header back. Passport auths the request by "deserializing" it to identify what user is making that request.
req.login() is the magic that is generating a session for a user. This session represents how long a login is good for without having to re-authenticate.
Let's take a look at the beginning of your snippet:
passport.authenticate('meetup', (err, user, info) => {
...
if (!user) { return...
In this snippet, passport is being set up as middleware. When a request comes through, passport behind the scenes has already interpreted the request header by deserializing the cookie and determines if it represents a user. If there is not a user or the request header does not represent a user, the request is not authorized.
req.login aliased as req.logIn
Passport exposes a login() function on req (also aliased as logIn()) that can be used to establish a login
session.
When the login operation completes, user will be assigned to req.user
Note: passport.authenticate() middleware invokes req.login() automatically.
Use a lower version of passport for this feature v0.4.1
You can install this with
npm install passport#^0.4.1
Your req.login function should work with that version.
I'm using active directory to authenticate users, so I thought I didn't need to use Passport and that all I would need to do is after the password checks out is to create a global(?) boolean with res.locals to indicate that the user has been authenticated.
I've tried something like this in a controller function:
ad.authenticate(username,password, function(err,auth) {
//some of the things I tried unsuccessfully -- should be true after logged in
res.locals.auth = auth
app.locals.auth = auth //app not defined
})
However, I've discovered that when I call a later function checking if the user is logged in as part of middleware for a diff route, res.locals.auth and app.locals.auth are either false or undefined. I've tried setting both vars in my server.js file at the beg with the code below but that didn't work either...
app.use((req, res, next) => {
app.locals.auth = false;
res.locals.auth = false;
next();
});
So my question is, what var/where should I be saving the authenticated status? Or should I just use passport instead because there's some security concern that I was unaware of? What is the point of the isMemberOf in passport setup example?
https://www.npmjs.com/package/passport-activedirectory
All I want to do is just check user credentials and basically recreate req.isAuthenticated in Passport because I couldn't figure out how to use it because of the isMemberOf.
Usually the server sends back a token containing some useful data (user or session id, expiration date) either by cookies or by JWT (json web token).
Then a client puts the token into every request to the server . The server validates expiration date and handles requests.
Cookies will be put into a request by the browser automatically. JWT should be put into a request by your client code.
I am working on a web app which which allows user logins through Facebook using Passport.js. My code is as follows:
/* Passport.js */
var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;
/* DB */
var User = require('../models/db').User;
exports.passport = passport;
passport.use(new FacebookStrategy(
{
clientID: '<ID>',
clientSecret: '<SECRET>',
callbackURL: 'http://localhost:4242/auth/facebook/callback'
},
function (accessToken, refreshToken, profile, done) {
console.log(profile.provider);
User.findOrCreate({ "provider": profile.provider,"id": profile.id },
function (err, user) { return done(err, user); });
}
));
passport.serializeUser(function(user, done) {
console.log('serialize');
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log('deserialize');
User.findOne({"id": id}, function(err, user) {
done(err, user);
});
});
This code works fine on Firefox; my user authenticates through Facebook and then routes successfully. On Chrome, however, I sometimes get the following error:
FacebookTokenError: This authorization code has been used.
at Strategy.parseErrorResponse (/Users/Code/Web/node_modules/passport-facebook/lib/strategy.js:198:12)
at Strategy.OAuth2Strategy._createOAuthError (/Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/lib/strategy.js:337:16)
at /Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/lib/strategy.js:173:43
at /Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:162:18
at passBackControl (/Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:109:9)
at IncomingMessage.<anonymous> (/Users/Code/Web/node_modules/passport-facebook/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:128:7)
at IncomingMessage.EventEmitter.emit (events.js:117:20)
at _stream_readable.js:910:16
at process._tickCallback (node.js:415:13)
My print statements reveal some rather unexpected behavior, as depicted in the pictures below:
The unfinished URL waiting to be submitted...
...results in print statements in my terminal.
It seems that Chrome attempts to preload the request to Facebook, causing a race condition resulting in an error if the client presses enter at just the right time, as shown below:
I have confirmed the multiple requests with Wireshark. If I wait long enough between autocompletion and submission of the URL (say, 3 seconds), both requests complete without error. The error only occurs if the two requests send just over a second apart. The error is unique to Chrome, as Firefox only sends one request.
Is there anything I can do here? My app surely cannot be the only one which experiences this error when it comes to something as frequent as Facebook authentication. Can I prevent Chrome from preloading somehow? If not, am I resigned to catching the error and just trying to authenticate again?
Bonus question: I seem to be deserializing multiple times for each request. My very first request will print the following:
facebook
serialize
deserialize
Every subsequent successful request prints
deserialize
deserialize
facebook
serialize
deserialize
while unsuccessful request pairs print
deserialize
deserialize
deserialize
deserialize
/* Error */
facebook
serialize
It looks like each request deserializes twice. I read this bug report suggesting a solution, but express.static does come before passport.session in my middleware stack, so that cannot be my problem.
Thanks!
I would leave this as a comment but I don't have the reputation. But Chrome will only prefetch pages when you're typing something into the URL bar, but why would you or a user manually type in /auth/facebook?
One possible solution would be to make the /auth/facebook route only accept POST requests. That would keep Chrome from being able to trigger the route when it tries to preload.
Another possible solution, and I'm not sure how well this would work, would to require a timestamp in the query string, something like /auth/facebook?_t=1406759507255. And only call passport.authenticate('facebook') when the timestamp is close enough to the current time. But I don't think either of these solutions are necessary simply because no one should be typing in that URL at all.