ExpressJS creates a new session on each page change - node.js

I'm trying to implement express and passport sessions like so:
app.use(connect.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
cookie: {
path: "/",
httpOnly: true,
maxAge: null
},
store: redisStoreConnect,
secret: "something",
key: 'pksy.sid'
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser (user, done) ->
done null, user.email
return
passport.deserializeUser (email, done) ->
User.findOne
email: email
, (err, user) ->
done err, user
return
return
If I navigate to a page in my site, a new session is created and stored in redis. If I refresh that page, the session appears to persist. If I navigate to a new page or even if I close the tab and reopen to the same page, a new session is created.
This is especially frustrating since passport only authenticates the session that was generated for my login page. How do I get my sessions to persist across pages?
Update: Thanks #robertklep for reminding me to check what cookie the browser was actually sent back (which I should have done to start). It turns out the browser is sending back the right cookie, and the authentication is working. The sessions are in fact persisting, but for some reason a new session (unused by the browser) get's created with each page request. How can I stop this?

"Oh, you didn't know the browser doesn't send the session cookie with the request for the favicon?" says my roomate the hacker, whose ilk created the need for the following single line of code.
11 hours of debugging later here is what solved the problem:
app.use express.favicon()
Express was treating the favicon like any other resource, while the browser was making a specialized, apparently cookieless request for it. Express assumed this request must have come from a client without a session cookie, so it created a new session for them; a session never to be called upon again. express.favicon() saves the day!

In my case I have to use
app.use(passport.initialize());
app.use(passport.session());
before
app.use(app.router);
Hope this can help.

Related

Can fs.writeFile called in a file routed to express, middlewared by session, delete the session cookie?

I routed a js file to the main js file using express and it's router middleware. Also each path is middlewared by session. After authentication, a get request which is received only when req.session.loggedIn exists, will execute on every request since the login.
But on a put request, it will execute once and then will be blocked by the authentication validator which is a middleware in both requests.
When commenting out the fs.writeFile block the cookie remains as expected, and put request are executed more than once.
Can fs.writeFile mess up cookies?
This is the problematic block:
fs.writeFile('./data/tasks.json', stringed, (err)=>{
if(err){return console.log(err)}
console.log('req.session from tasks put:',req.session)
}, ()=>res.send(`Successfuly added task. `))
This is the validation middleware:
const validator = (req, res, next)=>{
console.log('req.session in validator:',req.session)
if(!req.session.loggedIn){
return res.status(401).send('Please log in.')
}
next()}
This is the session middleware:
app.use(session({
secret: ';klmkljhjkhn;jk',
cookie: {
maxAge: 6000065456468476813541684864764561231,
httpOnly: true,
secure: false,
},
saveUninitialized: true,
resave: true,
}))
This is the routing middleware:
app.use('/tasks', validator, require('./routes/tasks'))
The problem that you describe is not easily exhibited by the code in your repository that you linked to in the comments. I was able to login and add 2 tasks successfully via POST, and then read them back, without losing the session data.
It is possible that you were having some timing-related issue with the 2 callbacks: fs.writeFile() takes 1 callback, so the other callback was never being executed; I was unable to verify this, since the code in git does contain this exact statement. Since express-session saves the session only when the response is sent, and it didn't happen here, it was likely timing out - so it's probable that:
You started a request to POST /tasks - it loaded the empty session (not logged in)
You then logged in, and that response finished
Finally, the first request to POST /tasks ended - and express-session resaved the (empty) session, so it overwrote your logged-in session
This can be avoided if you disable resave: true.
To sum up: fs.writeFile is not causing session problems by itself, but can lead to requests which overwrite session state, especially in the presence of:
callback bugs
very long writes
resave: true
The solution to my problem was disabling nodemon, which kills the server on each file change, which is what fs.writeFile just does. I'm so relieved!

passport-local cookie and session database handling

I am currently trying to setup an oauth service for my current project. Being new to all this, I have read many articles on the subject, but I am still missing some pieces.
My environment is:
servers: node, express, passport
authentication: local strategy (using my own accounts / passwords)
I have a database with user/password ( passwords run through bcrypt)
web interface: react, server accesses through superagent
On my nodejs server, I am using these modules:
express
express-session
express-mysql-session
passport
passport-local
bcrypt
Different parts of the solutions are working: I can create new users, create new sessions, see their content in the express-mysql-session database.
However I am quite confused on the following:
when my web client tries to access protected routes, I don't seem to be getting any cookie in the request. Is it that superagent will not send my passport cookie by default? I read somewhere that in single page apps, jwt might be more appropriate, is that linked to this problem?
despite all I read, I am still confused about deserializeUser. My understanding is that with the passport-local solution, upon access, the web client will send the session cookie, which contains the session Id. Passport will fetch further information concerning this session from database, and then continue to handle the request. Does this session info retrieval happen automatically (in express-mysql-session?)? Or do you have to "manually" do it in deserializeUser (many examples show a User.findById call in there)? If you have to do it "manually", it means that you have to access the express-mysql-session db using another connection than the one this module is using?
to log out, is req.logout() enough to ensure the session is erased from the session db entirely?
Answers I found so far:
One has to add the withCredential method to superagent, to get it to send authentication cookies:
res = await superagent
.get(url)
.withCredentials()
.send();
On the CORS side of things, on the server, the 'credentials' option is required if using the 'cors' npm module, for instance:
app.use(cors({
origin: ['http://localhost:3003'],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
}));
All session information is automatically retrieved by these modules. However, many example show this call going back to the user database to get more information (rights, other info). The goal is to avoid having the same information in two locations (sessions db, and user profiles db), and having these getting out of sync (when an account gets closed etc...)
req.logout() disconnects the session, but the session information sticks around in the database.
The following question put me on the right track: how to delete cookie on logout in express + passport js?. You need to use req.logout, res.session.destroy, and while you're at it res.clearCookie to delete the client cookie:
router.post('/logout/',
(req, res) => {
req.logout();
res.status(200).clearCookie('connect.sid', {
path: '/',
secure: false,
httpOnly: false,
domain: 'place.your.domain.name.here.com',
sameSite: true,
}).end();
req.session.destroy();
},
Session is disconnected, database cleaned, cookie gone.

Delete cookie from user browser after the session ends. Im using Passport-twitter to authenticate the user.

Hello i am building an app that is using passport-twitter to authenticate the user, im able to succesfully login the user using twitter credentials, but i would like for the user when we sign out (destroy the session), so the cookie is also destroy, so everytime the user comes back to the app, he needs to authenticate again. so im guessing my session has to be modified but i dont know how.
app.use(session({
secret: "our-passport-local-strategy-app",
resave: true,
saveUninitialized: true
}));
any help is appreciated
Try to use Passport's official approach for logging out. The request object has a decorator that it can be used. If you are using Express.js 4.x the 'result' object has cookie manipulating decorators as well.
app.get('/logout', function(req, res){
// Destroy the session if any
req.logout();
// Clear the specified cookies
res.clearCookie('your_key');
// Redirect to homepage
res.redirect('/');
});

Node.js Express + Passport + Cookie Sessions n > 1 servers

From the Passport docs:
In a typical web application, the credentials used to authenticate a
user will only be transmitted during the login request. If
authentication succeeds, a session will be established and maintained
via a cookie set in the user's browser.
Each subsequent request will not contain credentials, but rather the
unique cookie that identifies the session. In order to support login
sessions, Passport will serialize and deserialize user instances to
and from the session.
So, being that the authentication is maintained in the client's browser, I should be able to scale the web servers to > 1 (each server uses same database) and have Passport deserialize user from an ID stored in a cookie regardless if the server has seen the user before. If the server has seen the user before but no cookie exists, I would expect Passport to fail the authentication...but I can't even get that far with n > 1 servers.
With 1 server process, Passport works fine:
The server is more or less verbatim of docs on both servers (after much trials w/ connect references and such):
app.configure(function() {
app.use(express.static('public'));
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
});
When a user login is successful, right before I return the HTTP request, I log req.user and req.session:
console.log(req.user);
console.log(req.session);
res.send(200, req.user);
// {"cookie":{
// "originalMaxAge":null,
// "expires":null,
// "httpOnly":true,
// "path":"/"
// },
// "passport":{"user":"ID"}}
When the HTTP request returns, the web app requests / and expects the server(s) to set req.user with ID ID from the cookie passed:
passport.deserializeUser(function(req, user_id, cb) {
console.info('Passport deserializing user: '
.concat(user_id));
console.log(req.user);
console.log(req.session);
// ...
// cb(null, user);
});
With 1 server running, this logs as:
Passport deserializing user: ID
undefined
undefined
Immediately after the callback cb(null, user) and before I return the request, I log req.user and req.session and see what I would expect!
Now, with n > 1 servers running, let's say 2, if the web app requests / from server 2 and expects it to set req.user with ID ID from the cookie passed (originally set by logging into server 1), I don't even see the deserialize function called! When I added logging to the beginning of the HTTP route (after middleware):
console.log(req.user);
console.log(req.session);
I see:
undefined
// {"cookie":{
// "originalMaxAge":null,
// "expires":null,
// "httpOnly":true,
// "path":"/"
// },
// "passport":{}}
...which tells me something is up with the cookie, but what? Both server processes are on Heroku behind the same DNS. I have read various discrepancies about needing some "shared session" store for this...but that's what a cookie is at the client's browser level. Please help! Express is the latest 3.X and Passport is 2.1.
As an alternative to having a server side database, you can store the session data in encrypted cookies, such as those used by Mozilla: https://github.com/mozilla/node-client-sessions. That is more scaleable than any server side database but you have to keep the size of the cookie reasonably small. If I had a bit more time I'd write you a demo but for now I'll just put this down as a question to come back to.
The cookie does not contain session data, it contains just the session ID. Using this ID your Express application will get the related session data from it's session store. And since you are not defining a store when calling app.use(express.session({ secret: 'keyboard cat' })); you are using the built-in memory store which is not shared between processes.
So, install any session store that uses a database, eg. connect-mongo for MongoDB.

Persistent user sessions in node.js using Google Authentication

I followed a tutorial to include Google authentication for my web application using the passport-google-oauth module. The server.js file has the following lines of code:
app.use(express.session({ secret: 'victoriassecret' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
In addition, I find that the application automatically logs the user out after some time. Passport is configured in a separate file that is imported in server.js. Is there some way I can increase the time before the user is logged out, or even better, not log him out until he clicks on the logout button? Also, what is the session secret?
1) You can define the maximum life-time of a session cookie ( and concurrently the time before a user is automatically logged out ) using the maxAge option of the cookie parameter like this :
app.use(expressSession({ cookie: {maxAge: 10000} , secret: 'victoriassecret'}));
According to this maxAge value ( 10000 ) the cookie's maximum life-time will be 10.000ms(10 sec).
(obviously you need a much bigger value than this)
Thus,you can increase the maxAge value in order to suit your needs and make sure user does not get logged out until he decides so, pressing the Logout button.
2) The session secret is a random string used to hash the session with HMAC ( more on HMAC : here) in order to protect the session from being highjacked.

Resources