Openlitespeed Session Timeout after 1 min idle with Nextjs App - node.js

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}`);
});
});

Related

req.cookies and req.signedcookies are empty

I'm storing cookies on my server, but when I tried to access them. Object Returns Null.
code I'm using to store my cookies. This is done when I'm logging in!
res.cookie("accessToken", accessToken, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + oneDay),
});
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + oneDay),
});
index.ts
const dotenv = require("dotenv");
dotenv.config();
const PORT = process.env.PORT || 3001;
const cookies = require("cookie-parser");
const express = require("express");
const app = express();
const cors = require("cors");
app.use(cors());
app.use(express.json());
app.use(cookies());
import dbConnect from "./db/connect";
import adminAuthRoter from "./routes/admin/adminAuthRouter";
app.get("/", (req: any, res: any) => {
res.send("Hello World");
});
app.use("/api/v1/adminauth", adminAuthRoter);
const start = async () => {
try {
await dbConnect(process.env.MONGODB_URI);
app.listen(PORT, () =>
console.log(`Server is listening on port ${PORT}...`)
);
} catch (error) {
console.log(error);
}
};
start();
When I tried to console.log(req.cookies) or console.log(req.signedCookies) my response is empty. But when I see my Postmon cookies there are cookies stored
Postmon Cookie Reponse Image
What may be the issue here?

PassportJS session not working in subsequent request in express

I am making google authentication using PassportJS and I came into a problem where I cannot see any value in req.user object in the subsequent requests.
GoogleStrategy
const express = require("express");
const dotenv = require("dotenv");
const passport = require("passport");
dotenv.config();
const GoogleStrategy = require("passport-google-oauth20").Strategy;
passport.use(
new GoogleStrategy(
{
clientID: process.env.client_id,
clientSecret: process.env.client_secret,
callbackURL: "/api/google/callback",
},
function (accessToken, refreshToken, profile, done) {
done(null, profile);
console.log(profile.id);
}
)
);
passport.serializeUser((user, done) => {
done(null, user);
console.log("serialize");
console.log(user);
});
passport.deserializeUser((user, done) => {
done(null, user);
console.log("deserialize");
console.log(user);
});
server.js
const express = require("express");
const { db } = require("../database/db.js");
const dotenv = require("dotenv");
const passport = require("passport");
const passportsetup = require("./middleware/facebookMiddleware");
const userLoginRoutes = require("./routes/userRoutes");
const facebookRoutes = require("./routes/facebookRoutes");
const cors = require("cors");
const app = express();
const session = require("express-session");
const cookieSession = require("cookie-session");
const PORT = process.env.PORT || 3001;
app.use(express.json());
app.use(
session({
secret: "secret-key",
resave: false,
saveUninitialized: false,
name: "usersession",
cookie: {
maxAge: 24 * 60 * 60 * 100,
secure: false,
},
})
);
// app.use(
// cookieSession({
// name: "session",
// keys: ["amaaa"],
// maxAge: 24 * 60 * 60 * 100,
// })
// );
app.use(
cors({
origin: "http://localhost:3000",
methods: "GET,POST,PUT,DELETE",
credentials: true,
})
);
// initializing passport
app.use(passport.initialize());
// for persistent logins
app.use(passport.session());
// userRoutes
app.use("/api/users", userLoginRoutes);
app.use("/api/google", facebookRoutes);
app.get("/api", (req, res) => {
res.send("Api is running");
});
app.listen(PORT, console.log("server is running"));
routes
const express = require("express");
const passport = require("passport");
const router = express.Router();
const CLIENT_URL = "http://localhost:3000/signup";
router.route("/login/success").get((req, res) => {
console.log("hiiiiiiiiiiii raaaaaan");
console.log(req.user);
if (req.user) {
res.status(200).json({
success: true,
message: "successfull",
user: req.user,
// cookies: req.cookies
});
console.log(req.user);
}
});
router.route("/login/failed").get((req, res) => {
res.status(401).json({
success: false,
message: "failure",
});
});
router.route("/logout").get((req, res) => {
req.logout();
res.redirect(CLIENT_URL);
});
router
.route("/auth")
.get(passport.authenticate("google", { scope: ["profile"] }));
router.route("/callback").get(
passport.authenticate("google", {
successRedirect: CLIENT_URL,
failureRedirect: "/login/failed",
})
);
module.exports = router;
After logging in with google, I get the profile of the user but when I hit api/google/login/success I expect to get value in req.user but it gives null value. Is there anything wrong with my implementation?
And the fact that I am not checking the database while deserializing is that I just want the current value and I do not need everytime to check database.
Please correct me if I am using serialize and deserialize in wrong way.
Thanks.

Express-session mongodb session not persisting

I am new to sessions and this is my first serious coding project. I've tried a million things and I can't figure out why the session isn't persisting through different routes. When I log req.session.id from two routes, it gives me two different ids when it should be the same one.
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const session = require('express-session');
const bcrypt = require('bcrypt');
let User = require('./models/user.model');
const MongoStore = require('connect-mongo')(session);
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
app.set('trustproxy', true);
app.use(cors(corsOptions));
app.use(cors());
app.use(express.json());
const uri = process.env.ATLAS_URI;
mongoose.connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true });
const connection = mongoose.connection;
connection.once('open', () => {
console.log("MongoDB database connection established successfully")
})
app.listen(port, () => {
console.log(`Server is running on port: ${port}`);
});
const SESS_NAME = 'session';
const SESS_SECRET = 'youshouldchangethis'
const IN_PROD = false;
app.use(session({
name: SESS_NAME,
resave: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
saveUninitialized: true,
secret: SESS_SECRET,
proxy: true,
cookie: {
sameSite: true,
secure: IN_PROD,
}
}));
The login route creates the session (the session is stored inside Mongodb just fine at this point), then react redirects to dashboard, where req.session comes back undefined and I'm unable to access that session. Here's the login and dashboard routes. Any help is greatly appreciated!
app.post('/login', (req, res) => {
console.log("in login post");
User.findOne({ email: req.body.email })
.then(user => {
bcrypt.compare(req.body.password, user.password, function (err, result) {
if(result == true){
req.session.userId = user._id;
console.log(req.session.id);
res.send(user);
} else {
res.send('Authentication Failed');
}
})
})
.catch(err => res.send('Authentication Failed'));
});
// Dashboard Router
app.get('/dashboard', (req, res) => {
if (req.session.userId){
User.findOne({ email: req.session.user.email }) // FIX: not able to find session / user
.then(user => {
console.log("found user")
// req.session.user = user;
res.send(user);
})
.catch(err => {
//req.session.reset();
res.send(err);
})
} else {
console.log(req.session.id);
}
});
I found this in express-session docs, maybe this is what you are looking for :
Session.save(callback)
....
There are some cases where it is useful to call this method, for example, redirects, long-lived requests or in WebSockets.
req.session.save(function(err) {
// session saved
})

Nuxt-edge serverMiddleware route ignored (Error 404) when deployed

I'm trying to deplay an SSR nuxt-legacy-edge project on a firebase Hosting.
Every things work fine in dev mode (npm run dev).
I'm using serverMiddleware /api defined in nuxtconfig.js
serverMiddleware: [
bodyParser.json(),
session({
secret: 'amdskfmdlkfdklfndfmdfndsmfndfnejnjheheuewytwgssa',
resave: true,
saveUninitialized: false,
cookie: { maxAge: (20 * 60 * 1000), secure: false }
}),
//{ path: '/api', handler: '~/api/index.js' },
'~/api'
]
and my /api/index.js is
const express = require('express')
const bodyParser = require('body-parser');
const router = express.Router()
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ extended: false }));
router.use((req, res, next) => {
Object.setPrototypeOf(req, app.request)
Object.setPrototypeOf(res, app.response)
req.res = res
res.req = req
next()
})
router.post('/confirmlogin', (req, res) => {
console.log('[API] - LOGIN POST', req.body.role)
req.session.userId = req.body.uid
req.session.role = req.body.role
req.session.token = req.body.token
req.session.profile=req.body.profile
res.cookie('access_token', req.body.token, {
maxAge: 3600000,
httpOnly: true
}) /* 1 hour */
return res.json({
status: 'success'
})
})
router.post('/confirmlogout', (req, res) => {
console.log('[API] - LOGOUT POST')
delete req.session.userId
delete req.session.role
delete req.session.token
delete req.session.profile
res.clearCookie('access_token');
return res.json({
status: 'success'
})
})
app.use('/', router);
module.exports = {
path: '/api',
handler: router
}
The firebase functions is defined as:
const functions = require('firebase-functions');
const { Nuxt } = require('nuxt-legacy-edge');
var express =require("express");
var app=express();
const config = {
dev: false,
buildDir: '.nuxt',
};
const nuxt = new Nuxt(config);
exports.webaffitto = functions.https.onRequest((req, res) => nuxt.render(req, res));
As shown in route api/confirmlogin and api/confirmlogout works fine in dev mode but if put in production using command
firebase serve --only functions,hosting or
using
firebase deploy
seem those routes are missing and I obtain a 404 (non found).
Help is appreciated.

React App with Express Backend & express-session

I currently have a React App (via create-react-app) and an Express server for my API calls. They are both running separately and have been working fine until I ran into this problem:
I'm also using express-session and passport for user authentication. I'm able to authenticate users but the session doesn't persist between API calls. If I successfully login, the next time I make an API call, req.sessionID will be different, and req.isAuthenticated() will return false.
Here is my server code:
'use strict'
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var mongoose = require('mongoose');
var passport = require('passport');
var session = require('express-session');
var cors = require('cors');
var flash = require('connect-flash');
var store = require('./store');
var database = require('./database');
var app = express();
var port = process.env.port || 3001;
var router = express.Router();
var promise = mongoose.connect('mongodb://localhost:27017/lifeless-db', {useMongoClient: true});
//app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser('lifeless-secret-01890'));
app.use(session({
secret: 'lifeless-secret-01890',
saveUninitialized: true,
cookie: {
secure: false,
}
}));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
// Initialize Passport
var authinit = require('./auth/init');
authinit(passport);
// For debugging:
app.use(function(req, res, next) {
console.log("SessionID: " + req.sessionID);
console.log(req.isAuthenticated() ? "This user is logged in" : "This user is NOT logged in");
next();
});
// GET
app.get('/items', function(req, res) {
store.getAllItems((items) => {
if(!items) return res.send({error: true, message: 'Error loading store :/', items: null});
else return res.send({error: false, message: 'Success', items: items});
});
});
app.get('/login', function(req, res) {
console.log(req.flash());
console.log("Login fail");
res.send({error: true, message: 'Unknown error'});
});
// POST
app.post('/message', function(req, res) {
database.submitMessage(req.body.email, req.body.message, (success) => {
if (success) return res.send({error: false, message: 'Success'});
else return res.send({error: true, message: 'Error sending message'});
});
});
app.post('/login', passport.authenticate('local', {failureRedirect: '/login', failureFlash: true}), function(req, res) {
console.log(req.flash());
console.log("Login success");
return res.send({error: false, message: 'Success'});
});
// SERVER
app.listen(port, function(){
console.log('Server started.');
console.log('Listening on port ' + port);
});
And here is an example of an API call from the React App (using axios):
login(e) {
e.preventDefault();
axios({
method: 'post',
url: 'http://localhost:3001/login',
data: {
username: this.state.username,
password: this.state.password,
}
})
.then((response) => {
console.log(response.data);
if (response.data.error) {
this.setState({error: true, errmessage: response.data.message});
} else {
this.setState({redirect: true});
}
})
.catch((error) => {
this.setState({error: true, errmessage: 'Error logging in'});
});
}
I figure there must be some way to have React store the session somehow (?) but I'm fairly new to React and don't really know how to use it with an express backend.
Your axios request from your React client needs to be sent withCredentials. To fix it, either do axios.defaults.withCredentials = true; or do axios.get('url', {withCredentials: true})...Also in your expressjs backend, set cors options credentials: true
Here is an example of setting up express-session using connect-redis. First, setup both express-session and the Redis store.
var session = require('express-session);
var RedisStore = require('connect-redis')(session);
Then, where you're declaring middleware for your app;
app.use(session({
store: new RedisStore({
url: process.env.REDIS_URL
}),
secret: process.env.REDISSECRET,
resave: false,
saveUninitialized: false
}));
now when you use req.session.{etc} this writes to your Redis DB.

Resources