Search code examples
node.jsexpresscookiesjwtcors

Not able to set Cookies in browser in Express and React


I am trying to store JWt token in browser via cookies.

Post request on /login route in express is as follows:

const accessToken = jwt.sign({ email }, process.env.ACCESS_TOKEN_SECRET);

console.log(accessToken)

res.cookie('jwt', accessToken, { httpOnly: true })
// console.log("Cokkie set is", cookie);
res.json({ message: 'Logged in successfully' });

Axios call for frontend is as follows:

const res = await axios.post(
  `${process.env.REACT_APP_API_URL}/login`,
  {
    email: loginInputs.email,
    password: loginInputs.password,
  },
  {
    // credentials: 'include',
    withCredentials: true,
  }
)

Cors policy is set as follows:

const corsOptions = {
  origin: process.env.FRONTEND_URL,
  credentials: true,
  optionsSuccessStatus: 200,
  // exposedHeaders: \['Set-Cookie', 'Date', 'ETag'\]
};

app.use(cors(corsOptions));
app.options('\*', cors(corsOptions))

This code works perfectly in Postman and the cookies are being set but throws the following error in browser.Currently both client and server work on local host!

login:1 Access to XMLHttpRequest at 'http://localhost:8080/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

I tried everything mentioned in Express Session Cookie Not Being Set when using React Axios POST Request but was not able to resolve the error.


Solution

  • The origin configuration of cors middleware should be http://localhost:3000, add the http scheme. See Origin doc, the valid origin syntaxes are:

    Origin: null
    Origin: <scheme>://<hostname>
    Origin: <scheme>://<hostname>:<port>
    

    A response that tells the browser to allow requesting code from the origin https://developer.mozilla.org to access a resource will include the following:

    Access-Control-Allow-Origin: https://developer.mozilla.org
    

    If the server sends the below response header to the browser.

    Access-Control-Allow-Origin: localhost:3000
    

    The client running on http://localhost:3000 send a HTTP request to http://localhost:8080/api/login, the request header Origin will be:

    Origin: http://localhost:3000
    

    Access-Control-Allow-Origin does not match Origin, the user agent will throw the CORS error.

    The Origin request header is added to the HTTP request by the browser automatically.

    If a user agent needs to request resources included in a page, or fetched by scripts that it executes, then the origin of the page may be included in the request.

    But your error message is: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'

    If you want to send the cookie from the client to the server use the credentials: include option for fetch. Then don't set the origin to wildcard '*' and set the credentials: true for cors middleware.

    E.g.

    server.ts:

    import express from 'express';
    import cors from 'cors';
    
    const app = express();
    
    const corsOptions = {
      // set origin to a specific origin.
      origin: 'http://localhost:3000',
      
      // or, set origin to true to reflect the request origin
      //origin: true,
    
      credentials: true,
      optionsSuccessStatus: 200,
    };
    
    app.use(cors(corsOptions));
    
    app.post('/api/login', (req, res) => {
      res.cookie('jwt', '123', { httpOnly: true })
      res.status(200).json({ message: 'Logged in successfully' });
    })
    
    app.listen(8080, () => {
      console.log('Example app listening on port 8080!')
    })
    

    client/index.html:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    
    <body>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.4.0/axios.min.js"
        integrity="sha512-uMtXmF28A2Ab/JJO2t/vYhlaa/3ahUOgj1Zf27M5rOo8/+fcTUVH0/E0ll68njmjrLqOBjXM3V9NiPFL5ywWPQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
      <script>
        window.onload = function () {
          // use fetch
          fetch('http://localhost:8080/api/login', { method: 'POST', credentials: 'include' }).then(res => res.json()).then(data => {
            console.log(data)
          })
    
          // use axios
          axios.post('http://localhost:8080/api/login', {
            withCredentials: true
          }).then(res => {
            console.log(res.data)
          })
        }
      </script>
    </body>
    
    </html>
    

    Start the API server:

    $ npx ts-node server.ts 
    Example app listening on port 8080!
    

    Start the web server:

    $ npx http-server -p 3000 ./client/
    Starting up http-server, serving ./client/
    Available on:
      http://127.0.0.1:3000
      http://192.168.174.79:3000
    

    Access the front end using URL: http://localhost:3000

    Console logs:

    {message: 'Logged in successfully'}