I'm currently creating an application in React/Express and I'm learning how to create sessions. I'm using express-session as it's what everyone recommends but I have unexpected behaviors.
In my route post, the route used during the connection, I try to create a new session for the user but it does not seem to work (no cookie and the session is not created) while my console.log returns the expected information.
router.post('/login', async (req, res) => {
const user = await Users.findOne({where: {Email: req.body.Email}})
if (!user) res.json({error: "User doesn't exist"})
bcrypt.compare(req.body.Password, user.Password).then((match) => {
if (!match) res.json({error: "Wrong password"})
req.session.user = user.dataValues
console.log(req.session)
})
})
In my get route, which is called at every refresh of the page, I realize that the session is empty and a new cookie is created (I don't really know why).
router.get('/login', async (req, res) => {
console.log(req.session)
if (req.session.user) {
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({ loggedIn: false})
}
})
Here is how I set up express-session as well as cors (I read that the problem could come from there but all seems correct).
app.use(cors({
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
credentials: true //permet d'activer les cookies
}))
app.use(session({
key: "userId",
secret: "foo",
resave: false,
saveUninitialised: true,
cookie: {
expires: 60 * 60 * 24
},
}))
I also read that the problem could come from the API call, I use Axios and I was careful to add the line Axios.defaults.withCredentials = true
before the call.
Your router.post("/login", ...)
route never sends any response back to the client. An express session works by establishing a cookie with the browser that the browser will send back on future requests. That cookie contains an encrypted session key that is the magic sauce that makes the session possible. When you don't send any response back from the /login
POST, then that cookie never gets back to the browser and thus the session cookie can't be sent back on future requests and thus the session does not work.
Instead, the next request coming from the browser will not have a session cookie and thus Express will try to create yet another new empty session.
To fix that part of the issue, send a response back from your POST request:
router.post('/login', async (req, res) => {
const user = await Users.findOne({where: {Email: req.body.Email}})
if (!user) res.json({error: "User doesn't exist"})
bcrypt.compare(req.body.Password, user.Password).then((match) => {
if (!match) res.json({error: "Wrong password"})
req.session.user = user.dataValues;
console.log(req.session)
res.send("some response"); // <== send some response here
}).catch(err => {
// some error handling here
console.log(err);
res.sendStatus(500);
});
});
For, more complete and centralized error handling where you use http status to reflect actual errors, you can do something like this:
class myError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
router.post('/login', async (req, res) => {
try {
const user = await Users.findOne({where: {Email: req.body.Email}})
if (!user) throw new MyError("User doesn't exist", 404) ;
const match = await bcrypt.compare(req.body.Password, user.Password);
if (!match) throw new MyError("Wrong password", 401);
req.session.user = user.dataValues;
console.log(req.session);
res.json({loggedIn: true});
} catch(e) {
const status = e.status || 500;
res.status(status).json({error: e.message});
}
});
Note, I've stopped mixing await
with .then()
which is not considered good style and then used try/catch
and throw
to integrate the more comprehensive error handling into one spot.