Custom local domain solves CORS and other issues

Problem

http://localhost:3000 can’t call your remote API because of browser-enforced limitations involving CORS, cookie sharing, and HTTPS.

Solution

Set up local.yourdomain.com on your dev machine so your browser will successfully call your remote service at api.yourdomain.com, for example.

How to set it up

1. Edit your hosts file

Add a custom host to your /etc/hosts file (location of hosts file varies by OS):

127.0.0.1    localhost
127.0.0.1    local.yourdomain.com

The domain you set here depends on the domain of the API. If, for example, the API server you’re trying to call is staging.api.yourdomain.com and that server’s CORS settings are configured to allow local.yourdomain.com then you would add local.yourdomain.com to the hosts file. If the server’s CORS config allows *.yourdomain.com then you could technically use any subdomain you want. The point is whatever your server CORS settings allow your local host needs to be a match.

Also, cookies’ domain should be set to this host (or at least set to the domain, e.g. .yourdomain.com) so the browser will pass cookies to the remote API.

2. Install nginx

This will vary by OS, but brew install nginx is recommended on macOS.

3. Install dev-nginx

Again, brew makes this easy. Run these two commands:

brew tap guardian/homebrew-devtools
brew install guardian/devtools/dev-nginx

Then in your app’s root directory create a file named nginx-mappings.yml and paste this in it:

name: your-domain
domain-root: yourdomain.com
mappings:
  - port: 3000 (or whatever your local app server is running on)
    prefix: local

Now cd to your app’s root if you’re not already there and then run:

dev-nginx setup-app nginx-mappings.yml

This creates an nginx config based on that YAML file, sets up a certificate for local HTTPS, and restarts nginx. You may need to restart you app server, but then you should be able to access https://local.yourdomain.com!

4. Other settings

If you can access your new local.yourdomain.com but the browser is still failing to call the remote API, there are a number of things to double-check. These include:

  • fetch's credentials: 'include'

  • fetch's mode: 'cors'

  • Any required cookies have a matching domain

  • The API server CORS settings using a wildcard (*) to allow any domain. This setting prevents browsers from sending auth tokens (i.e. Authorization header and cookies can't be sent).

Help me help you

I wanted to do this with as simple a setup as possible, but couldn't find anything explaining how to do this directly with an Apache server or nginx without the dev-nginx tool (not that the tool isn't good - it was a lifesaver!).

If you know how to achieve this without extra tools, please share in the comments and I'll update the article for others.