Simple Web-only SSO with NGINX, PHP and Basic Auth

About

This is a guide on how to make a simple SSO solution for your webservices.
This guide uses PHP, .htpasswd files and the webserver NGINX.
NGINX handles the authentication and lets the applications know which users are logged in.
This works for: Jenkins, MediaWiki, MantisBT, Kanboard, Nextcloud.
This does not work for: git servers. Git needs a different authentication method, Basic Auth doesn't work, because for pushing you can't use basic auth.
For a service to work with this method it needs to support "Pre Basic Authentication". The service gets only the username of the logged-in user, so it has to support this.

Problem

There is a NGINX webserver as a reverse-proxy for 3 web-based services: Kanboard, MantisBT and MediaWiki.
The Problem: You have to login into every application one by one each time you want to work with them.
This means every platform has their own database for users and each user has a different password for each service.

Everything is on one webserver and managed by the same system, so why not combine them?
This brings us to another problem: Radius/Active Directory take a lot of work to setup. Too much work for a small one-server system.
I read about SSO, but it's a lot to learn and setup for only 3 mini-web-services.

Solution

The solution to this is NGINX's built-in basic-authentication.
You can let NGINX handle the authentication and just send the username to the service.

Warning: This is only secure if your web-service is only accessible via NGINX and has HTTPS enabled! For example: Jenkins should only be accessible through port 443 (https) and NOT port 8080 (outside of nginx via http).

Create the .htpasswd file in the web root folder. Then add the "deny access" block for your .htpasswd file to your config file in nginx:

sudo htpasswd -c /var/www/html/.htpasswd chris

sudo nano /etc/nginx/sites-enabled/<yourfile>
  
  location = /.htpasswd {
    deny all;
    return 404;
  }
Now you can - for example - make a folder login-only by adding the following in your nginx server block config:
location /wiki/ {
  auth_basic "Please Login";
  auth_basic_user_file /var/www/html/.htpasswd;
}
Now comes the auto-login/single-sign-on part: NGINX can forward your authenticated username to backend applications like the following for PHP:
location ~ \.php$ {
  fastcgi_param REMOTE_USER $remote_user;
  include snippets/fastcgi-php.conf;
  fastcgi_pass unix:/run/php/php7.3-fpm.sock;
}
Every PHP file you visit gets your username by nginx. This is only save to use if there is no other method of visiting this website except nginx.

Now you can install an Addon that lets you use "preauthenticated" / "basic-auth" to set the user. For mediawiki and Kanboard there are already addons available for that. (Even Jenkins has an addon for this)

If you write a .php file that prints out your headers (example: <?php print_r($_SERVER); ?>) you can read the following:
Array ( ...  [REMOTE_USER] => Chris ... )
This means that our REMOTE_USER setting worked and the PHP applications behind your nginx webserver now can use this username to automatically log you in your user.

An example of this:

Ofcourse this only works for websites behind nginx, but it's a nice way of not making your team sign in to every application each time they try to use it.

Managing Users

For managing these basic authentication passwords I recommend this simple one-file-based PHP file: htpasstool (local download: htpasstool.zip)
With this file you can create new users for .htpasswd files. These users will then automatically be created and logged in in your backend websites.
Warning: Be sure to make this file password-protected from normal users!

To protect the file you can do the following (file is example.com/htpasstool.php):

location = /htpasstool.php {
  if ($remote_user != "Administrator"){
    return 404;
  }
}
Now only the Administrator user can see and use the htpasstool file.

Example Screenshot of the application:

Logout Problems

One problem this method has: Basic Authentication doesn't have a simple way to logout.

To actually make a logout functionality you have to delete the browsers basic auth cache.
This used to work by respond with an 401 http error code, but this doesn't work anymore.
The easiest method now is to fake-login with a non-existent user and the browser will delete the old (working) authentication.
To make a logout-functionality I use this as a link:

<a href="https://a@example.com">Logout</a>
Basic Authentication can be used as user:pass@example.com. If you do that with a new (non-existant) user while logged-in it will forget the old user.