todd
todd

Reputation: 51

CORs error AFTER deploying React App and separate Node API to production Unbuntu server where React static files are servered with NGINX

Situation:

I have an Express\Node server using the CORs middleware module and a separate React application generated with Create-React-App. The React app makes API calls to the Node server to perform simple CRUD operations. Pretty straight forward stuff and works as expected when running locally. I even get CORs errors locally if I remove the CORs middleware. I'm running Node locally with 'nodemon src/server.js' and React with 'react-scripts start'.

Node is running on port 8000

React is running on port 3000

PROBLEM:

Once deploying both the Node server and React app to production, which is an Ubuntu server with NGINX, I get a CORs error when calling the Node API from the React app

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/api/auth/login. (Reason: CORS request did not succeed)

The production Node server is running with PM2 and works as expected when calling it from Postman. The PM2 evosystem.config.js file looks like this

module.exports = {
  apps : [
    {
      name: "commonapi",
      script: "./src/server.js",
      env: {
        "PORT": 8000,
        "NODE_ENV": "production",
      },
    }
  ]
};

For the production React app, I've done a production build and moved the static files to an NGINX in /var/www/mysite. The NGINX config for the React app server block started like this:

server {
        listen 80;
        listen [::]:80;

        root /var/www/mysite/build;
        index index.html index.htm;

        server_name mysite.com www.mysite.com;

        location / {
                try_files $uri $uri/ /index.html;
        }
}

The login page is rendered as expected. The CORs error is thrown when the React front-end makes a call to the Node API at localhost:8000 to authenticate the user.

I tried to address this issue by adding Access Control headers to my server block in the NGINX config but unfortunately, this has not worked.

server {
        listen 80;
        listen [::]:80;

        root /var/www/mysite/build;
        index index.html index.htm;

        server_name mysite.com www.mysite.com;

        location / {
     
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-   Since,Cache-Control,Content-Type,Range';
        #
        # Tell the client that this pre-flight info is valid for 20 days
        #
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }
     if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }
     if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
     }
             try_files $uri $uri/ /index.html;
        }
}

Partial Node Server Code:

require('dotenv').config();

const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const helmet = require('helmet');
const { NODE_ENV } = require('./config');
const ResearchRouter = require('./research/research-router');
const usersRouter = require('./users/users-router');
const authRouter = require('./auth/auth-router');

const app = express();

const morganOption = (NODE_ENV === 'production')
    ? 'tiny'
    : 'common';

app.use(morgan(morganOption));
app.use(helmet());
app.use(cors({
    origin : '*',
    "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
    "preflightContinue": false,
    "optionsSuccessStatus": 204
}));

app.use('/api/auth', authRouter);
app.use('/api/register', usersRouter);
app.use('/api/research', ResearchRouter);

app.use(function errorHandler(error, req, res, next) {
    let response
    if (NODE_ENV === 'production') {
        response = {error: {message: 'server error'}}
    } else {
        console.error(error)
        response = {message: error.message, error}
    }
    res.status(500).json(response)
})

module.exports = app;

Upvotes: 2

Views: 4201

Answers (1)

todd
todd

Reputation: 51

I was able to fix this issue and ultimately the problem was with the front-end environment variables and the NGINX config. I also added certs from LetsEncrypt, which was not part of the problem but needed to be done.

For the front-end:

The API location in the environment variable file .env.production was changed from

'REACT_APP_API_ENDPOINT=http://localhost:8000'

to

REACT_APP_API_ENDPOINT='https://example.com'

Then I rebuilt the project with react-scripts build and deployed(copyed) the static files to NGINX's /var/www/ folder.

The NGINX config was updated as follows:

server {

        ## This server block is listening on port 443 and
        ## encrypts all connection with SSL certs from LetsEncrypt
        listen [::]:443 ssl ipv6only=on; # managed by Certbot
        listen 443 ssl; # managed by Certbot
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

        ## Points to Create-React-App production build static files
        root /var/www/example.com/build;
        index index.html index.htm;

        server_name example.com www.example.com;

        ## Requests from the browser for React app resources are handled here
        location / {
          try_files $uri $uri/ /index.html;
       }

       ## Api call from the React app to the Node server are handled here
       location /api {
         proxy_pass http://localhost:8000; #or whatever port your node server runs on
         proxy_http_version 1.1;
         proxy_set_header Upgrade $http_upgrade;
         proxy_set_header Connection 'upgrade';
         proxy_set_header Host $host;
         proxy_cache_bypass $http_upgrade;
    }
}
server {

        ## This server block handles requests coming in on port 80
        ## and redirects them to port 443 in the server block above
        ## so the connections can be encrypted
        listen 80;
        listen [::]:80;

        if ($host = www.example.com) {
          return 301 https://$host$request_uri;
        } # managed by Certbot

        if ($host = example.com) {                                                                                                                                       return 301 https://$host$request_uri;
        } # managed by Certbot

    server_name example.com www.example.com;
    return 404; # managed by Certbot
}

Upvotes: 3

Related Questions