Cookie is sent, but not stored when deployed

Viewed 295

I'm having issues with cookies in a MERN app hosted to Vercel (Front-end) and Heroku (Back-end).

Everything is working fine in localhost, but when deployed i'm having issues storing cookies. Set-Cookie is sent with the sign in request, and the request itself looks fine. I get no errors.

After being signed in, authenticated routes return what I expect for each user, so the cookie seems to be accessible to the back-end even tho the cookie is not stored on the front-end under storage, which I assume is a security issue. This is not the case in Safari, in Safari the cookies are gone after login, so the user is logged out again.

The only route that does not work is signing out. On sign out I clear the token cookie, but it is trying to delete something that technically isn't there, still no errors. UPDATE: Clearing cookie works! I wasn't sending any data back with the sign out, so the front-end thought it was unresponsive, adding a .send({ message: 'Sign Out Successful' }) solved that issue. However, still no cookie visible in the front-end storage.

For CORS settings I have the origin set to the front-end url, and credentials set to true. While on the front-end I have withCredentials: true set on every request.

When deployed, the cookie is using sameSite: none, and secure: true.

Below are the sign in and sign out routes, but you can find the full back-end code here, and the front-end code here on GitHub.

Sign in route on back-end

import type Route from '../../types/Route'
import jwt from 'jsonwebtoken'
import env from '../../env/env'
import bcrypt from 'bcryptjs'

import User from '../../models/User'
import validateSignIn from '../../utils/validation/signIn'

const route: Route = {
  method: 'post',
  execute: async (req, res) => {
    const { username, password } = req.body.user

    try {
      const { errors, valid } = validateSignIn(username, password)
      if (!valid) return res.status(401).send({ errors })
      
      const user = await User.findOne({ username })
      if (!user) return res.status(404).send({ errors: { username: 'User not found' }})

      const correctPassword = await bcrypt.compare(password, user.password)
      if (!correctPassword) return res.status(401).send({ errors: { password: 'Wrong password' } })

      const token = jwt.sign({ userId: user._id }, env.SECRET, { expiresIn: "1hr" })
      return res.status(200).cookie('token', token, {
        expires: new Date(Date.now() + 604800000),
        secure: env.ENVIRONMENT === 'LIVE',
        sameSite: env.ENVIRONMENT === 'LIVE' ? 'none' : 'lax',
        httpOnly: true
      }).send(user)
    } catch (error) {
      console.log('@sign/in', error)
      return res.sendStatus(500)
    }
  }
}

export default route

Sign out route on back-end:

import type Route from '../../types/Route'
import authorization from '../../middlewares/http'
import env from '../../env/env'

// TODO: Fix Sign Out

const route: Route = {
  method: 'get',
  authorization,
  execute: async (req, res) => {
    try {
      return res.clearCookie('token').sendStatus(200)
    } catch (error) {
      console.log('@sign/out', error)
      return res.status(500)
    }
  }
}

export default route
1 Answers

The .clearCookie() doc says

Web browsers and other compliant clients will only clear the cookie if the given options is identical to those given to res.cookie(), excluding expires and maxAge.

So, try making your clear operation match your set operation, something like this.

res
  .clearCookie('token', token, {
        secure: env.ENVIRONMENT === 'LIVE',
        sameSite: env.ENVIRONMENT === 'LIVE' ? 'none' : 'lax',
        httpOnly: true })
  .sendStatus(200)
Related