Nginx basic authentication

Hi,

I have node-red running behind a nginx proxy. I also have a couple of other things I want to put behind nginx too, including homebridge and mosquitto_sub+frontail for viewing MQTT logs.

I would really like to have nginx perform HTTP Basic Authentication on HTTPS to limit access to node-red (and the other systems) but I keep running into problems:

  • On Chrome is repeatedly asks for the same password for every resource it tries to load, even though everything is on the same realm.
  • It works better on Safari, but the Websocket requests fail (I presume because Safari isn't passing through the basic auth for the Websocket request)

In the past I have used mutual TLS for authentication (client cert). This worked a bit better but again WebSockets+client cert didn't work in Safari. I may end up going back to this approach.

Using the auth_request module in nginx is a possibility but it seems overkill. Particularly if I have to have separate process/server just validate requests. It does seem likely that using a cookie based authentication is more likely to work with all of Chrome/Safari and Websockets.

Is there an obvious solution that I am missing?

Here is my nginx config:

server {
    server_name  foo.example.com;

    ssl_certificate /var/lib/dehydrated/certs/foo.example.com/fullchain.pem;
    ssl_certificate_key /var/lib/dehydrated/certs/foo.example.com/privkey.pem;
    include "/etc/nginx/snippets/modern-tls.conf";

    # Require password
    auth_basic "Enter the Password";
    auth_basic_user_file /etc/nginx/htpasswd;

    root /srv/www/example;

    index index.html;

    # Redirect to the primary hostname
    if ($host != $server_name) {
        rewrite ^ $scheme://$server_name permanent;
    }

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }

    # Proxy to node-red
    location /node-red {
        proxy_set_header Host $http_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;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://localhost:1880;
    }
}

There are a few things here, not sure I know how to answer them all.

Taking websockets. You need some more configuration to handle wss: - remember that ws(s) initially connects over http(s) and then "upgrades". To proxy this, you need some more config. You probably also need to restrict which versions of TLS and HTTP NGINX needs to serve. I see you have some of it, not sure you have everything?

This article I think has the right details though I've not actually checked it: How to Proxy WSS WebSockets with NGINX - Serverlab

And this may help with the basic auth? Again, not tested it: security - How to make websocket authentication with Nginx. (I'm using JWT with "auth_request" module) - Stack Overflow

Thanks for replying to my thread Julian.

The WebSockets part is all working fine if I remove the basic authentication. The TLS settings are defined in my modern-tls.conf included file - this is taken from the very handy Mozilla SSL Configuration Generator.

The second link you gave is someone using JWT and nginx's auth_request. I suspect this is the method that is most like to work (otherwise how does anyone ever secure WebSockets). However it just seems massively overkill when all I want to do is protect a website something that only one person uses (me!).

I guess my main question was not what is wrong with my nginx config but has anyone else solved the general problem in a different way? It does seem that I am the only one trying to do this!

I will probably just go back to using client certs again and accept that it doesn't work in Safari.

Websockets security is certainly non-trivial given that you only get full access to headers on the initial connection. So the initial authentication is only a small part of the issue. Once you have upgraded the connection, you no longer have access to custom http headers which means that you can no longer continue to validate your JWT - so no session management. Not really very secure.

So I think the normal way is, once the connection upgrades, you have to pass the JWT in the websocket messages themselves. Remembering that JWT is not a security method, just a convenience. This is why uibuilder doesn't yet have a fully working security mechanism.

Basic auth simply doesn't cut it here.

I'm not building an enterprise login system! Just trying to prevent unauthorised people from accessing my home automation.

I forgot that there are web browsers other than Safari and Chrome. Just tried Firefox and it is working fine. So maybe I just use Firefox for now :thinking: