I did not write the backend, and do not have backend experience. It was 2 man project and now I am trying to gain that experience by deploying it.
I have backend deployed on Render, frontend on Vercel.
Everything worked in development, and in preview as well. It was only after deploying, that the cookie based issues appeared, meaning to test the code I have to keep redeploying as they won't appear in testing.
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const socket = require('socket.io');
const multer = require('multer');
const cors = require('cors');
require('dotenv').config();
const User = require('./models/user');
const MONGODB_URI = process.env.MONGO_KEY;
const app = express();
const http = require('http');
const server = http.createServer(app);
const io = socket(server);
let users = [];
const addUser = (userId, socketId) => {
!users.some((user) => user.userId === userId) && users.push({ userId, socketId });
};
const removeUser = (socketId) => {
users = users.filter((user) => user.socketId !== socketId);
};
const getUser = (userId) => {
return users.find((user) => user.userId === userId);
};
io.on('connection', (socket) => {
// when connect
console.log('a user connected.');
// take userId and socketId from user
socket.on('addUser', (userId) => {
addUser(userId, socket.id);
io.emit('getUsers', users);
});
// send and get message
socket.on('sendMessage', ({ senderId, receiverId, text }) => {
const user = getUser(receiverId);
io.to(user.socketId).emit('getMessage', {
senderId,
text,
});
});
// when disconnect
socket.on('disconnect', () => {
console.log('a user disconnected!');
removeUser(socket.id);
io.emit('getUsers', users);
});
});
const store = new MongoDBStore({
uri: MONGODB_URI,
collection: 'sessions',
});
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'images');
},
filename: (req, file, cb) => {
cb(null, new Date().toISOString().replace(/:/g, '-') + '-' + file.originalname);
},
});
const fileFilter = (req, file, cb) => {
if (
file.mimetype === 'image/png' ||
file.mimetype === 'image/jpg' ||
file.mimetype === 'image/jpeg'
) {
cb(null, true);
} else {
cb(null, false);
}
};
app.set('views', 'views');
app.set('view engine', 'ejs');
const marketRoutes = require('./routes/market');
const adminRoutes = require('./routes/admin');
const authRoutes = require('./routes/auth');
const messageRoutes = require('./routes/message');
const chatRoutes = require('./routes/chat');
const user = require('./models/user');
app.use(
cors({
origin: process.env.CLIENT_URL, // configured to accept credentials from front end for session cookies to work
credentials: true,
})
);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(multer({ storage: storage, fileFilter: fileFilter }).single('image'));
app.use(express.static(path.join(__dirname, '/public')));
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use(
session({
secret: 'my secret',
resave: false,
saveUninitialized: false,
store: store,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
secure: true,
sameSite: 'None',
},
})
);
app.use((req, res, next) => {
res.locals.isAuthenticated = req.session.isLoggedIn;
next();
});
app.use((req, res, next) => {
if (!req.session.user) {
return next();
}
User.findById(req.session.user._id)
.then((user) => {
if (!user) {
return next();
}
req.user = user;
next();
})
.catch((err) => {
console.log(err);
});
});
app.use(marketRoutes);
app.use(adminRoutes);
app.use(authRoutes);
app.use(messageRoutes);
app.use(chatRoutes);
mongoose
.connect(MONGODB_URI)
.then((result) => {
server.listen(3000);
console.log('connected!');
})
.catch((err) => {
console.log(err);
});
So after going round in circles with several AIs, they keep repeating the same thing, to add these lines that I did not originally have:
httpOnly: true,
secure: true,
sameSite: 'None',
This hasn't worked. Without these lines, the app did have full functionality only in Firefox browser. Any other browser I tested it on, you can see the DB data, you can signup/login (POST requests), but you cannot perform any other request, because the controller will check for a req.session.user in pretty much every controller. This relies on cookies to work correctly. The client side will use some hooks to make requests which will call this function
export interface Fetch {
params: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: object;
}
export const fetchData = async ({ params, method, data }: Fetch) => {
const isFormData = data instanceof FormData;
const body = data ? (isFormData ? data : JSON.stringify(data)) : null;
const response = await fetch(import.meta.env.VITE_SERVER_URL + params, {
method,
headers: isFormData ? undefined : { 'Content-Type': 'application/json' },
credentials: 'include', // send cookies to backend
body,
});
const resData = await response.json();
if (!response.ok) {
throw resData;
}
return resData;
};
The Cors is set to receive credentials. Credentials set to true on Client side. Everything works in Dev/Preview and Firefox with the App code just like this:
app.use(
session({
secret: 'my secret',
resave: false,
saveUninitialized: false,
store: store,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
},
})
);
But the cookies will not work in any non Firefox browser. Then adding the extra cookie config prevents it from working in any environment at all.
I have tried amending the cookie config and redeploying but it only made it less functional. I would have thought the set up is correct but I am not able to receive any further help on this. Right now I could deploy it without the cookie config and just leave a note that this only has full functionality in Firefox.