Skip to content

How to Set up CORS and Cookie-Session in Express

Express/

I have been working on an application with a Vue3 client and an Express server. The client, hosted on Netlify, is sending requests to the server, which is hosted on Heroku. The Express application uses Passport and cookie-session packages for authentication.

In this setup the client can’t properly communicate with the server without a specific configuration.

After running into many errors myself, I can help you avoid the headache. You will learn how to configure CORS and cookie-session to get cross-origin requests to work.

Setting up CORS

Without any configuration, the first problem you will run into is not having set “Access-Control-Allow-Origin” header. When making a request, your web browser will show an error message in your browser’s console.

Chrome:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Or, Firefox:
CORS header ‘Access-Control-Allow-Origin’ missing

To fix this issue, you will need to install cors package from NPM.

npm install cors

Open the file where you are setting up your Express application. Add the cors middleware to your application before you register any routes.

// index.ts

import express, { Application } from 'express';
import cors from 'cors';
import router from './src/routes';

const app: Application = express();
const port = process.env.PORT || 8000;

app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});
app.use(cors());
app.use(router);

If you are using cookie-based authentication, you need to include the cookie when sending requests from client to sever.

// client-side code making a request to server
const response = await fetch(`${apiUrl}`, {
  // ...other config
  credentials: 'include',
});

But including the cookie in the client is not enough. You face another error from the server.

Chrome
The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'

Or, Firefox
expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’

To fix this, you need to configure the cors middleware to pass “Access-Control-Allow-Credentials” header to requests.

app.use(
  cors({
    credentials: true,
  }),
);

Sending a request from the client now will unfortunately raise another error.

Chrome
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'.

Or, Firefox
Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’

You need to add the request origin address, which is your client, in your server’s cors configuration.

app.use(
  cors({
    credentials: true,
    origin: 'https://your-client-address.com',
  }),
);

If you have more than one client sending requests to your server, you can pass origin an array.

origin: ['https://your-client-address.com', 'https://another-address.io'],

When you use cookies for authentication, it is important to make them as secure as possible.

import cookieSession from 'cookie-session';

app.use(
  cookieSession({
    secret: 'yourSecret',
    sameSite: 'none',
    secure: true,
    httpOnly: true,
  }),
);

Use secure to allow Express to send cookies over HTTPS only. You should also use httpOnly to prevent client-side JavaScript from accessing your cookies.

Since you are sending cookies from one host to another, set sameSite to none.

Of course, this configuration is for production-ready environments that use HTTPS for communication. You will most likely use HTTP while developing locally.

You can use environment variables to configure cookie-session to use HTTP on your local host.

app.use(
  cookieSession({
    secret: 'yourSecret',
    secure: process.env.NODE_ENV === 'development' ? false : true,
    httpOnly: process.env.NODE_ENV === 'development' ? false : true,
    sameSite: process.env.NODE_ENV === 'development' ? false : 'none',
  }),
);

If you are wondering how to set environment variables, look into dotenv package.

Trusting Proxy

The last thing you should do when using hosting services, like Heroku, is to trust their proxy. It is important if you configured Express to send cookies over HTTPS only.

app.enable('trust proxy');

Heroku runs requests through reverse proxies that use HTTP even if the original requests used HTTPS. If you don’t trust the proxy, the client and server can fail to exchange authentication cookies.

Summary

You learned how to set up CORS to allow client requests coming from a host that differs from your server’s host. You also learned what settings to use for cookie-session to work in cross-origin requests. Finally, you learned about trusting your hosting service’s proxy.

// index.ts

import express, { Application } from 'express';
import cors from 'cors';
import router from './src/routes';

const app: Application = express();
const port = process.env.PORT || 8000;

app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

app.use(
  cookieSession({
    secret: 'yourSecret',
    secure: process.env.NODE_ENV === 'development' ? false : true,
    httpOnly: process.env.NODE_ENV === 'development' ? false : true,
    sameSite: process.env.NODE_ENV === 'development' ? false : 'none',
  }),
);

app.enable('trust proxy');

app.use(
  cors({
    credentials: true,
    origin: 'https://your-client-address.com',
  }),
);

app.use(router);