15

I am currently trying to set up a Node/Express app with a React client to interact with it. I setup passport to handle authentication with JWT. When the user logs in, I validate the email/password. I then set the cookie:

res.cookie('jwt', token, { httpOnly: true, secure: false });

I see the token being passed back in the response header, but when I inspect my Chrome browser's cookie under Developer Tools > Application > Cookies, I see an empty cookie. What am I doing wrong and how do I send the jwt in the response header with subsequent requests?

server/App.js

const app = express()

app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.use(cookieParser());

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.post('/login', (req, res) => {
  passport.authenticate('local', { session: false }, (error, user) => {
    if (error || !user) {
      res.status(400).json({ error });
    }

    // Construct JWT payload
    const payload = {
      email: user.email,
      expires: Date.now() + parseInt(process.env.JWT_EXPIRATION_MS),
    };

    // Assign payload to req.user
    req.login(payload, {session: false}, (error) => {
      if (error) {
        res.status(400).send({ error });
      }
      // Generate a signed JWT
      const token = jwt.sign(JSON.stringify(payload), process.env.JWT_SECRET);

      // Assign JWT to cookie
      res.cookie('jwt', token, { httpOnly: true, secure: false });
      res.status(200).send({ email: user.email });
    });
  })(req, res);
});

client/LoginModal.js

  handleLogin = async () => {
    const { name, email, password } = this.state

    try{
      const res = await axios.post('http://localhost:8080/login', {
        email: email,
        password: password,
      })

      if(res.status == 200){
        console.log("Logged in")
        console.log(res)
      }

    } catch (err) {
      console.log(err)
    }
  }

Edit: My current workaround is to send the token as part of the payload. My react client then grabs the token from the payload and stores it in the browser's cookie. Is there a way to avoid this workaround (see below for example)?

server

 res.status(200).send({ email: user.email, jwt: token });

client

  if(res.status == 200){
    cookies.set('jwt', res.data.jwt)
    cookies.set('email', res.data.email)
  }
Huy
  • 10,806
  • 13
  • 55
  • 99

4 Answers4

15

When making the axis.post() call, you'll have to pass {withCredentials: true, credentials: 'include'} as your second argument, only this way will your browser set the cookies.

Jeremy
  • 327
  • 2
  • 12
0

You have the cookie set with the httpOnly flag enabled. Most modern browsers restrict read access to such cookies through developer tools. You can read more about it here.

If you'd like to see the contents of the cookie in your development environment, set httpOnly to false.

0

This solution I found works with both local development and production ( and alos LAN access, eg. when you access the website on your LAN IP address such as http://192.168.xxx.xxx:<port>):

// Set CORS options 
const cors = require(`cors`)

const whitelist = [ 
    'http://localhost:<application port>', // not https
    'https://yourprod.ip.address.com' // must be https!
    'http://<your local IP>:<port>', // optional, LAN access
    // ...
]

const corsOptions = {
    credentials: true,
    origin: (origin, callback) => {

        // `!origin` allows server-to-server requests (ie, localhost requests)
        if(!origin || whitelist.indexOf(origin) !== -1) {
            callback(null, true)
        } else {
            callback(new Error("Not allowed by CORS: "+ origin))
        }
    },
    optionsSuccessStatus: 200
}

app.use(cors(corsOptions))

Then on the authentication endpoint:

// Set Cookie
const cookieContent = 'this is a cookie'

const cookieOptions = {
  httpOnly: true,    // safety, does not allow cookie to be read in the frontend javascript
  maxAge: 24*3600*1, // cookie age in seconds
  sameSite: 'Strict' // works for local development
}

if(process.env.NODE_ENV === 'production') {

  // these options work on a https server
  cookieOptions.secure = true 
  cookieOptions.sameSite= 'None'
}

res.cookie(
  'cookie-tag', 
  refreshToken, 
  cookieOptions
)
res.json(cookieContent)  
Bersan
  • 1,032
  • 1
  • 17
  • 28
0

What worked for me is setting app.use(cors({ origin: true, credentials: true })) in cors package. Also setting withCredentials: true, credentials: 'include' while fetching from backend

mahin
  • 57
  • 6