I'm using the cookie-session module for Expresss.js to deal with sessions.
I would like the sessions to be renewed on every page load (or ajax call). That's how they usually work anywhere.
The documentation doesn't say a word about it.
I've tried doing this, but without success:
app.use(function(req,res,next ){
req.sessionOptions.maxAge = 20*1000;
return next();
});
I suspect that you are not sending the response cookie to the client. I solved the same problem (using express and cookie-session) with a middleware that sends a cookie containing a different value for each get:
var cookieSession = require('cookie-session')
app.use(cookieSession({
key: cookieKey,
secret: cookieSecret,
maxAge: 1 * 60 * 60 * 1000 // 1 hour (rolling)
})
);
app.get('*', function(req, res, next) {
// To update the session expiration time we need to send the new
// expiration in the response cookie.
// To send again the response cookie to the client we need to
// update the session object.
req.session.fake = Date.now();
next();
});
Indeed, if the session object does not change, cookie-session v. 1.2.0 does not set the Set-Cookie header to send the response cookie to the client.
Related
req.login = (user) => {
const token = createJWTToken({ user });
console.log(token);
res.cookie('jwt', token, {
maxAge: 1000 * 60 * 15, // would expire after 15 minutes
httpOnly: true, // The cookie only accessible by the web server
});
req.user = user;
};
I use express, cookieParser is correctly used, but are never set.
Tried on other browser, and differents options.
Thanks.
req.cookies is an object and you can get cookie by using req.cookies object and if you need to set cookie then use res.cookie function(as you are doing in your code).
Note: Make sure req is a Request object and res is a Response object
I have a server where users sign up by email. I want to allow connection in at most N devices, such as computer, phone and tablet. I want to discourage a user sharing credentials with many others, and so I want to logout all but the N most recent sessions when a user logs in.
I am using NodeJS, MongoDB, and Passport with a custom one-time password (otp) authentication strategy:
The user model file includes:
const mongoose = require('mongoose');
const UserSchema = new Schema({
// ...
});
UserSchema.methods.validateOtp = async function(otp) {
// ...
};
The users' routes file includes:
const express = require('express');
const router = express.Router();
const passport = require('passport');
router.post(
"/login",
passport.authenticate("user-otp", {
successRedirect: "/dashboard",
failureRedirect: "back",
})
);
passport.use('user-otp', new CustomStrategy(
async function(req, done) {
user = await User.findOne({req.body.email});
let check = await user.validateOtp(req.body.otp);
// more logic...
}
));
I found NodeJS logout all user sessions but I could not find the sessions collection in the database, even though I have two active sessions on it.
How can I log the user out of all but the N most recent sessions?
update
After the answer, I realize I left out code related to the session. The main script file includes:
const cookieParser = require('cookie-parser');
const passport = require('passport');
const session = require('cookie-session');
app.use(cookieParser("something secret"));
app.use(
session({
// cookie expiration: 90 days
maxAge: 90 * 24 * 60 * 60 * 1000,
secret: config.secret,
signed: true,
resave: true,
httpOnly: true, // Don't let browser javascript access cookies.
secure: true, // Only use cookies over https.
})
);
app.use(passport.initialize());
app.use(passport.session());
app.use('/', require('./routes/users'));
The module cookie-session stores data on the client and I don't think it can handle logging out all but the last N sessions, since there is no database on the server.
Are you sure you actually have a persistent session store currently? If you are not intentionally leaving out any middleware in your post then I suspect you do not.
The go-to for most development using express is express-session which needs to be added as its own middleware. In its default configuration, express-session will just store all sessions in memory though. Memory storage is not persistent through restarts and is not easy to interact with for any purpose other than storing session information. (like querying sessions by user to delete them)
I suspect what you will want to use is connect-mongodb-session as a session storage mechanism for express-session. This will store your sessions in mongodb in a 'sessions' collection. Here's some boilerplate to help you along.
Please excuse any minor bugs that may exist here, I am writing all of this code here without running any of it, so there could be small issues you need to correct.
const express = require('express');
const passport = require('passport');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const app = express();
const router = express.Router();
// Initialize mongodb session storage
const store = new MongoDBStore({
uri: 'mongodb://localhost:27017/myDatabaseName',
// The 'expires' option specifies how long after the last time this session was used should the session be deleted.
// Effectively this logs out inactive users without really notifying the user. The next time they attempt to
// perform an authenticated action they will get an error. This is currently set to 1 hour (in milliseconds).
// What you ultimately want to set this to will be dependent on what your application actually does.
// Banks might use a 15 minute session, while something like social media might be a full month.
expires: 1000 * 60 * 60,
});
// Initialize and insert session middleware into the app using mongodb session storage
app.use(session({
secret: 'This is a secret that you should securely generate yourself',
cookie: {
// Specifies how long the user's browser should keep their cookie, probably should match session expires
maxAge: 1000 * 60 * 60
},
store: store,
// Boilerplate options, see:
// * https://www.npmjs.com/package/express-session#resave
// * https://www.npmjs.com/package/express-session#saveuninitialized
resave: true,
saveUninitialized: true
}));
// Probably should include any body parser middleware here
app.use(passport.initialize());
app.use(passport.session());
// Should init passport stuff here like your otp strategy
// Routes go here
So after you get cookies and sessions working, the next part is to have routes which are actually protected by your authentication. We're setting this up so that we know for sure that everything is working.
// Middleware to reject users who are not logged in
var isAuthenticated = function(req, res, next) {
if (req.user) {
return next();
}
// Do whatever you want to happen when the user is not logged in, could redirect them to login
// Here's an example of just rejecting them outright
return res.status(401).json({
error: 'Unauthorized'
});
}
// Middleware added to this route makes it protected
router.get('/mySecretRoute', isAuthenticated, (req, res) => {
return res.send('You can only see this if you are logged in!');
});
At this step you should check that if you are not logged in that you can't reach the secret route (should get error), and if you are logged in you can reach it (see the secret message). Logging out is the same as usual: req.logout() in your logout route. Assuming all is well now let's attack the actual issue, logging out everything except the 4 most recent sessions.
Now, for simplicity, I'm going to assume you are enforcing otp on every user. Because of this we can take advantage of the passport otp middleware you declared earlier. If you aren't then you may need do a bit more custom logic with passport.
// Connect to the database to access the `sessions` collection.
// No need to share the connection from the main script `app.js`,
// since you can have multiple connections open to mongodb.
const mongoose = require('mongoose');
const connectRetry = function() {
mongoose.connect('mongodb://localhost:27017/myDatabaseName', {
useUnifiedTopology: true,
useNewUrlParser: true,
useCreateIndex: true,
poolSize: 500,
}, (err) => {
if (err) {
console.error("Mongoose connection error:", err);
setTimeout(connectRetry, 5000);
}
});
}
connectRetry();
passport.use('user-otp', new CustomStrategy(
async function(req, done) {
user = await User.findOne({ req.body.email });
let check = await user.validateOtp(req.body.otp);
// Assuming your logic has decided this user can login
// Query for the sessions using raw mongodb since there's no mongoose model
// This will query for all sessions which have 'session.passport.user' set to the same userid as our current login
// It will ignore the current session id
// It will sort the results by most recently used
// It will skip the first 3 sessions it finds (since this session + 3 existing = 4 total valid sessions)
// It will return only the ids of the found session objects
let existingSessions = await mongoose.connection.db.collection('sessions').find({
'session.passport.user': user._id.toString(),
_id: {
$ne: req.session._id
}
}).sort({ expires: 1}).skip(3).project({ _id: 1 }).toArray();
// Note: .toArray() is necessary to convert the native Mongoose Cursor to an array.
if (existingSessions.length) {
// Anything we found is a session which should be destroyed
await mongoose.connection.db.collection('sessions').deleteMany({
_id: {
$in: existingSessions.map(({ _id }) => _id)
}
});
}
// Done with revoking old sessions, can do more logic or return done
}
));
Now if you login 4 times from different devices, or after clearing cookies each time, you should be able to query in your mongo console and see all 4 sessions. If you login a 5th time, you should see that there are still only 4 sessions and that the oldest was deleted.
Again I'll mention I haven't actually tried to execute any of the code I've written here so I may have missed small things or included typos. Please take a second and try to resolve any issues yourself, but if something still doesn't work let me know.
Tasks left to you:
Your mongo query performance will be sub-par if you do not add an index for session.passport.user to the sessions collection. You should add an index for that field, e.g. run db.sessions.createIndex({"session.passport.user": 1}) on the Mongo shell (see docs). (Note: although passport is a sub-document of the session field, you access it like a Javascript object: session.passport.)
You should probably also logout other sessions if a password reset is
executed.
You should delete the session from the collection when calling req.logout().
To be friendly to the user, you could add a message to the revoked sessions to display when the user tries to access the content from a previous device. The same goes for expired sessions. And you could delete those sessions to keep the collection small.
The module express-session stores a cookie in the user's browser even without logging in. To be compliant with GDPR in Europe, you should add a notice about cookies.
Implementing the change from cookie-session (stored in the client) to express-session will log out all previous users. To be friendly to the user, you should warn them ahead of time and make sure you make all the changes at once, instead of trying multiple times and them getting exasperated at having to log in multiple times.
I am trying to access my session token from other routes after setting it in a route. I am currently unsuccessful. Following the relevant code of the three files.
server.js: It calls the routes thermostats, login and also sets session token.
var session = require('express-session');
var app = express();
app.use(session({secret: 'keyboard cat',cookie: { secure: true }}))
var router = express.Router();
var thermostats = require('./api/routes/thermostats')(router, app, session);
require('./api/routes/login')(router, app, session, thermostats);
login.js: When the user goes to localhost:3000/login/, the login token needs to be saved in the session
module.exports = function(router,app, session, thermostats){
router.get('/login/', function(req, res) {
list(req, res) //response of this function has session which needs to be saved.
console.log(res.session)
app.use(session(res.session)) //trying to save the res.session as session token
});
}
thermostat.js: Needs to access the session token before can display any information.
module.exports = function(router,app){
router.get('/thermostats/', function(req, res) {
console.log(req.session) //Set to default/null values and not the updated session values
});
}
It might be something small but I cannot figure out the issue. I would appreciate any help.
Thank you
Express-session should automatically save the session, based on the configuration.
Looking at the 'resave' config option in the express-session docs:
resave
Forces the session to be saved back to the session store, even if the session was never modified during the request. Depending
on your store this may be necessary, but it can also create race
conditions where a client makes two parallel requests to your server
and changes made to the session in one request may get overwritten
when the other request ends, even if it made no changes (this behavior
also depends on what store you're using).
This is by default, true, so it should already start working without you needing to add app.use(session(res.session).
Edit: You will be able to save to the session by adding fields to the req.session object:
router.get('/login/', function(req, res) {
getDataFromExternalApi(req, function(err, apiResponse) {
var data = apiResponse.data;
req.session.data = data;
// if resave option is true, this should automatically save to the session store after this request is done.
});
});
Generally, you shouldn't be using app.use in your request handlers. Those are generally reserved for setting up the server, as it defines what middleware express uses.
I'm using cookie-parser, all the tutorial talk about how to set cookie and the time it expiries but no where teach us how to get the value of these cookie
For people that stumble across this question, this is how I did it:
You need to install the express cookie-parser middleware as it's no longer packaged with express.
npm install --save cookie-parser
Then set it up as such:
const cookieParser = require("cookie-parser");
const app = express();
app.use(cookieParser());
Then you can access the cookies from
req.cookies
Hope that help.
First note that Cookies are sent to client with a server request and STORED ON THE CLIENT SIDE. Every time the user loads the website back, this cookie is sent with the request.
So you can access the cookie in client side (Eg. in your client side Java script) by using
document.cookie
you can test this in the client side by opening the console of the browser (F12) and type
console.log(document.cookie);
you can access the cookie from the server (in your case, expressjs) side by using
req.cookies
Best practice is to check in the client side whether it stored correctly. Keep in mind that not all the browsers are allowing to store cookies without user permission.
As per your comment, your code should be something like
var express = require('express');
var app = express();
var username ='username';
app.get('/', function(req, res){
res.cookie('user', username, {maxAge: 10800}).send('cookie set');
});
app.listen(3000);
hope this will help you
const app = require('express')();
app.use('/', (req, res) => {
var cookie = getcookie(req);
console.log(cookie);
});
function getcookie(req) {
var cookie = req.headers.cookie;
// user=someone; session=QyhYzXhkTZawIb5qSl3KKyPVN (this is my cookie i get)
return cookie.split('; ');
}
output
['user=someone', 'session=QyhYzXhkTZawIb5qSl3KKyPVN']
Just want to add that we shouldn't be using modules to do trivial stuff. Modules are very convenient and fast forward development, but keep us from learning by creating infrastructural code.
I'm a professor not a boss so I value more programmers knowledge/skill development than to write code in lesser time without learning anything...
Back to business...
Unless you need signed cookies, or anything more complex, it's perfectly possible to write your own middleware to parse cookies and add the values to the rest of the pipeline as the module does.
app.use((req, res, next) => {
const { headers: { cookie } } = req;
if (cookie) {
const values = cookie.split(';').reduce((res, item) => {
const data = item.trim().split('=');
return { ...res, [data[0]]: data[1] };
}, {});
res.locals.cookie = values;
}
else res.locals.cookie = {};
next();
});
Anywhere you need to read the cookie it's available via res.locals.cookie, conveniently formatted as an object.
You could even add a custom cryptography strategy here to make sure no one is reading your cookie.
Just remember middlewares are ordered, so this one has to be added before any other middleware or route that uses the cookie.
In my application, I need to set a cookie using the express framework. I have tried the following code but it's not setting the cookie.
var express = require('express'), http = require('http');
var app = express();
app.configure(function(){
app.use(express.cookieParser());
app.use(express.static(__dirname + '/public'));
app.use(function (req, res) {
var randomNumber=Math.random().toString();
randomNumber=randomNumber.substring(2,randomNumber.length);
res.cookie('cokkieName',randomNumber, { maxAge: 900000, httpOnly: true })
console.log('cookie have created successfully');
});
});
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(5555);
The order in which you use middleware in Express matters: middleware declared earlier will get called first, and if it can handle a request, any middleware declared later will not get called.
If express.static is handling the request, you need to move your middleware up:
// need cookieParser middleware before we can do anything with cookies
app.use(express.cookieParser());
// set a cookie
app.use(function (req, res, next) {
// check if client sent cookie
var cookie = req.cookies.cookieName;
if (cookie === undefined) {
// no: set a new cookie
var randomNumber=Math.random().toString();
randomNumber=randomNumber.substring(2,randomNumber.length);
res.cookie('cookieName',randomNumber, { maxAge: 900000, httpOnly: true });
console.log('cookie created successfully');
} else {
// yes, cookie was already present
console.log('cookie exists', cookie);
}
next(); // <-- important!
});
// let static middleware do its job
app.use(express.static(__dirname + '/public'));
Also, middleware needs to either end a request (by sending back a response), or pass the request to the next middleware. In this case, I've done the latter by calling next() when the cookie has been set.
Update
As of now the cookie parser is a seperate npm package, so instead of using
app.use(express.cookieParser());
you need to install it separately using npm i cookie-parser and then use it as:
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Set Cookie?
res.cookie('cookieName', 'cookieValue')
Read Cookie?
req.cookies
Demo
const express('express')
, cookieParser = require('cookie-parser'); // in order to read cookie sent from client
app.get('/', (req,res)=>{
// read cookies
console.log(req.cookies)
let options = {
maxAge: 1000 * 60 * 15, // would expire after 15 minutes
httpOnly: true, // The cookie only accessible by the web server
signed: true // Indicates if the cookie should be signed
}
// Set cookie
res.cookie('cookieName', 'cookieValue', options) // options is optional
res.send('')
})
Not exactly answering your question, but I came across your question, while looking for an answer to an issue that I had. Maybe it will help somebody else.
My issue was that cookies were set in server response, but were not saved by the browser.
The server response came back with cookies set:
Set-Cookie:my_cookie=HelloWorld; Path=/; Expires=Wed, 15 Mar 2017 15:59:59 GMT
This is how I solved it.
I used fetch in the client-side code. If you do not specify credentials: 'include' in the fetch options, cookies are neither sent to server nor saved by the browser, even though the server response sets cookies.
Example:
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
return fetch('/your/server_endpoint', {
method: 'POST',
mode: 'same-origin',
redirect: 'follow',
credentials: 'include', // Don't forget to specify this if you need cookies
headers: headers,
body: JSON.stringify({
first_name: 'John',
last_name: 'Doe'
})
})
Set a cookie:
res.cookie('cookie', 'monster')
https://expressjs.com/en/4x/api.html#res.cookie
Read a cookie:
(using cookie-parser middleware)
req.cookies['cookie']
https://expressjs.com/en/4x/api.html#req.cookies
Setting cookie in the express is easy
first install cookie-parser
npm install cookie-parser
using middleware
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Set cookie know more
res.cookie('cookieName', '1', { expires: new Date(Date.now() + 900000), httpOnly: true })
Accessing that cookie know more
console.dir(req.cookies.cookieName)
Done!
setting a cookie can be done as such:
res.cookie('cookie name', 'cookie value', [options])
where cookie_name is the name(String) of the cookie you wish to set, for example - "token", and the cookie value is the value(String) you wish to store in the said cookie.
as far as options go, you can read more about them here:
https://expressjs.com/en/api.html
one example of an option is 'maxAge' which indicates how long a cookie is valid, this is used for example when assigning an authentication token and you wish to limit the time a user can stay logged in before having to re-login.
Reading a cookie can be done as such:
req.cookies['cookie name']
which will return the value of the cookie.
Isomorphic Read cookie helper:
function getCookieValue(cookieName = '', cookie = '') {
const matches = cookie.match(`(^|[^;]+)\\s*${cookieName}\\s*=\\s*([^;]+)`)
return matches ? matches.pop() : ''
}
// Node with express:
getCookieValue('cookieName', req.headers.cookie)
// Browser:
getCookieValue('cookieName', document.cookie)
Write in Node with express:
res.cookie('cookieName', 'cookieValue')
Write in the browser:
function setCookie(
cname,
cvalue,
exdays = 100 * 365 /* 100 days */
) {
const now = new Date()
const expireMs = exdays * 24 * 60 * 60 * 1000
now.setTime(now.getTime() + expireMs)
document.cookie = `${cname}=${cvalue};expires=${now.toUTCString()};path=/`
}
// Example of usage
setCookie('cookieName', 'cookieValue')
If you have a problem with setting multiple cookies for one request
Try this way:
res.setHeader('Set-Cookie', [
`accessToken=${accessToken}; HttpOnly; Path=/; Max-Age=${60 * 60}; Secure=True;`,
`refreshToken=${refreshToken}; HttpOnly; Path=/; Max-Age=${60 * 60 * 24 * 7 * 2}; Secure=True;`
]);