Implement HTTPS for API Access on localhost with Nginx 
Tags: nginx, certificate, api
Published: 2019-10-09 08:25:49

---
title: Implement HTTPS for API Access on localhost with Nginx 
published: true
description: Feedback after securing my webapp locally
tags: nginx, certificate, api
---

Our open source app, [FlexOffice](https://github.com/BREDFactory/flex-server) which provides employees a way to find and book a desk in, has a technical stack based on React with Typescript, Node and mongodb.

Our app is composed of two-parts : a web Front which access resources through JSON calls to RestAPI provided by a web back-end.
Our production target is to have a Front-end with a different URL than the Back-end. Front-end makes a *cross-domain request* to access back-end resources. As such, we are assigned to follow [CORS](https://www.moesif.com/blog/technical/cors/Authoritative-Guide-to-CORS-Cross-Origin-Resource-Sharing-for-REST-APIs/) policy by specifying the allowed *origin* URL to get resources.

As we have at least two different ports on localhost, serving each the Front and the Back end, we make *de facto* cross-domain requests when accessing back-end APIs.
 
In this post, we'll use a radically different domain name for our back-end : `local.web.com`, but you're free to choose another one, and we'd like to secure API access and test it locally.

To attach this domain name to our IPv4 loopback (127.0.0.1), you must add a record on `/etc/hosts`as such :

```
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1       localhost
127.0.0.1       local.web.com 
(...)
```

To use HTTPS protocol, we need local self-signed certificates, plus a root CA certificate which will give full confidence to our browsers (with a green lock).

Let's create our local certificates :
Let's just follow this [great gist here](https://gist.github.com/cecilemuller/9492b848eb8fe46d462abeb26656c4f8):

Generate :
The root certificate : `RootCA.crt`
The key : `RootCA.key`
The pem : `RootCA.pem`

```
$ openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=FlexOffice-Root-CA"
```
```
$ openssl x509 -outform pem -in RootCA.pem -out RootCA.crt
```


First, create a file `domains.ext` that lists all your local domains

```
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = local.web.com
```

Generate `localhost.key`, `localhost.csr`, and `localhost.crt`:

```
$ openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=FR/ST=IDF/L=Paris/O=FlexOffice-Certificates/CN=local.web.com"
```
```
$ openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile domains.ext -out localhost.crt
```

Once you get your certificate, you can declare them in `nginx.conf`.
 
The back end is accessible locally at `http://local.web.com:3000`, but we'd like to avoid that and force API users to pass through `https://local.web.com:443`. Our Front is located at `http://local.web.com:1025`, and must be authorized to access the Back-end. This can be done by adding a header with `Access-Control-Allow-Origin` valued with the Front URL a.k.a the 'origin' of the request.

```
# nginx Configuration File
	# http://wiki.nginx.org/Configuration

	
	# Run as a less privileged user for security reasons.
	#user nginx;
	
	worker_processes auto;
	
	events {
		worker_connections 1024;
	}
	
	#pid /var/run/nginx.pid;
	
	http {
	
        sendfile        off;

        upstream backend {
            server local.web.com:3000;
        }        

	#Redirect to https, using 307 instead of 301 to preserve post data
		server {
			listen 80 default_server;
			server_name _;
			return 307 https://$host$request_uri;
		}
	
		server {
			listen 443 ssl ;
			
			server_name local.web.com;
			
			# Protect against the BEAST attack by not using SSLv3 at all. If you need to support older browsers (IE6) you may need to add
			# SSLv3 to the list of protocols below.
			ssl_protocols TLSv1.2 TLSv1.1 TLSv1 ;

			
			# Ciphers set to best allow protection from Beast, while providing forwarding secrecy, as defined by Mozilla - https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx

			ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
			ssl_prefer_server_ciphers on;
			
			# Optimize SSL by caching session parameters for 10 minutes. This cuts down on the number of expensive SSL handshakes.
			# The handshake is the most CPU-intensive operation, and by default it is re-negotiated on every new/parallel connection.
			# By enabling a cache (of type "shared between all Nginx workers"), we tell the client to re-use the already negotiated state.
			# Further optimization can be achieved by raising keepalive_timeout, but that shouldn't be done unless you serve primarily HTTPS.
			ssl_session_cache shared:SSL:10m; # a 1mb cache can hold about 4000 sessions, so we can hold 40000 sessions
			ssl_session_timeout 24h;
			
			
			# Use a higher keepalive timeout to reduce the need for repeated handshakes
			keepalive_timeout 300; # up from 75 secs default
			
			# remember the certificate for a year and automatically connect to HTTPS
			#add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';
            add_header Strict-Transport-Security 'max-age=0;';
			
			ssl_certificate /<CERTIFICATE_PATH>/<your_certificate>.crt; // <- CHANGE IT !
			ssl_certificate_key /<KEY_PATH>/<your_key>.key; // <- CHANGE IT !

            access_log /<LOG_PATH>/<your_log>.log; // <- CHANGE IT !
			
			location / {

                add_header 'Access-Control-Allow-Origin'  '*' always;
				add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
					#
					# Custom headers and headers various browsers *should* be OK with but aren't
					#
        		add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;				add_header 'Access-Control-Expose-Headers' 'Access-Control-Allow-Origin';
				add_header    'Access-Control-Allow-Credentials' 'true' always;

				proxy_pass http://local.web.com:3000;
				proxy_set_header Connection "";
				proxy_set_header Host $host;
				proxy_set_header X-Real-IP $remote_addr;
				proxy_set_header X-Forwarded-For $remote_addr;

				if ($request_method = 'OPTIONS') {
                    
					#
					# Tell client that this pre-flight info is valid for 20 days
					#
					add_header 'Access-Control-Max-Age' 1728000;
					add_header 'Content-Type' 'application/json;';
					#add_header 'Content-Length' 0;
					return 204;
				}
				if ($request_method = 'POST') {
                    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
				}
				if ($request_method = 'GET') {
                    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
				}
				proxy_pass_request_headers      on;
			}
			location /api/ {

				proxy_pass http://local.web.com:3000;

				proxy_set_header X-Real-IP $remote_addr;
				proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
				proxy_set_header authorization $http_authorization;
				proxy_pass_request_headers      on;
				proxy_no_cache $cookie_nocache  $arg_nocache$arg_comment;
				proxy_no_cache $http_pragma     $http_authorization;
				proxy_cache_bypass $cookie_nocache $arg_nocache $arg_comment;
				proxy_cache_bypass $http_pragma $http_authorization;

					#
					# Custom headers and headers various browsers *should* be OK with but aren't
					#

				if ($request_method = 'OPTIONS') {
                    
					#
					# Tell client that this pre-flight info is valid for 20 days
					#
					add_header 'Access-Control-Max-Age' 1728000;
					add_header 'Access-Control-Allow-Origin'  'http://local.web.com:1025' always;
					add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
        			add_header 'Access-Control-Allow-Headers' 'authorization, Origin, X-Requested-With, Content-Type, Accept' always;
					add_header  'Access-Control-Allow-Credentials' 'true' always;
					return 204;
				}
				if ($request_method = 'POST') {
                    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
					add_header 'Access-Control-Allow-Origin'  'http://local.web.com:1025' always;
					add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
        			add_header 'Access-Control-Allow-Headers' 'authorization, Origin, X-Requested-With, Content-Type, Accept' always;
					add_header    'Access-Control-Allow-Credentials' 'true' always;
				}
				if ($request_method = 'GET') {
                    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
					add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
					add_header 'Access-Control-Allow-Origin'  'http://local.web.com:1025' always;
					add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
        			add_header 'Access-Control-Allow-Headers' 'authorization, Origin, X-Requested-With, Content-Type, Accept' always;
					add_header    'Access-Control-Allow-Credentials' 'true' always;

				}

			}
						
		}
	}



```
Reload or stop and restart nginx.
You could run `nginx -t` to test your configuration file and verify the file path. 
```
$ nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
```

Start nginx
```
$ nginx
```
You can now call your back-end through HTTPS. 

Please feel free to comment or suggest improvements.


Tested with environment :
macOS v10.13.6 (High Sierra)
node v10.15
npm v6.4.1
MongoDB shell version v3.6.6
curl 7.60.0
nginx v1.5.7
Firefox Quantum 69.0.2 (64 bits)
Chrome v77.0.3865.90 (official Build) (64 bits)
Safari v13.0.1 (13608.2.11.1.10)

Useful resources :

######[1] CORS, detailed explanation, https://www.moesif.com/blog/technical/cors/Authoritative-Guide-to-CORS-Cross-Origin-Resource-Sharing-for-REST-APIs/#
######[2] Root CA creation, https://gist.github.com/cecilemuller/9492b848eb8fe46d462abeb26656c4f8