I'm trying to get sessions variables working in my Express Node.js project, using the express-session module. I'm not getting any errors, and setting session variables seems to work, however they don't persist across requests. Here's a simplified bit of my code:
server.js
var express = require('express');
var bodyParser = require('body-parser');
var session = require('express-session');
var app = express();
app.use(bodyParser.json());
// Sessions
app.use(session({
secret: config.secret
}));
app.use('/api/projects', require('./controllers/api/projects'));
var server = app.listen(3000, function() {
console.log('Server listening on', 3000)
});
api/projects.js router
var router = require('express').Router()
router.get('/set', function(req, res, next) {
req.session.test = "test";
res.status(200).json({"test":req.session.test});
});
router.get('/current', function(req, res, next) {
res.status(200).json({"test":req.session.test});
})
Setting the variable seems to works...
The /set API call should set a session variable called test. This API call appears to work when I try it with curl:
curl -X GET localhost:3000/api/projects/set --cookie "connect.sid=s%3AyP3a8siRuA-5jDxWH4T03UxNpFd6lfBq.Ha8b8eJxbtW8fAJlbgR9jumfmBpJIXNE6444fOb2Jro"
{"test":"test"}
This is also confirmed in the console log:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
test: 'test' }
...however next time I check it it's not set
When I try to check the variable with my second API call, it appears to be undeclared:
curl -X GET localhost:3000/api/projects/current --cookie "connect.sid=s%3AyP3a8siRuA-5jDxWH4T03UxNpFd6lfBq.Ha8b8eJxbtW8fAJlbgR9jumfmBpJIXNE6444fOb2Jro"
{}
This is confirmed in the console log, the test variable is no longer set:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
How can I ensure my sessions variables persist across requests?
(PS: I've been stuck on this for a while and any small comment on hints or resolution is very welcome, even if you're not sure you've got the answer)
I can see two possible reasons for this.
First, you could've messed up with cookies in curl.
You could ensure that you've set your cookies correctly by checking http response. If it contains Set-Cookie header with a new connect.sid then you did something wrong.
Alternatively, you could use a web browser with native cookies support to guard yourself against such mistakes.
Second, you may've restarted your sever between two requests.
Since you didn't specify any persistent store for your sessions, no data will persist between node.js server restarts.
If you want session data to persist after node.js server stops or restarts, you should consider using some persistent session store (i.e. redis-store).
Related
I have a problem saving data to the Express Session middleware. I am using a Vue.js frontend to communicate with the server which is running at localhost:8080. The server runs on localhost:3002.
I suspect that the interaction between the Vue app and the server may be the source of the problem as I have tried tests with a bare bones Express app that serves HTML as simple template literals and req.session.save() works fine.
Here is my code.
vue.config.js
module.exports = {
"transpileDependencies": [
"vuetify"
],
devServer: {
"https": true
}
}
index.js(Express server)
const corsOptions = {
origin: 'https://localhost:8080', // Have tried with and without these options
credentials: true,
};
app.use(cors(corsOptions));
// Set up app to use session
let sess = {
secret: 'What secret?',
resave: false, // Tried true
saveUninitialized: false, // Tried true
cookie: {secure: process.env.NODE_ENV=="prod",httpOnly: false}, // Tried true
store: MongoStore.create({ mongoUrl: process.env.DB_URL,
ttl: 14 * 24 * 60 * 60 }) // = 14 days. Default
}
app.use(session(sess));
Login route where data is being set to the session.
app.post('/api/login', async (req, res) => {
...
request(options, function (error, response) {
if (error) throw new Error(error)
let fm_res = JSON.parse(response.body)
req.session.FM_TOKEN = fm_res.response.token
req.session.save()
console.log('TOKEN STORED IN SESSION :: ', req.session) // token present in session here
res.json({message: 'Token saved in session...', status: 200})
});
});
Separate route where token is not accessible.
// CHECK AUTH
app.post('/api/token_auth', async (req, res) => {
let authToken = req.session.FM_TOKEN
console.log('TOKEN FROM SESSION : ', authToken) // undefined
....
});
I have researched and tried various solutions suggested here on SO but nothing I have found from any answers has worked. Can anyone point me in the right direction to solve this one as I am out of ideas. Thanks in advance.
UPDATE
I have tested the above endpoints with with Postman and they work, i.e. the /api/token_auth has access to the token saved on the session. So, it appears the problem might be connected to the fact that my frontend is not using https but http to make these calls.
I have tried using httpOnly: false setting in the session.cookie as suggested in this SO answer, but it still doesn't work. I am out of ideas.
Does anyone know how https requirement can be circumvented for development purposes?
I'm making a web app. My back end uses Node.js, Express.js, and specifically, it uses the module express-session to create a session object for session-based authentication in order to persist a user login. I understand that when I use express-session to create a session, a cookie with the session ID is created on the back end and sent to the browser on the front end. I have verified that this cookie sends seamlessly when I use my browser and visit the page the Express app is hosted on (in my case, localhost:3001).
// My Express app's index.js file
// This code seamlessly sends a session ID cookie to the browser when I visit http://localhost:3001
const express = require('express');
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const app = express();
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/GameDB',
collection: 'UserSessions'
});
store.on('error', (err) => {
console.log('Something exploded:' + err);
});
app.use(session({
secret: 'I am stuck, help me please!',
store: store,
saveUninitialized: true,
resave: false,
} ));
app.listen(3001, () => {
console.log('server started on 3001');
})
And I get my cookie just fine. Here's a screenshot in my Chrome developer tools:
However, I want my front end to be a separate app (I'm using React), and I want to use API requests from my front end to access everything on my back end. As the front end and back end are separate apps, they will need to run on different ports. My front end will run on port 3000 and my back end will continue to run on port 3001. And with that in mind, I'll be running localhost:3000 in my browser instead of localhost:3001.
The only problem is, with these changes, the cookie made by express-session no longer gets sent to my browser, even when I do an HTTP POST (via JavaScript fetch()) from my front end to my back end and get a valid response back.
In a nutshell, my question is: how can I have my express-session session ID cookie saved to my browser when I'm using a separate app for the front end?
Here's my front end API request:
fetch('http://localhost:3001/gimmecookie', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ data: "data" })
})
.then(response => response.json() )
.then(response => {
console.log(response)
})
.catch((error) => {
console.error(error);
});
And here's the slightly-modified-from-before index.js file for my Express app:
// My Express app's index.js file that *should* create and
// send a session-id cookie to my React project
const express = require('express');
const cors = require('cors');
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const app = express();
const corsOptions = {
origin: 'http://localhost:3000',
}
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/GameDB',
collection: 'UserSessions'
});
store.on('error', (err) => {
console.log('Something exploded:' + err);
});
app.use(cors(corsOptions));
app.use(session({
secret: 'I am stuck, help me please!',
store: store,
saveUninitialized: true,
resave: false,
}));
app.post('/gimmecookie', (req, res) => {
res.json({ sendsome: "data" })
});
app.listen(3001, () => {
console.log('server started on 3001');
});
Right now, I'm using a hack to send the cookie (creating a cookie in JS and sending it manually), but the hack is getting more and more tiresome as my project gets bigger. What do I need to add to this code to have the cookie send like when I was using just one app? Is express-session just not designed for this? (It seems like it would be, I know it is extremely common to have a separate app for both front end and back end.)
Should I expect the cookie to send automatically if the front end and back end have any sort of handshake? Do I need to mess with the cookie.domain or cookie.sameSite attributes in the express-session initialization object? Do I need to mess with CORS or the fetch() Accept header? Is res.json() the wrong method to use? Is it easier to deal with a real IP and not localhost? I've tried a bunch of things, but I no matter what I do, I can't get that blasted express-session generated session ID cookie on my browser.
It turns out that the problem was with the front end, not the back end, and it was a CORS issue. The express-session code was making the cookie just fine, but the front end couldn't accept it because having the back end hosted on port 3001 made it a cross-origin request (you'll recall the front end was on port 3000), even though both the front and back ends were on the same machine, localhost.
All I had to do was use the proxy field in my package.json file in my React project, like so:
...
},
"proxy": "http://localhost:3001"
}
With the proxy change, I also had to change the fetch() in my React code, from this:
fetch('http://localhost:3001/gimmecookie', {
...
to this:
fetch('/gimmecookie', {
...
as my proxy field was tricking my React project into thinking my back end on a different port was actually on the same origin.
Side note: once I realized this was a front end CORS issue, I toyed around with some other solutions (such as using credentials: include in the fetch() init object), but it quickly became apparent that these solutions had significant drawbacks, until I found the proxy solution. Flippn' CORS!
I´ve a problem by saving something in the session above a NodeJs Script. If I start the script and making a post login like this:
app.post('/login', function(req, res) {
sess = req.session;
sess.key = "SecureKEy";
console.log(sess);
});
I got as rusult that what I want:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
key: 'SecureKEy' }
But if I reload the page with this code the session.key is empty. Just like it´s not saved.
app.get('/', function(req, res) {
sess = req.session;
console.log(sess);
res.sendFile(__dirname+'/wwwroot/index.html');
});
My configuration for the express-session is this:
const session = require('express-session');
app.use(session({
secret: 'importent',
resave: true,
saveUninitialized: true
}));
I´ve rewrite the code like this:
app.post('/login', function(req, res) {
console.log("Before: ");
console.log(sess);
sess = req.session;
sess.key = "SecureKEy";
req.session.save();
console.log("After: ");
console.log(sess);
});
With that it work correctly. But if I would resend the logged in page with res.send the session would be automaticly saved? Is that correct?
express-session auto-save edge cases?
The express-session save(...) method is certainly not triggered for some express response transport methods. It seems to trigger consistently for the frequently encountered ones such as response.send(...), response.json(...) etc.
But same is not the case for the special case transport method such as the express.response.end() method - from my observation at least; and also response.sendFile(...) according to the OP and response.redirect(...) according to posts elsewhere.
To avoid unforeseen issue, pay close attention when applying express-session to requests where special case response transport methods were used. The express-session save(...) method may have to be called directly to persist changes made during those requests. Even then, there is no guarantee that persistence would take place.
For example, there are occasions where setting values to null and/or calling the session.destroy(...) and/or session.regenerate(...) methods have no effect. Those destructed session data basically resurface on the next page refresh. Not even calling the save(...) method or setting the unset option to 'destroy' can remedy that situation.
The express-session readme should include these edge case scenarios in one of its Note sections at the top of the page. It would curb some of the headwinds surrounding its auto-save feature.
My philosophy to this type of thing is: when a package is too quirky for a use-case, either find a more suited package or just source your own solution if possible. Workarounds tend to warp application logic thereby making it error prone and difficult to maintain over time.
I am still quite new to node and am experimenting with session management using express + redis.
My current project requires the user to be redirected to an external site to authenticate. After authentication, the browser is redirected to a callback url that i've specified. I require session data to persist across the redirect.
My implementation is as follows:
User triggers POST call to nodeJS server, where I persist data to session by req.session.foo = 'bar' and then return an auth_url to the browser.
Browser redirects using window.location.replace(auth_url); to external site for authentication
After authentication, browser is redirected to localhost:3000/mycallbackurl which is a route defined below (pardon the poor formatting):
router.get('/mycallback', function(req, res) {
console.log("data at callback: " + req.session.foo);
//do other stuff
});
My problem now is that the console prints: data at callback: undefined at the callback route.
Is this a problem with my redis setup, or am I just missing something here?
So far i've ascertained that redis is working by incrementing a value stored in req.session on each browser refresh and printing it.
My redis setup is as follows:
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
app.use(cookieParser());
app.use(session({ store: new RedisStore({
host: 'localhost',
port: 6379,
db: 1,
cookie: { maxAge: (24*3600*1000*30)},
}), secret: 'keyboard cat',
saveUninitialized: true,
resave: true
}));
Thanks for reading and look forward to your replies!
After a redirect (res.redirect), the res and req objects are recreated. That means if you added any parameters to these objects, you need to be sure they are re-added to the req or res objects on each request.
I'm trying to set up a basic session system in node. Here's what I've got so far:
app.js:
app.use(express.cookieParser('stackoverflow'));
app.use(express.session());
I'm setting the session data in ajax.js:
addClassToCart: function(req, res) {
req.session.cart = req.body.classId;
console.log(req.session.cart);
}
This logs the correct information. However, when I try to retrieve that information elsewhere (same file, different function):
console.log(req.session.cart);
I get undefined. I feel like I'm missing something incredibly basic. Various tutorials for this are either awful or require me to add in even more packages (something I'm trying to avoid).
More data from my debugging:
This works with non-AJAX requests
The session is set before it's logged.
As it turns out, the issue wasn't with Express' session (as the other answers seem to think). Rather, it was a misunderstanding on my part. I changed addClassToCart to the following:
addClassToCart: function(req, res) {
req.session.cart = req.body.classId;
console.log(req.session.cart);
res.send('class added');
}
Adding res.send() fixed the problem.
As noted in the answer to a related SO question, this can also occur if you're using fetch to get data from your server but you don't pass in the credentials option:
fetch('/addclasstocart', {
method: 'POST',
credentials: 'same-origin' // <- this is mandatory to deal with cookies
})
Cookies won't be passed to the server unless you include this option which means the request's session object will be reset with each new call.
I don't know about basic session store, but redis only took me a few minutes to setup:
app.use(express.session({
store: new RedisStore({
host: cfg.redis.host,
db: cfg.redis.db
}),
secret: 'poopy pants'
}));
On a mac:
brew install redis
app.use(express.session({
secret: "my secret",
store: new RedisStore,
cookie: { secure: false, maxAge:86400000 }
}));
Not sure the problem is in session age, but it just to be safe, I'd suggest you to specify maxAge.