Installing Authentik for Authentication and Single-Sign-On

The OER Foundation offers many Free and Open Source Software services to our learners and educators to provide them with resources for learning, developing Open Educational Resources (OER), and collaborating - both professionally and socially - with one another. Due to this proliferation of 'point source' technologies, users have to create a myriad of user accounts, each requesting an email and password and perhaps a user name. For many, especially those who have not adopted a password keeper, this can be an onerous situation, and often results in very poor 'internet hygiene' due to people choosing simplistic passwords and using the same small set of passwords in many contexts.

An alternative to this default approach is for the OER Foundation to adopt an authentication and authorisation solution. Authentication means creating a single link between a real (physical) person and their online (virtual) identity - usually managed with a login and password and might also include one or more additional factors of identification, also known as 'Multi-Factor Authentication' or MFA. Authorisation involves allowing authenticated users access to systems to which they should have access, and not those to which they should not. An suitable authentication and authorisation system can then be used to implement a 'Single Sign-On' (SSO) system that provides the option to all those with credentials on it to access - subject to any authorisation limitations - any of the OER Foundation's services transparently without needing to create a new, separate account.

The Authentik project offers quite good documentation for Docker Compose installation, too. This tutorial should be seen as a complement to that, perhaps providing a bit more guidance.

Preparing a suitable server

All of our OER Foundation services share a common hosting platform. You'll need to create a replica of that platform - if you don't already have one - so you can add this service. Everything else in this post assumes you've followed those instructions!

Note, the Authentik developers suggest a minimum VPS specification of 2 CPU cores and 2 GB RAM, so a very small instance should suffice.

Ground work

To set up Authentik, you'll need a few key details specific to your instance. For the rest of this tutorial, we'll use the convention of representing those variables as a name inside [], for example, the domain name that identifies your instance, which is referred to by the token [domain name]. Note - you'll see a few other instances of [] in the content below - the ones with multiple ::: in them, e.g. [:::], are not tokens, they're the way IPv6

The tokens for which you'll need values are as follows:

  • your instance [domain name], i.e. a fully qualified domain name or a sub domain by which people will reach your instance. In our case, for example, we use 'auth.fossdle.org', which is the 'auth' subdomain of the 'fossdle.org' fully qualified domain name.
  • Authenticating SMTP (Simple Mail Transfer Protocol, i.e. the language for sending email) details - this is required so your services can send emails to users - crucial things like email address validation and password recovery emails...
    • [smtp host] - the domain name or IPv4 or IPv6 address of an SMTP server
    • [smtp port] - the port number on the server that is listening for your connection. By convention it's likely to be 465 or 587, or possibly 25.
    • [smtp reply-to-email] - a monitored email to which people can send email related to this WordPress site, e.g. notifications@[domain name]
    • [smtp username] - the username (often an email address) used to authenticate against your SMTP server, provided by your email provider.
    • [smtp password] - the accompanying password, provided by your email provider.
    • [smtp from email] - this is the email address from which the system emails will be sent - it's what recipients will see and the address to whom they'll be able to respond. You might want something like 'noreply@[domain name]'... or maybe (if you do accept responses) 'webmaster@[domain name]'.
  • [postgresql password] - a strong random password, say 12-99 characters long, sticking with letters and numbers as some symbols might be mis-interpreted in the context of the configuration file in which it's stored (e.g. if it contains a '#', the rest of the password might be interpreted as a comment. Or if you enclosed it in "", if it includes a " as a symbol, it might be interpreted as prematurely closing your double quotes). To generate, see below.
  • [authentik secret] - a random 'secret key' of 50 characters is recommended. Again, see below.

To generate (relatively) random passwords/keys, you can install the following:

sudo apt install -y pwgen

To generate, say, a 40 character [postgresql password], you could run

pwgen -s 40 1

and grab the output, e.g. 28RFCIFtnVM9tFOmT2zx2fKiK5sf42WGK8FHr9Bn

For a 50 character [authentik secret] just copy the output of

pwgen -s 50 1

Learn more about specifying random passwords via man pwgen or pwgen --help and look at our tutorial on creating good passwords.

Also, you'll need to edit files, so it's worth setting up a text editor. If you're new to Linux, I recommend using the 'nano' text editor which is installed by default on Ubuntu Linux systems. It's fairly simple, and all of its options are visible in the text-based interface. I tend to use a far more powerful but far less beginner-friendly editor called 'vim'. There're other editors people might choose, too. To use your preferred editor for the rest of the tutorial, enter the following to set an environment variable EDIT, specifying your preferred editor, e.g.:

EDIT=$(which nano)

or, if you're like me

EDIT=$(which vim)

so that subsequent references to $EDIT will invoke your preferred editor. Note the command $(which nano) is a script which finds the full path to the named command, in this case 'nano'. Putting a command inside the $() means 'replace with the value the script returns', so it sets the value of EDIT to the path of the nano command in this case. On my current machine, the value it returns is /usr/bin/nano, which is pretty typical.

To test (at any time) whether you session still knows your $EDIT command, run

echo $EDIT

if it returns the path to your preferred editor, you're good to go. If not, just reassert (run again) the EDIT= line from above!

Note: if you log out and back in again, change users, or create a new terminal tab/session, you'll need to reassert the EDIT value.

Configuring the reverse proxy

We need to configure a 'reverse proxy' so that external requests to our new Authentik service are routed to our containers, and so that all communication between our service's users and our service are suitably encrypted via Secure Sockets Layer (SSL).

You should already have Nginx and Let's Encrypt installed on your server, so all you need to do is set up a new configuration file. The convention I use would involve doing this:

sudo $EDIT /etc/nginx/sites-available/[domain name]

where you put your domain name as the filename as indicated. You can copy and paste the following into the configuration file, replacing tokens like [domain name] with your value:

# Upstream where your authentik server is hosted.
upstream authentik {
    server 127.0.0.1:9443;
    # Improve performance by keeping some connections alive.
    keepalive 10;
}
 
# Upgrade WebSocket if requested, otherwise use keepalive
map $http_upgrade $connection_upgrade_keepalive {
    default upgrade;
    ''      '';
}
 
server {
    # HTTP server config
    listen 80;
    listen [::]:80;
    server_name [domain name];
 
    include includes/letsencrypt.conf;
 
    access_log /var/log/nginx/[domain name]_access.log;
    error_log /var/log/nginx/[domain name]_error.log;
 
    # 301 redirect to HTTPS
    location / {
            return 301 https://$host$request_uri;
    }
}
server {
    # HTTPS server config
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name [domain name];
 
    # TLS certificates
    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
    #ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;
    #ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;
    # to create this, see https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
 
    access_log /var/log/nginx/[domain name]_access.log;
    error_log /var/log/nginx/[domain name]_error.log;
 
    # Proxy site
    location / {
        proxy_pass https://authentik;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade_keepalive;
    }
}

Note that if your server has other services on it and, by coincidence, one is already using port 9443, then you can pick another port (e.g. 9444) and see if that works. If so, you'll need to make sure that you replace 9443 in the docker-compose.yml below, too.

Once you've saved the file, you can 'enable' it straight away. To do that, you need to do the following (again, replacing the tokens):

sudo ln -sf /etc/nginx/sites-available/[domain name] /etc/nginx/sites-enabled

which links the configuration file you've just created into the 'sites-enabled' directory. You can then test whether Nginx accepts the configuration you've added:

sudo nginx -t

If there're no errors here, you can make the changes live:

sudo service nginx reload

Getting your Let's Encrypt SSL certificate

You can now request an SSL certificate for your new domain (making use of the configuration set up the server tutorial), obviously replacing [domain name] with yours:

`sudo letsencrypt certonly --webroot -w /var/www/letsencrypt -d [domain name]

After you've got your certificate (which should only take a few seconds), you can go back into your proxy configuration (remember it's the same file as /etc/nginx/sites-enabled/[domain name]):

sudo $EDIT /etc/nginx/sites-available/[domain name]

and alter the certificates by changing just the following lines:

    # TLS certificates
    #ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    #ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
    ssl_certificate /etc/letsencrypt/live/[domain name]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[domain name]/privkey.pem;

Then you just need to make sure there're no typos:

sudo nginx -t

and make the changes live:

sudo service nginx reload

Now external people will be able to securely access your Authentik once the service is running!

Docker Compose configuration

The next thing you need is a docker-compose.yml file. I usually put this in the a directory called /home/docker/[domain name] (replacing the token with your domain name!). You'll want to be in that directory:

cd /home/docker/[domain name]

Because the developers of Authentik periodically change the default configuration of their docker-compose.yml file, it's best to grab their current one from here:

wget https://goauthentik.io/docker-compose.yml

which should result in a file called docker-compose.yml appearing in that directory (run ls -l to confirm).

I'd make a backup of this file for future reference:

cp docker-compose.yml docker-compose.yml-default

You can then

$EDIT docker-compose.yml

and it should look similar to the one below, albeit probably with a different AUTHENTIK_TAG number - the version as of this writing is 2023.10.2 - and with different 'volumes' specified.

The only things you'll likely need to change are the designations for the 'volumes:'. See below where I've specified volumes in your /home/data/[domain name] directory rather than the local directory as the default docker-compose.yml defaults to.

version: '3.4'
 
services:
  postgresql:
    image: docker.io/library/postgres:12-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
    volumes:
      - /home/data/[domain name]/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${PG_PASS:?database password required}
      - POSTGRES_USER=${PG_USER:-authentik}
      - POSTGRES_DB=${PG_DB:-authentik}
    env_file:
      - .env
  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - /home/data/[domain name]/redis:/data
  server:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.2}
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
    volumes:
      - /home/data/[domain name]/media:/media
      - /home/data/[domain name]/custom-templates:/templates
    env_file:
      - .env
    ports:
      - "127.0.0.1:${AUTHENTIK_PORT_HTTP:-9080}:9080"
      - "127.0.0.1:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.2}
    restart: unless-stopped
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
    user: root
    volumes:
      - /home/data/[domain name]/media:/media
      - /home/data/[domain name]/certs:/certs
      - /var/run/docker.sock:/var/run/docker.sock
      - /home/data/[domain name]/custom-templates:/templates
    env_file:
      - .env

One you've got that saved, there's just one thing left! You just need to set up your specific configuration file, which is held in a 'hidden' file, .env in /home/docker/[domain name].

This is what you should put in it, replacing [tokens]. Note that you can comment out any line you want by putting a '#' at the start of the line.

Note that if you set the AUTHENTIK_TAG below and uncomment the line (remove the #) you won't (normally) need to update the docker-compose.yml file to apply updates to Authentik in future.

# AUTHENTIK_TAG=2023.10.2
PG_PASS=[postgresql password]
AUTHENTIK_SECRET_KEY=[authentik secret]
AUTHENTIK_ERROR_REPORTING__ENABLED=true
# SMTP Host Emails are sent to
AUTHENTIK_EMAIL__HOST=[smtp host]
AUTHENTIK_EMAIL__PORT=[smtp port]
# Optionally authenticate (don't add quotation marks to your password)
AUTHENTIK_EMAIL__USERNAME=[smtp username]
AUTHENTIK_EMAIL__PASSWORD=[smtp password]
# Use StartTLS
AUTHENTIK_EMAIL__USE_TLS=true
# Use SSL
AUTHENTIK_EMAIL__USE_SSL=false
AUTHENTIK_EMAIL__TIMEOUT=10
# Email address authentik will send from, should have a correct @domain
AUTHENTIK_EMAIL__FROM=[smtp from email]
AUTHENTIK_PORT_HTTP=9080
AUTHENTIK_PORT_HTTPS=9443

Once that's done and saved, you can start your Authentik service!

docker-compose up -d && docker-compose logs -f

This will trigger Docker to download the container images specified in your docker-compose.yml (based on AUTHENTIK_TAG), and start them up. That, in turn, will cause Docker Compose to create the directories specified in your volumes: stanzas in your docker-compose.yml file in /home/data/[domain name], including creating your PostgreSQL database.

Boom: Setting up your Authentik instance

Once that is up and running, you should be able to point your browser at

https://[domain name]/if/flow/initial-setup/

Here's more documentation to help you from here. Also, cooptonian's YouTube Channel is most helpful!

Upgrading your Authentik

Periodically, you'll see in the admin interface (or via an email to the site's administrators) that your Authenitik can be upgraded to a newer version.

Before doing anything to upgrade, I encourage you to make a copy of both your key files:

DATE=$(date +%d-%m-%Y) cp docker-compose.yml docker-compose.yml-$DATE cp .env .env-$DATE`

because having them handy might save your bacon if, for example, you need to revert back to your old configuration.

Sometimes the Authentik developers change the configuration of their default docker-compose.yml file. You'll want to verify that your version is the same except for any changes to volumes: you've made as follows:

Download the current reference version as docker-compose.yml-default (so it doesn't overwrite your docker-compose.yml):

wget -O docker-compose.yml-default https://goauthentik.io/version/2023.10/docker-compose.yml

Then, you'll want to run

diff docker-compose.yml-default docker-compose.yml

to see how the new file differs. If it differs in areas other than volumes: and the value for AUTHENTIK_TAG, you'll want to apply those changes to your docker-compose.yml via

$EDIT docker-compose.yml

Also, because upgrades might well result in a change to your PostgreSQL database's schema, you'll want a full backup of the existing database!

Once you have that, you can run

docker-compose pull && docker-compose up -d && docker-compose logs -f

to pull the new containers, run them, and watch the logs of the upgrade as it progresses and, hopefully, completes successfully.

Backing up your Authentik data

At the OER Foundation, we recognise that our Authentik is pretty mission-critical. To protect our users' data integrity and privacy, we do nightly encrypted incremental file backups and hourly PostgreSQL database backups. For the former, we use a Restic remote server script we've written (which requires that you have either sufficient local storage, or (preferably) another server you can access via SSH which has sufficient disk space to hold versioned backups), and the latter, we use a backup script we've developed.

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
CAPTCHA
13 + 7 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
Are you the real deal?