NGINX gives us the possibility to use user certificates to authenticate web sites, even sites that are being intercepted by proxy. This is a very interesting alternative to protect web sites that do not have adequate or obsolete authentication mechanisms, such as HTTP basic authentication.

For this configuration we need to have a CA certificate accessible to NGINX, against which the user certificates will be validated. In addition, users must have their user certificates signed by the above CA installed in their browsers and computers.

CA creation

Private key

openssl genrsa -des3 -out ca.key 4096

Certificate

openssl req -new -x509 -days 365 -key ca.key -out ca.crt

Creation of the client certificate

Private key

openssl genrsa -des3 -out user.key 4096

Certificate request

openssl req -new -key user.key -out user.csr

Certificate signing by the CA

openssl x509 -req -days 365 -in user.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out user.crt

Certificate and key packaging

openssl pkcs12 -export -out user.pfx -inkey user.key -in user.crt -certfile ca.crt

This file, user.pfx will be the one that each user installs in his/her browser to authenticate against the server or the proxy.

NGINX configuration

server {
  listen 80;
  
  server_name server.example.com;

  return 301 https://server.example.com$request_uri;
}
server {
  listen 443 ssl;
  
  ssl_certificate /usr/local/etc/ssl/fullchain.pem;
  ssl_certificate_key /usr/local/etc/ssl/privkey.pem;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers HIGH:!aNULL:!MD5;
  
  ssl_client_certificate /usr/local/etc/nginx/ca.crt;
  ssl_verify_client optional;

  server_name server.example.com;

  location / {
    if ($ssl_client_verify != SUCCESS) {
      return 403;
    }
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;

    # Fix the "reverse proxy broken" error.
    proxy_pass          http://localhost:8080;
    proxy_read_timeout  90;
    
    # Web sockets
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_redirect      http://localhost:8080 https://server.example.com;
  }
}

This configuration redirects HTTP to HTTPS on the one hand, and on the other hand already in HTTPS performs the validation of the client certificate, returning a status code 403 NOT FOUND, if the certificate is not present, or is invalid.

Note: In this case the server is a **proxy** since the target server does not have HTTPS, so it is this instance of **NGINX** that performs the **SSL offload** and also takes care of the user validation that we commented.