Expressjs session not persiting between routes - javascript

I have scoured the community for answers, but I cant seem to find one.
In my application, I send user info from the client (react) to /user/save route. When I log the session to the console, all the data is there. But when I log req.session out on any other route it doesn't have any data.
Right now I am accessing the API from localHost while the API is hosted with ngrok.
When I had the API integrated within the same codebase as my React app, all worked well, but now it's a bit funky.
Server.js
const express = require('express');
var expressSession = require('express-session');
const cors = require('cors');
const cookieParser = require("cookie-parser");
const path = require('path');
const app = express();
const axios = require('axios');
const mongoose = require('mongoose');
const rateLimit = require("express-rate-limit");
var cron = require('node-cron');
require('dotenv').config()
const corsConfig = {
"origin": "http://localhost:3000",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"credentials": true
};
// Implemented a rate limiter which is a TEMPORARY FIX for the infinite loop by the useEffect
const limiter = rateLimit({
windowMs: .1 * 60 * 1000, // 10 seconds limit
max: 4
});
// Middleware
const hostValidationMiddleware = require('./Middleware/HostValidationMiddleware');
const sessionValidationMiddleware = require('./Middleware/SessionValidationMiddleware');
const {authenticateJWT} = require('./Middleware/JwtMiddleware');
async function connectToDB() {
// Database
await mongoose.connect(process.env.mongo_url, { useNewUrlParser: true, useUnifiedTopology: true }, () => {
console.log('[connectToDB]: Connected to DB');
})
}
connectToDB();
app.use(cookieParser());
const oneDay = 1000 * 60 * 60 * 24;
// Creating the session in order to save user data to req.session
app.use(
expressSession({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
secure: false,
cookie: {
maxAge: oneDay,
sameSite: "none",
}
})
);
app.use(cors(corsConfig));
//app.use('/', limiter);
app.use(express.json());
app.use('/', hostValidationMiddleware, sessionValidationMiddleware, require('./Routes/Store-Invoices'));
app.use('/', require('./Routes/SaveLoggedInUser') , authenticateJWT, require('./Routes/GetUserInvoices'));
app.use('/', require('./Routes/UpdateUserData'));
app.get('/', async (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(8081, () => {
console.log(`Server listening on 8081`);
});
Saved Logged In User
saveLoggedInUser.post('/user/save', async (req, res) => {
const User = req.body;
const token = await GetJwt(User);
req.session.currentUser = User;
if (token && !('authorization' in req.session) && User) {
req.session.authorization = `Bearer ${token}`
req.session.save();
}
console.log('USER', req.session);
const usersFromDB = await fetchUsersFromDB().catch((e) => { console.log(e) });
findCommonUser(usersFromDB,User);
res.sendStatus(200);
})
Get User Invoices
userInvoices.get('/invoice/user', async (req,res) => {
const invoices = await InvoiceModel.find().catch((e) => {console.log(e)});
const user = req.session;
console.log('INVOICE',user);
});
Client Call
async function fetchUserInvoices() {
// Making a call to external api
const url = `https://f604-104-49-198-21.ngrok.io/invoice/user`;
const invoiceResponse = await axios.get(url, {withCredentials: true}).catch((e) => { console.log(e) });
setData(invoiceResponse.data);
return;
}

It looks like you have your SameSite cookie flag set to "none" which according to MDN now requires the "Secure" flag to also be set to true in order to work. However, as you're using localhost, you probably also can't set Secure to true since you're not on HTTPS.
Removing that line in your Server.js code fixed it for me, as did setting sameSite: "strict".

Related

How to authenticate socket.io connection in nodejs application

I am working on a chat application where socket is used for bi-directional sharing of messages.
The socket currently works fine but I want to be able to authenticate user before they can have access to the socket connection. I have trying to use passport but it would seem there is something I am not doing right as I get the error No session found.
const createError = require("http-errors");
const express = require("express");
const { join } = require("path");
const logger = require("morgan");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const SequelizeStore = require("connect-session-sequelize")(session.Store);
const db = require("./db");
const { User } = require("./db/models");
const passport = require('passport');
// create store for sessions to persist in database
const sessionStore = new SequelizeStore({ db });
const http = require("http");
const db = require("../db");
const onlineUsers = require("../onlineUsers");
const passportSocketIo = require('passport.socketio');
const { json, urlencoded } = express;
const app = express();
app.use(logger("dev"));
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(express.static(join(__dirname, "public")));
app.use(cookieParser());
app.use(session({
genid:(req)=>{
return req.cookies
},
secret: process.env.SESSION_SECRET,
resave: true,
store: sessionStore,
saveUninitialized: true,
}));
app.use(function (req, res, next) {
const token = req.cookies["messenger-token"];
if (token) {
jwt.verify(token, process.env.SESSION_SECRET, (err, decoded) => {
if (err) {
return next();
}
User.findOne({
where: { id: decoded.id },
}).then((user) => {
req.user = user;
return next();
});
});
} else {
return next();
}
});
app.use(passport.initialize());
app.use(passport.session());
const port = normalizePort(process.env.PORT || "3001");
app.set("port", port);
/**
* Create HTTP server.
*/
const server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces, and sync database.
*/
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST", "PUT"],
credentials: true
}
});
io.use(
passportSocketIo.authorize({
cookieParser: cookieParser,
key: 'messenger-token',
secret: process.env.SESSION_SECRET,
store: sessionStore,
success: onAuthorizeSuccess,
fail: onAuthorizeFail
})
);
function onAuthorizeSuccess(data, accept) {
console.log('successful connection to socket.io');
accept(null, true);
}
function onAuthorizeFail(data, message, error, accept) {
if (error) throw new Error(message);
console.log('failed connection to socket.io:', message);
accept(null, false);
}
io.on("connection", (socket) => {
// console.log(`USER ${JSON.stringify(socket)}`);
socket.on("go-online", (id) => {
if (!onlineUsers.includes(id)) {
onlineUsers.push(id);
}
// send the user who just went online to everyone else who is already online
socket.broadcast.emit("add-online-user", id);
});
socket.on("new-message", (data) => {
socket.broadcast.emit("new-message", {
message: data.message,
sender: data.sender,
});
});
socket.on("read-message", (data) => {
socket.broadcast.emit("read-message", data);
});
socket.on("read-messages", (data) => {
socket.broadcast.emit("read-messages", data);
});
socket.on("logout", (id) => {
if (onlineUsers.includes(id)) {
userIndex = onlineUsers.indexOf(id);
onlineUsers.splice(userIndex, 1);
socket.broadcast.emit("remove-offline-user", id);
}
});
});
How can I authenticate user with socket. Feel free to suggest other method aside using passport.
I found the same error in Github and it hasn't been resolved yet.
According to the official document (see "Compatibility with Express middleware" section), you can use express middleware in io.use by using wrap.
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
The section shows how to use "express-session" module with socket.io. I guess this way is better for you because you already use it.

How to solve: "ForbiddenError: invalid csrf token"

I have problems with setting up csrf. I hope that someone can point me in the right direction.
I'm using next.js with express.js.
When I refresh the page following happens:
I get a _csurf cookie (dev tools > application > cookies)
a csrf token is logged in my console (-> see last code snipped from context)
when I make a POST request (-> see routes/index.js f.ex. "/aignupQ"), I get the error "Invalid csurf token"; in the request header I can see the _csrf cookie
when I refresh the page and make the POST request again everything works.
I'm really confused by the error and really don't understand what is wrong. Here is some relevant code:
server.js:
require("dotenv").config();
const express = require("express");
const next = require("next");
const bodyParser = require("body-parser");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const routes = require('./routes');
const csrf = require("csurf");
const csrfProtection = csrf({
cookie: true,
});
//next.js configuration
const dev = process.env.NODE_DEV !== "production";
const nextApp = next({ dev });
const port = 3000;
const handle = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());
app.use((err, req, res, next) => {
res.status(500).send("Something went wrong!");
});
app.use(csrfProtection);
app.use('/api', routes);
app.get("*", (req, res) => {
return handle(req, res);
});
//start server
app.listen(port, (err) => {
if (err) throw err;
console.log(`listening on port ${port}`);
});
});
routes/index.js:
const express = require('express')
const router = express.Router()
const getCsrfToken = require('../controllers/csrf')
const postSignupQ = require("../controllers/postSignupQ");
const attachUser = require("../middleware/attachUser");
router.get("/csrfToken", getCsrfToken.getCsrfToken);
router.use(attachUser);
router.post("/signupQ", postSignupQ.postSignupQ);
module.exports = router
controllers/csrf.js
const getCsrfToken = (req, res) => {
res.json({ csrfToken: req.csrfToken() });
};
module.exports = { getCsrfToken };
context - here I console.log(csrf):
useEffect(() => {
const getCsrfToken = async() => {
const { data } = await axios.get('api/csrfToken');
console.log("csurf", data);
axios.defaults.headers['X-CSRF-Token'] = data.csrfToken;
};
getCsrfToken();
}, []);
I don't understand why I get the error message, when I make a POST request for the first time and when I refresh the page everything works. What's the problem and how can I solve this?
EDIT
Thanks to Jack Yu the code snippets above are working. Maybe it can help someone else..
EDIT
I also found your api path might be wrong. In your axios is await axios.get('csrfToken'). But, I saw /api/csrfToken in your router. Change it to await axios.get('/api/csrfToken')
Original Answer
In csurf package, when you use csurf({cookie: true}) with cookie mode in middleware at multiple times, it'll break the csrf token in response header with first time post. You could take a look for more detail in CSRF doesn't work on the first post attempt, I've explain the reason in that post. So, there are two solutions you could use.
Solution 1
According to the comments, you use app.use(csruf({cookie: true})) in server.js and router/index.js. Remove the following line in your router/index.js. When you setup csruf in server.js, you could use req.csrfToken() in controllers/csrf.js without setting up csruf again.
const csrf = require("csurf");
const csrfProtection = csrf({
cookie: true,
});
router.use(csrfProtection);
Solution 2
You'll need to use express-session package. Add following code before the csurf. If you have .use(csrf({cookie: true})) in your routes/index.js, remove it.
const session = require('express-session')
// mark1: change it to false
const csrfProtection = csrf({
cookie: false,
});
// blablabla ... other code
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());
// mark2: put here
app.use(session({
name: "test",
secret: "test",
cookie: { maxAge: 3 * 60 * 60 * 1000 },
resave: false,
saveUninitialized: false
}))
// put here
app.use((err, req, res, next) => {
res.status(500).send("Something went wrong!");
});
app.use(csrfProtection);
app.use('/api', routes);
Then change {cookie: true} to {cookie: false} in all csurf setting. When you use session mode, you could use csruf({cookie: false}) many times in middleware.
We need to pass a cookie in header like below:
headerMaps.put("cookie", "_csrf=value; connect.sid=value; csrfToken=value")

Express could not store cookie

I am trying to store a login-session into a cookie when a user login in via username/passport, so the server knows the user is logged in. But the cookie will never be set.
Here is the relevant code:
index.js:
if (process.env.NODE_ENV !== 'production') {
require("dotenv").config();
}
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json({ limit: "16mb", extended: true }));
app.use(bodyParser.urlencoded({ extended: true, limit: "16mb" }));
const session = require("express-session");
app.use(
session({
secret: "thisIsMySecretMessageHowWillYouGuessIt",
resave: true,
saveUninitialized: true,
cookie: {
sameSite: 'none',
httpOnly: true,
secure: true
},
})
);
const passport = require("passport");
app.use(passport.initialize());
app.use(passport.session());
const cookieParser = require("cookie-parser");
app.use(cookieParser());
const cors = require("cors");
const whitelist = env.process.CLIENT_URL;
app.use(cors({ origin: whitelist, credentials: true }));
auth.js:
const cookieKey = "sid";
const md5 = require("md5");
const bcrypt = require("bcrypt");
const redis = require("redis");
const client = redis.createClient(process.env.REDIS_URL);
const cookieOption = { maxAge: 3600 * 1000, httpOnly: true, sameSite: 'none', secure: true};
login = async (req, res) => {
const sessionKey = md5(
getSecretMessage() + new Date().getTime() + user.username
);
client.hmset("sessions", sessionKey, JSON.stringify(user), function(err) {
if (err) throw err;
});
// this sets a cookie
res.cookie(cookieKey, sessionKey, cookieOption); // expire after 60 mins
res.send({ username: user.username, result: "success" });
};
isLoggedIn = async (req, res, next) => {
if (
(req.cookies === undefined || req.cookies[cookieKey] === undefined) &&
!req.isAuthenticated()
) {
res.sendStatus(401);
return;
}
};
The req.cookies['sid'] will always be undefined, so the server would return 401 status.
For the react client, the 'credentials' has been set to 'include'.
Things I tried:
Flipping around the 'secure' values in the cookie option in both index.js and auth.js
Used 'express-samesite-default' package
One point to notice is that the functionality was working half-year ago, there might be some dependencies update so it changed the cookie.
You could use local storage.Local storage saves on the clients device and is accessible using localStorage.getItem('key') and you can add items by using the localStorage.setItem('key', 'value').

Node.js REST endpoint not catching parameters passed from axios request

I'm making a POST request from a React front-end using axios to an endpoint to save some data to my DB (MongoDB). I'm getting an error that one cannot read property 'name' of undefined. I think that's occurring because req.body is undefined but I can't understand what's wrong with my axios request. I logged all the parameters and they are there (not undefined). The axios request and the endpoint are written below. Any help will be appreciated. Thanks!
Axios Request
const uploadElement = async (name, HTMLCode, JSCode, CSSCode, screenshot) => {
console.log(name)
try {
await axios({
method: 'post',
url: '/api/elements',
data: {
name: name,
HTMLCode,
JSCode,
CSSCode,
screenshot
}
});
} catch (e) {
console.log(e);
}
}
Endpoint for POST Request
router.post("/", upload.single("screenshot"), async (req, res) => {
try {
const newElement = new Element({
name: req.body.name,
JSCode: req.body.JSCode,
HTMLCode: req.body.HTMLCode,
CSSCode: req.body.CSSCode,
screenshot: req.file.buffer,
});
await newElement.save();
res.send("Data uploaded successfully!");
} catch (e) {
console.error(e);
}
});
Server.js
const express = require("express");
const passport = require("passport");
const session = require("express-session");
const cors = require('cors');
const elementRouter = require("./routes/elementRoute");
const authRouter = require("./routes/authRoute");
const connectDB = require("./config/db");
const app = express();
const port = process.env.PORT || 5000;
connectDB();
app.use(
session({
secret: "googleOAuth",
resave: false,
saveUninitialized: true,
})
);
app.use(cors());
// Passport Config
require("./config/passport")(passport);
app.use(passport.initialize());
app.use(passport.session());
app.use("/api/elements", elementRouter);
app.use("/api/auth", authRouter);
app.listen(port, () => {
console.log(`Server is up on port ${port}`);
});
You need to install and require body-parser in your serverside code
First run npm i --save body-parser
Then require it like this
const bodyParser = require("body-parser");
Then use it after you declare your app ( after this line const app = express();)
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
This makes the data of your request available in req.body

Nextjs -Express App.render The "path" argument must be of type string. Received type object

I use Nextjs and Express.js. I separate the nextjs root folder in the app folder like this :
app->pages->
-_app.js
-login.js
-index.js
When i add this :
app.get('*', (req, res) => {
const parsedUrl = parse(req.url,true);
const {pathname, query = {} } = parsedUrl;
const route = routes[pathname];
/**
* Pull in front end routes and check request against those routes
*
*/
if(route){
return app.render(req,res,route.page,query);
}
handle(req, res) // for all the react stuff
});
I got error like this :
Note that pages will be compiled when you first load them. ready at
http://localhost:3000 TypeError [ERR_INVALID_ARG_TYPE]: The "path"
argument must be of type string. Received type object
at assertPath (path.js:39:11)
at extname (path.js:835:5)
at new View (D:\WEBSITE\MENPRO\TRACER-STUDY-NEXTJS\node_modules\express\lib\view.js:57:14)
at Function.render (D:\WEBSITE\MENPRO\TRACER-STUDY-NEXTJS\node_modules\express\lib\application.js:570:12)
at app.get (D:\WEBSITE\MENPRO\TRACER-STUDY-NEXTJS\server\index.js:77:24)
But when i remove this : return app.render(req,res,route.page,query); its work again.
What happens with that ?
full code my server :
const express = require('express');
const bodyParser = require('body-parser');
const next = require('next');
const passport = require('passport');
const session = require('express-session');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const keys = require('./config/keys');
const uuidv4 = require('uuid').v4;
// const path = require('path');
// const RouteAPI = require('./modules');
const dev = process.env.NODE_ENV !== 'production';
const PORT = process.env.PORT || 3000;
const nextApp = next({ dev, dir: "./app" });
const { parse } = require('url');
const handle = nextApp.getRequestHandler()
const getRoutes = require('./routes');
const routes = getRoutes();
nextApp.prepare().then(() => {
// express code here
const app = express();
app.use(session({
genid: function (req) {
return uuidv4() // use UUIDs for session IDs
},
name: keys.session.name,
secret: keys.session.secret,
resave: false,
saveUninitialized: true,
rolling: true,
cookie: {
secure: false,
httpOnly: true,
maxAge: keys.session.maxAge, // satu hari,
sameSite: true,
}
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(cookieParser());
app.disable('x-powered-by');
app.use(cors({ origin: keys.origin.url, credentials: true }))
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// app.use((req, res, next) => {
// res.header('X-XSS-Protection', '1; mode=block');
// res.header('X-Frame-Options', 'deny');
// res.header('X-Content-Type-Options', 'nosniff');
// res.header("Access-Control-Allow-Origin", keys.origin.url);
// next();
// })
// app.use('/api/', [
// RouteAPI.MahasiswaRoutes
// ]);
app.get('*', (req, res) => {
const parsedUrl = parse(req.url,true);
const {pathname, query = {} } = parsedUrl;
const route = routes[pathname];
/**
* Pull in front end routes and check request against those routes
*
*/
if(route){
return app.render(req,res,route.page,query);
}
handle(req, res) // for all the react stuff
});
app.listen(PORT, err => {
if (err) throw err;
console.log(`ready at http://localhost:${PORT}`)
});
})
// Server static assets if in production
Routes
module.exports = () =>{
return{
"/":{page:"/"},
"/login":{page:"/login"},
"/404":{page:"/404"}
};
};
Ah yes, I've had the same issue and just found the solution: use nextApp.render() instead of app.render()
if(route){
return nextApp.render(req,res,route.page,query);
}

Categories