3

I'm having problems with express-session. Session data is not persisting between requests.

As you can see in the code below the /join route sets some session properties, but when the /surveys route is hit the session data is not available. req.session.loggedIn returns undefined in the /surveys route.

require('dotenv').config()

const cors = require('cors')
const express = require('express')
const open = require('amqplib').connect(process.env.RABBIT_SERVER)
const session = require('express-session')
const subscribeValidator = require('./src/validators/subscribe')
const { validationResult } = require('express-validator/check')

const app = express()

app.set('trust proxy', 1)
app.use(session({
  name: 'giro',
  secret: process.env.SESSION_SECRET,
  saveUninitialized: false,
  cookie: { secure: false },
  maxAge: process.env.SESSION_EXPIRY,
  resave: false
}))
app.use(express.json())
app.use(cors())

app.post('/join', subscribeValidator, function (req, res) {
  /*
  if(session.loggedIn) {
    return res.send(JSON.stringify({
      redirect: '/surveys'
    }))
  }
  */

  const data = {
    firstname: req.body.firstname,
    surname: req.body.surname,
    email: req.body.email,
    birthday: req.body.birthday,
    gender: req.body.gender,
    isSubscribed: req.body.isSubscribed
  }

  const errors = validationResult(req.body)
  if (!errors.isEmpty()) {
    return res.status(422).json({ errors: errors.array() })
  }

  open
    .then(conn => conn.createChannel())
    .then(ch => ch
      .assertQueue('subscribe')
      .then(ok => {
        ch.sendToQueue('subscribe', Buffer.from(JSON.stringify(data)))

        req.session.firstname = data.firstname
        req.session.email = data.email
        req.session.isSubscribed = data.isSubscribed
        req.session.confirmed = false
        req.session.loggedIn = true

        req.session.save()
        res.send(JSON.stringify({
          redirect: '/surveys',
          firstname: data.firstname,
          email: data.email
        }))
      })
    )
    .catch(console.warn)
})

app.post('/surveys', (req, res) => {
  console.log(req.session.loggedIn)
  if (!req.session.loggedIn) {
    return res.send(JSON.stringify({
      error: {
        type: 'auth',
        message: 'You must be logged in to view and complete surveys. Have you signed up?'
      }
    }))
  }

  res.send(JSON.stringify({
    surveys: [
      'one',
      'two',
      'three'
    ]
  }))
})

// Start the server :)
app.listen(3000, () => 
  console.log('Server running at: http://localhost:3000')
)

I've checked many SO posts which don't relate to my problem, read the docs over and over, but I still seem to be missing something.

Thanks

BugHunterUK
  • 8,346
  • 16
  • 65
  • 121
  • What is the client? Browser? Or some other programmatic request? – jfriend00 Aug 23 '18 at 00:16
  • Client is a browser. – BugHunterUK Aug 23 '18 at 00:17
  • Are these routes regular browser requests? Or are these ajax calls? I see you are enable CORs. Are these requests coming from a web page at a different domain than this host? Have you looked in the browser to see if the desired session cookie is there? Have you logged the cookie on the server to see if the desired cookie is there? You can go to the network tab in the Chrome debugger to see everything the client is sending and receiving from the server (including cookies) for any given request. – jfriend00 Aug 23 '18 at 00:23
  • The cookie is encrypted but yes the cookie is present in the browser. The requests are being made on `locahost:1313`, and the api is on `localhost:3000`. I'm able to make requests to and from the API without issues but I hadn't considered CORS being an issue here with cookies. – BugHunterUK Aug 23 '18 at 00:26
  • Do one of the following. Look at the network tab in the browser and see if the cookie is being sent to the server with a given request OR log the value of the cookies upon receiving a given request on the server. You need to first establish whether the browser is or isn't sending the cookies to the server. Looking at the network tab in the browser will also show you whether the browser thinks it has to do pre-flight or not with an OPTIONS request and what it is getting back from that. – jfriend00 Aug 23 '18 at 00:32
  • I think I see the problem. The cookies are not present on the `1313` host. My guess is now that because the request is coming from `1313` no cookies are being sent with the request. – BugHunterUK Aug 23 '18 at 00:42
  • Solved it. Thank you for your help. Without it I would have never have thought to look at cors. TIL. – BugHunterUK Aug 23 '18 at 01:03

2 Answers2

3

With the help of @jfriend00 I was able to solve the problem. Because the API was on a different port from the CDN that was making the request it falls under a cross origin request when making an xhr request from the CDN to the API. Although I had cors enabled in order for cookies to work cross origin you have to make a few tweaks.

First of all I had to configure express-cors like so:

app.use(cors({
  origin: 'http://localhost:1313',
  credentials: true
}))

This sets the Access-Control-Allow-Origin header value to http://localhost:1313. Wildcards can not be used otherwise it will fail the pre-flight check.

The crednetials property sets the Access-Control-Allow-Credentials header value to true. Again, pre flight will fail without this.

When making requests on the host I also had to use withCredentials. I'm using superagent so I did it like so:

componentDidMount () {
    request
      .post('http://localhost:3000/surveys')
      .withCredentials()
      .set('accept', 'json')
      .end((err, res) => {
        this.setState({ isLoading: false })
        ...
      })
}

And now it works :)

BugHunterUK
  • 8,346
  • 16
  • 65
  • 121
0

On express: when we use cors, we put:

app.use(
  cors({
    origin: "http://localhost:3000",
    credentials: true,
  })
);

And on client side: with each axios request, we put:

const res = await axios.get(url, {withCredentials: true})
Luhaib-j
  • 79
  • 7