How to properly use express-session on firebase cloud functions - node.js

Hello I am trying to use express sessions in my backend deployed to firebase cloud functions and I trying to use express-sessions to save data related to the current session. but this does not work, I save something using req.session.value = 5 and I try to get that value later and it is undefined.
this is my current express session config.
const session = require('express-session')
const config = require('./config')
const { v4: uuidv4 } = require('uuid');
admin.initializeApp();
const api = require("./api/index");
const app = express();
//app.use(cookieParser());
//app.set('trust proxy',1)
const sess = {
secret: config.secretKey,
genid: function(req){
return uuidv4();
},
resave: true,
saveUninitialized: true,
cookie: {
sameSite: false
}
}
if(app.get('env')==='production'){
app.set('trust proxy',1)
sess.cookie.secure = true;
}
app.use(session(sess));
app.use(cors());
app.use(express.json());
and I have a middleware that sets the value based in some conditions
// mymdileware
module.exports = (req, res,next)=>{
if(/* conditions */ ){
req.session.value = 5
next()
}
// other code here with a res.send()
})
and an endpoint to get the value of the session, and gets executed after the middleware
app.get("/someEndpontToReadSession", mymdileware,(req, res)=>{
if(req.session.value===5){
// do something and return something to the user
}
// other code here with a res.json()
})
the problem is when I read req.session.value it is undefined, even though I set it in the middleware.
this works locally but It does not work when I deployed to firebase functions.
Edit: I thought the problem was related to that I am not providing a store to the session config, so I added it.
but I am still facing this problem : /
const session = require('express-session')
const config = require('./config')
const { v4: uuidv4 } = require('uuid');
const {Firestore} = require('#google-cloud/firestore');
const {FirestoreStore} = require('#google-cloud/connect-firestore');
admin.initializeApp();
const api = require("./api/index");
const app = express();
//app.use(cookieParser());
//app.set('trust proxy',1)
const sess = {
secret: config.secretKey,
genid: function(req){
return uuidv4();
},
resave: false,
saveUninitialized: true,
cookie: {
sameSite: false
}
}
if(app.get('env')==='production'){
console.log('production : )');
app.set('trust proxy',1)
sess.store = new FirestoreStore({
dataset: new Firestore(),
kind: 'express-sessions'
})
sess.cookie.secure = true;
}
app.use(session(sess));

Firebase Cloud functions dont allow severals cookies Firebase Doc for their CDN cache , they allow just the cookie named __session so :
app.use(session({ name: "__session",
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {maxAge: 24 * 60 * 60 * 1000}, // 24 hours
store: sessionStorage}));

Related

Server not functioning in mern heroku deployment

I have a semi successful deployment to heroku but the calls to the server are saying CONNECTION REFUSED and I can't figure it out.
I can hit the route and it returns correctly in postman but not in production
Below is the services file (the ones with /api in front are the ones being called)enter image description here
`import http from "../utils/http-common";
class CountriesService {
getAll() {
return http.get("/api/country");
}
getAllCountries() {
return http.get("/country/getAll");
}
getScroll(skip) {
return http.get(`/country?skip=${skip}`);
}
get(id) {
return http.get(`/country/${id}`);
}
create(countryForm) {
return http.post("/country/new", countryForm);
}
edit(id, values) {
return http.put(`/country/${id}`, values);
}
delete(id) {
return http.delete(`/country/${id}`);
}
}
export const getPostsPage = async (pageParam = 1, options = {}) => {
const response = await http.get(`/api/country?_page=${pageParam}`, options)
return response.data
}
export default new CountriesService();
`
This is the http-common
import axios from "axios";
var url;
process.env.REACT_APP_NODE_ENV === "production"
? (url = "")
: (url = "http://localhost:5000/");
export default axios.create({
baseURL: `${url}`,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
});
this is the server file on the backend with a proxy from the frontend to localhost 5000
const path = require('path');
require("dotenv").config();
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const cors = require("cors");
var morgan = require("morgan");
const MongoDBStore = require('connect-mongo');
const mongoSanitize = require('express-mongo-sanitize');
const helmet = require('helmet');
// Models
const User = require("./models/user.js");
// Passport Config
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const flash = require("connect-flash");
const session = require("express-session");
const cookieParser = require("cookie-parser");
// const { MongoClient } = require("mongodb");
const BodyParser = require("body-parser");
const { storage } = require("./imageupload/cloudinary");
const userAuthRoute = require("./routes/user.js");
const reviewRoute = require("./routes/review.js");
const countryRoute = require("./routes/country.js");
const cityRoute = require("./routes/city.js");
const activityRoute = require("./routes/activity.js");
const restaurantRoute = require("./routes/restaurant.js");
const dishesRoute = require('./routes/dishes.js');
const outdoorsRoute = require('./routes/outdoors.js');
const apiRoutes = require("./routes/api.js");
const cityReviewRoute = require("./routes/cityReviews.js");
const foodRoute = require('./routes/food.js');
const landmarkRoute = require('./routes/landmark.js');
const searchRoute = require('./routes/search.js');
const contactRoute = require('./routes/contact.js');
const db_url = process.env.DB_URL;
const PORT = process.env.PORT || 5000;
const secret = process.env.SESSION_SECRET;
// const client = new MongoClient(process.env.DB_URL);
const corsOptions = {
origin: [
"http://localhost:3000",
"http://localhost:5000",
"https://geopilot.herokuapp.com",],
credentials: true,
optionSuccessStatus:200,
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(mongoSanitize({ replaceWith: "_" }));
app.use(BodyParser.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan("tiny"));
// app.use(cookieParser());
mongoose
.connect(db_url)
.then(() => {
console.log("database connection established successfully");
})
.catch((error) => console.log("this is the error", error));
const store = MongoDBStore.create({
mongoUrl: process.env.DB_URL,
ttl: 24 * 60 * 60 * 365, // 1 year
autoRemove: 'native',
crypto: {
secret,
},
});
store.on('error', function(error) {
console.log('SESSION STORE ERROR:', error);
});
// Session Settings
const sessionOptions = {
name: "geopilot_session",
secret: secret,
store: store,
resave: false,
saveUninitialized: false,
cookie: {
samesite: false,
// httpOnly: true,
// secure: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
maxAge: 1000 * 60 * 60 * 24 * 365,
},
};
// app.set('trust proxy', 1) // trust first proxy
// Session Setup
app.use(session(sessionOptions));
// Helmet Setup
// app.use(helmet())
// Passport Middleware
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Routes
app.use("/api/country/:countryId/reviews", reviewRoute);
app.use("/api/country", countryRoute);
app.use('/api/city', cityRoute);
app.use('/api/city/:cityId/reviews', cityReviewRoute);
app.use('/api/activity', activityRoute);
app.use('/api/restaurant', restaurantRoute);
app.use('/api/restaurant/:restaurantId/reviews', reviewRoute);
app.use('/api/landmark', landmarkRoute);
app.use('/api/landmark/:landmarkId/reviews', reviewRoute);
app.use('/api/food', foodRoute);
app.use('/api/dishes', dishesRoute);
app.use('/api/outdoor', outdoorsRoute);
app.use('/api/search', searchRoute);
app.use('/api/user', userAuthRoute);
app.use('/api/contact', contactRoute);
// ----------- Deployment -----------
__dirname = path.resolve();
if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "../client/build")));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "../client", "build", "index.html"));
});
}
// ----------- Deployment -----------
app.get("*", () => {
res.send("PAGE NOT FOUND");
});
I tried requesting in postman which works.
I tried changing things in the package.json to help proxy or run server.
I tried to switch endpoints and change CORS policy but it won't work.
For anyone seeing this and is stuck, I changed http-common baseUrl to match my website url (not localhost) and then had to do "npm run build" to make it actually implement the changes.

Storing sessions with express-session, connect-mongo, and mongoose

I am looking for guidance on setting up session based authentication with with Express-Session, connect-mongo, and Mongoose. Currently it's just generating a new UUID with every request and not saving anything to the sessions collection. Am I missing something obvious?
index.js
const mongoose = require("./db/connection");
const express = require("express");
const cors = require('cors')
const session = require('express-session')
const MongoStore = require("connect-mongo");
const app = express();
const { v4: uuidv4 } = require('uuid');
//Register .env file
require('dotenv').config()
//Middleware
app.use(express.json());
app.use(session({
genid: (req) => {
return uuidv4()
},
secret: process.env.EXPRESS_SESSION_SECRET,
resave: true,
saveUninitialized: false,
cookie: { maxAge: 24 * 60 * 60 * 1000 },
store: MongoStore.create({
client: mongoose.connection.getClient(),
dbName: process.env.MONGO_DB_NAME,
collectionName: "sessions",
stringify: false,
autoRemove: "interval",
autoRemoveInterval: 1
})
})
);
connection.js
const mongoose = require("mongoose");
require('dotenv').config()
mongoose.connect(`mongodb://devroot:devroot#localhost:27017/${process.env.MONGO_DB_NAME}?authSource=admin`, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
});
mongoose.connection
.on("open", () => console.log("The goose is open"))
.on("close", () => console.log("The goose is closed"))
.on("error", (error) => {
console.log(error);
process.exit();
})
module.exports = mongoose;
The setting saveUninitialized: false means that a session is established only if it contains some information, that is, if a statement like req.session.attribute = "value" is executed during request processing. If that does not happen, the session is not stored, and also no session cookie issued, so that the next request triggers a new session (with a new UUID), but which may again not be stored.
The author probably "solved" the issue by setting saveUninitialized: true, but this has the following consequences:
Every visitor to the website creates a new session entry (without any information in it) in the database even if they never interact with the site nor log on.
Every visitor gets a session cookie in their browser even before actually logging on.
I consider both these consequences undesirable and would therefore prefer saveUninitialized: false so that sessions without information are effectively not created.
Posting for visibility; this was related to:
saveUninitialized: false
Changing this to true forces save to the store.

Openlitespeed Session Timeout after 1 min idle with Nextjs App

Hello Stackoverflow Community.
So I am encountering a very weird problem when hosting my nextjs powered by express with openlitespeed. Everything works great in production, except one thing - the authentification of sessions. The user is saved in the cookies correctly and it works if you are not idle for more than a minute on the page you are on, but if you are idle for more than a minute, then the request is not authenticated anymore even though the cookie is still there.
I am using redis for my cookie store, and everything works in local testing, where openlitespeed is not present. The authentification I am using is passportjs with express-session. Have any of you encountered this problem, and if so, how did you solve it?
I have tried disabling the cache module, set all timeouts to a higher value or disabling them, use different memorystores and more, but no luck. Here is the server.js file, however, I do not believe it has something to do with the code itself, but rather the config of openlitespeed:
const express = require('express')
const next = require('next')
const passport = require('passport');
const redis = require('redis')
const session = require('express-session')
const {v4: uuidv4} = require('uuid');
const path = require('path');
const log = require('./logger')
let RedisStore = require('connect-redis')(session)
let redisClient = redis.createClient()
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = express()
//Json parsing
server.use(express.json());
server.use(express.urlencoded({extended: true}));
if (dev){
//Express session
server.use(session({
store: new RedisStore({ client: redisClient }),
genid: function() {
return uuidv4()},
secret: uuidv4(),
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
maxAge: 86400000
}
}))
}
else{
//Express session
server.use(session({
store: new RedisStore({ client: redisClient }),
genid: function() {
return uuidv4()},
secret: uuidv4(),
proxy: true,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
maxAge: 86400000
}
}))
}
//Passport auth
server.use(passport.initialize());
server.use(passport.session());
//Import of the passport config
const initializePassport = require('./passport-config');
initializePassport(passport);
//Login route
server.post('/login', passport.authenticate('login'), (req, res) => {
res.send({message: 'Successful login', login: true})
});
const passportLogout = function (req, res, next) {
req.logout()
next()
}
//Logout route
server.get('/logout', passportLogout, (req, res) => {
req.session.destroy();
res.redirect('/login');
});
//Import registrerings route. Pga. brugen af route i stedet for app kan vi bruge denne middleware med en anden underside, hvis vi f.eks. ville gøre så admins også kunne lave brugere.
const registerRoute = require('./routes/register-user');
server.use('/register', registerRoute);
//User routes hvor login er required. Rendering. Skal stå under called til initializepassport, ellers kan den ikke finde ud af at den er authenticated via passport, og auth.js returnerer dig derfor til login
const usersRoutes = require('./routes/user/user-routes');
server.use(usersRoutes);
//Admin routes til rendering
const adminRoutes = require('./routes/admin/admin-routes');
server.use(adminRoutes);
const indexRoutes = require('./routes/index-routes');
server.use(indexRoutes);
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
log.logger.log({
level: "info",
message: `Server was started on ${port}`,
additional: "properties",
are: "passed along",
});
console.log(`> Ready on http://localhost:${port}`)
})
})
All right, so I figured it out finally. The configuration for Openlitespeed was set, so that it could create as many httpd workers as it wants. Therefore, when a new was created and the requests went over to that one, it seems the authentification did not stick. I have fixed this by setting the "Number of Workers" to 1 under Server Configuration -> Server Process -> Number of Workers.
As for my server.js file I used to setup nextjs and openlitespeed:
const express = require("express");
const next = require("next");
const passport = require("passport");
const redis = require("redis");
const session = require("express-session");
const { v4: uuidv4 } = require("uuid");
const path = require("path");
const log = require("./logger");
let RedisStore = require("connect-redis")(session);
let redisClient = redis.createClient({ auth_pass: process.env.DB_PASSWORD });
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
//Json parsing
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
if (dev) {
//Express session
server.use(
session({
store: new RedisStore({ client: redisClient }),
genid: function () {
return uuidv4();
},
secret: uuidv4(),
resave: false,
saveUninitialized: false,
cookie: {
secure: false,
maxAge: 86400000,
},
})
);
} else {
//Express session
server.use(
session({
store: new RedisStore({ client: redisClient }),
genid: function () {
return uuidv4();
},
secret: uuidv4(),
proxy: true,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
maxAge: 86400000,
},
})
);
}
//Passport auth
server.use(passport.initialize());
server.use(passport.session());
//Import of the passport config
const initializePassport = require("./passport-config");
initializePassport(passport);
//Login route
server.post("/login", passport.authenticate("login"), (req, res) => {
res.send({ message: "Successful login", login: true });
});
const passportLogout = function (req, res, next) {
req.logout();
next();
};
//Logout route
server.get("/logout", passportLogout, (req, res) => {
req.session.destroy();
res.redirect("/login");
});
//Import registrerings route. Pga. brugen af route i stedet for app kan vi bruge denne middleware med en anden underside, hvis vi f.eks. ville gøre så admins også kunne lave brugere.
const registerRoute = require("./routes/register-user");
server.use("/register", registerRoute);
//User routes hvor login er required. Rendering. Skal stå under called til initializepassport, ellers kan den ikke finde ud af at den er authenticated via passport, og auth.js returnerer dig derfor til login
const usersRoutes = require("./routes/user/user-routes");
server.use(usersRoutes);
//Admin routes til rendering
const adminRoutes = require("./routes/admin/admin-routes");
server.use(adminRoutes);
const indexRoutes = require("./routes/index-routes");
server.use(indexRoutes);
server.all("*", (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on ${port}`);
});
});

Session token isn't storing and isn't viewable from browser

I'm trying to setup a session storage of a userID for an app im working on and I cannot for the life of me get express-session to work.
I've checked out a ton of stack overflow posts, tutorials, and other websites and followed all of the instructions there to no avail. The cookie doesn't even appear in the browser. I've tried changing the order of the .use as well and no other location worked.
Here's the code
const session = require('express-session');
const cookieParser = require('cookie-parser');
const App = require('./app');
var app = new App();
const server = express();
const port = process.env.PORT || 3030;
server.use(cors());
server.use(express.static(path.join(__dirname, buildPath)));
server.use(cookieParser());
server.use(session({
key: 'user_sid',
secret: 'somerandonstuffs',
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 10000,
secure: false,
ttpOnly: false
}
}));
server.use((req, res, next) => {
console.log(req.cookies);
console.log(req.session);
if (req.cookies.user_sid && !req.session.user) {
res.clearCookie('user_sid');
}
next();
});
server.get('/api/userRole', async (req, res, next) => {
try {
const role = await app.userRole(req.query.userID, req.query.email);
res.send({ role });
req.session.user = req.query.userID; //assign
}
catch (error) {
next(error);
}
});
server.get('/api/music', async (req, res, next) => {
try {
console.log(req.session.user) //returns undefined
const uid = req.query.user;
app.checkAuth(uid, app.constants.roles.member);
const music = await app.music(req.query.status);
res.send(music);
}
catch (error) {
next(error);
}
});
And here is the result from the console logs
{}
Session {
cookie:
{ path: '/',
_expires: 2019-07-19T22:01:58.342Z,
originalMaxAge: 10000,
httpOnly: false,
secure: false } }
{}
Session {
cookie:
{ path: '/',
_expires: 2019-07-19T22:01:58.387Z,
originalMaxAge: 10000,
httpOnly: false,
secure: false } }
undefined
All I can seem to get as a response is undefined. Any idea what might be going wrong? Thanks in advance for any help.
You need to set up a storage option for express-session. The easiest one to set up is session-file-store, but I'd recommend using something like connect-redis for a production environment.
You then pass the session storage instance to the express-session options like this:
var session = require('express-session');
var FileStore = require('session-file-store')(session);
var fileStoreOptions = {};
app.use(session({
store: new FileStore(fileStoreOptions),
secret: 'keyboard cat'
}));

set up mssql-session-store in node.js

I'm trying to use mssql-session-store as nodejs (express) store for session:
https://www.npmjs.com/package/mssql-session-store
This is how it should be configured(from npm page):
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false,
store: new MssqlStore(options) // see options below
}));
var options = {
connection: existingConnection,
ttl: 3600,
reapInterval: 3600,
reapCallback: function() { console.log('expired sessions were removed);
}
};
My problem is with the options.connection. It should be "Optional instance of a Connection from mssql".
This connection establishment is an async process (from the npm page):
const sql = require('mssql')
async () => {
try {
await sql.connect('mssql://username:password#localhost/database')
const result = await sql.query`select * from mytable where id =
${value}`
console.dir(result)
} catch (err) {
// ... error checks
}
}
This is how express session is being defined in the nodejs initialization:
app.use(session({
name:<session name>,
key: <session key id>,
resave:false,
saveUninitialized:false,
secure: process.env.NODE_ENV ==="production",
secret:<secret string>,
store: new MssqlStore(options), //This is optional - for use when using sql server as a store
cookie:{
//httpOnly: true,
secure: process.env.NODE_ENV ==="production",
expires: config.expressSession.cookieLifeTime
}
}));
The problem is that establishment of the connection is an async process. I've tried several versions to both use the express-session in the application, but doing so just after the connection has been set up (async).
See my basic code (initialization of node.js - servre.js file):
const express = require('express');
const app = express();
const sql = require('mssql');
const session = require ('express-session');
const MssqlStore = require ('mssql-session-store')(session);
var sqlStore = null;
var store = null;
var mssqlConfig =
{
user: <user>
password: <password>,
server: <server name>
database: <database>,
options: {
encrypt: true // Use this if you're on Windows Azure
}
}
I've tried setting the session in the app in the connetion promise:
var sqlConnection = null;
async function getConnectedConnectionOptions()
{
try
{
sqlConnection = await sql.connect(<connection string>);
return await Promise.resolve(sqlconnection: sqlConnection);
} catch (err)
{
sqlConnection = null;
}
}
getConnectedConnectionOptions(),then(result =>
app.use(session({
name:<session name>,
key: <session key id>,
resave:false,
saveUninitialized:false,
secure: process.env.NODE_ENV ==="production",
secret:<secret string>,
store: new MssqlStore(result) ,
cookie:{
//httpOnly: true,
secure: process.env.NODE_ENV ==="production",
expires: config.expressSession.cookieLifeTime
}
}));
but then there's a scope problem where session is not defined in the global app.
Please support.
this is inside example folder in the mssql-session-store module
var dbConfig = {
server: "localhost\\sqlexpress",
database: "sessiontest",
user: "sa",
password: "atonan"
};
var start = function(callback) {
callback = callback || function() {};
sql.connect(dbConfig, function(err) {
if (err) return callback(err);
var app = express();
app.use(session({
secret: '991E6B44882C4593A46C0DDFCA23E06A',
resave: false,
saveUninitialized: false,
store: new MssqlStore({ reapInterval: 10, ttl: 10 })
}));

Resources