Authelia for MFA on docker

I am never going to state this is "the correct way", "the best way", or even "the only way". What I will state is that following these instructions provided me with a working environment with either Nginx Proxy Manager, or Caddy working as the reverse proxy on a self hosted Authelia deployment.

This started as a single page but became too large to manage, I've now split it up into these sections.

  1. This page: Authelia for MFA on docker
  2. Deploying test hosts for Authelia MFA authentication
  3. Configuring Nginx reverse proxy in front of Authelia
  4. Configuring Caddy reverse proxy in front of Authelia
  5. Logging in to Authelia

Authelia Background

Authelia is a great open source authentication and authorisation server that you can host yourself to provide increased security to any number of tools. There are examples out there for all types of services. However, whilst working through this to deploy in front of some of my hosted services I've found many of the Youtube Videos a bit misleading, or not explaining why something is configured the way it is shown.

I intend to write up how to use Authelia behind a reverse proxy, and will provide a link from here when it is finished.

I'm not about to try to present all of the various deployment options etc. I would recommend anyone looking for more in-depth answers look on the Authelia web site.

My aim here is to document how I have got it working, for personal reference. If this does assist others, then that is just an added bonus. I will not be detailing the deployment of the NGINX Proxy Manager itself, or Caddy in terms of docker files etc. I will though provide details of the configuration steps required to provide a working solution.

So, first things first we need a domain name to host our services. I will be using Milpixel.com which was a dormant domain that I registered for no other reason that it was on the Million Dollar Homepage. It does however give me a public Domain Name to play with in these examples. Everything will be deleted at the end, so nothing here will remain as public DNS records long term.

Configure the required DNS records

First we need to set up the required DNS records using your providers utilities, they are all very similar. I already have an "A" record for Milpixel.com pointing to my public IP address, so I can just add the two CNAME records as below:

Record Name Target
A milpixel.com My_Public_IP
CNAME auth.milpixel.com milpixel.com
CNAME 1-factor-WebSite.milpixel.com milpixel.com
CNAME 2-factor-WebSite.milpixel.com milpixel.com

Once these are configured, so long as the initial A record is correct the auth and the ProtectedWebSite will both resolve to the same IP.

Remember, if you are running this in a home lab environment, you must create the same DNS records for auth.milpixel.com etc etc pointing at you internal IP. If you don't do this using Pihole or some other DNS server, it will cause Authelia is likely to fail with the "Error occurred retrieving user session" being reported.

Ensure Ports are available publically

We next need to make sure that the TCP-80 & TCP-443 ports are open and if necessary are forwarded to the host we are going to deploy on. Failure to do this step will result with the deployment failing with the message:

authelia exited with code 1

If you get this error ensure the port forwarding is configured correctly before investigating other reported errors first. This seems to be a common mistake reading the various forums etc.

Ensure Docker is installed and functional

Docker installation is pretty straight forward and is well documented here, I do not intend covering it further in this guide.

Prepare Docker Directories for Authelia. I will be using a docker subdirectory of my home directory, adjust for your preferences.

mkdir -p ~/docker/authelia/{config,config/secrets,postgres,redis}

Configure the required Passwords & Keys.

Authelia requires a number of passwords for various functions, we need to supply those, and I've followed this guide to generate those:


cd ~/docker/authelia
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/JWT_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/SESSION_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/STORAGE_PASSWORD
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/STORAGE_ENCRYPTION_KEY
tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 | tr -d '\n' > config/secrets/REDIS_PASSWORD

Finally we need the SMTP password for the mail service you plan on using to send emails. I've used Gmail in my example so this is my gmail password for my account. This password needs to be saved in the following file:

~/docker/authelia/config/secrets/SMTP_PASSWORD

Create the docker-compose.yaml file

I will be using both Redis and Postgres in this docker compose file, other options are available but this docker-compose file works fine for me. The file should be created as per the path below:

~/docker/authelia/docker-compose.yaml

The file below has worked in my environment, I have explored each section below to highlight the key areas of interest below:

name: authelia
services:
  app:
    image: authelia/authelia:latest
    container_name: authelia
    depends_on:
      - database
      - redis
    volumes:
      - ./config:/config
    environment:
      AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: /config/secrets/JWT_SECRET
      AUTHELIA_SESSION_SECRET_FILE: /config/secrets/SESSION_SECRET
      AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE: /config/secrets/SMTP_PASSWORD
      AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /config/secrets/STORAGE_PASSWORD
      AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /config/secrets/STORAGE_ENCRYPTION_KEY
      AUTHELIA_SESSION_REDIS_PASSWORD_FILE: /config/secrets/REDIS_PASSWORD
    ports:
      - 9091:9091

  database:
    image: postgres:15
    restart: unless-stopped
    container_name: auth_database
    volumes:
      - ./postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: "authelia"
      POSTGRES_PASSWORD: "FhpQLpp6Nl3Lsg3Di5bjrXcCbqmbs6uPkbHiQv3qII8BkohZrWKQSnBKRBiUT3Gy"

  redis:
    image: redis:7
    restart: unless-stopped
    container_name: auth_redis
    command: "redis-server --save 60 1 --loglevel warning --requirepass bkerbcl5oQsmVknP4nwzT8dDCQLHu7hhTibWqmcvNhtXKwGsc5csiTVfLC6Y3bZ2"
    volumes:
      - ./redis:/data

Working from the top of the docker-compose file above:

  • The app section defines the main Authelia parameters as you would expect. Here we can see a number of password files that we created earlier being referenced. Five of these were created earlier using random strings. the 6th being the SMTP password. This is good practise because it keeps the actual passwords out of the docker-compose file. However on this occasion this might seem redundant as we will see further down the file.
  • The port being exposed here is 9091, this can be modified to suit your environment, just make sure you only adjust the port on the left of the ':' the port on the right must be left at 9091 as this is used within the docker container.
  • Within the Database section you can see I'm using Postgres with the :15 tag. This is simply because that was in one of the YouTube video's I watched. I do intend updating this to the :latest and will update here accordingly.
  • Under the Environment section of the Database, I have specified the Authelia username along with the password. The password specified here is the same as that stored in the config/secrets/STORAGE_PASSWORD file. So whilst it is good practise not to have passwords in the docker compose file, here I couldn't get it working without specifying it. If I do work out a way of doing it, this will be reflected in a future update to this post.
  • Moving on the the Redis section, this is all pretty straight forward as you would expect. However, here again I've had to specify the password taken from that stored in the config/secrets/REDIS_PASSWORD file. As before if I find out how to avoid this file in the docker-compose file I will update this section.

That is all there is to say about the docker-compose.yaml file. Once the passwords are ask set and match then we can move on.

Create a users_database.yml file

For my use case with a single user for testing, a simple yaml file for user credentials is sufficient. The Authelia Password Reference documentation is very comprehensive and should be checked if required to clarify settings etc.

Before we look into the file we will first need to generate a password hash. I used the https://argon2.online site to do this. In my example the password is "mystrongpassword" which resulted with the hash of "$argon2i$v=19$m=32,t=2,p=4$c1lZdURxcjJ1bnBzOWZpSQ$bzci8AqOzQJharl5+EsANw".

It is this strong hash value that we write in the password section of the user_database.yml file.

We also need to provide the email address for the user, this is required so that authelia can send you the token's etc to set up MFA. If you don't take the time to set up the email both here and in the Authelia password sections MFA, and the reporting of user account activity will not function. It wouldn't surprise me if Authelia even fails to start without these email credentials being configured.

The file should be created as per the path below:

~/docker/authelia/config/users_database.yml

Containing the information below:

users:
  my_user:  #username for user 1. change to whatever you'd like
    displayname: "Joe Bloggs" #whatever you want the display name to be
    password: "$argon2i$v=19$m=32,t=2,p=4$c1lZdURxcjJ1bnBzOWZpSQ$bzci8AqOzQJharl5+EsANw"
    email: JoeBloggs-Sample@gmail.com #whatever your email address is
    groups: #enter the groups you want the user to be part of below
      - admins
      - dev

I haven't really delved into the groups, but I believe this to be a way of having certain restrictions applicable too certain groups of users. Now we have our docker-compose file and the user_database.yml we can start to pull it all together in our Authelia configuration.yml file.

Create a configuration.yml file

If you start the Authelia docker without a configuration file it will generate one with the very many options along with remarks. If you wish to see that file simply skip this step start the docker stack using the docker-compose file from earlier and it will generate the template for you to browse / edit as required.

Or, we can just start with a small new file and work from there. This was my approach based on research from other blogs and youtube.

The file should be created as per the path below:

~/docker/authelia/config/configuration.yml

In my example here, this is what is contained in the file:

# yamllint disable rule:comments-indentation
---
###############################################################################
#                           Authelia Configuration                            #
###############################################################################

theme: auto #light/dark/auto

server:
  host: 0.0.0.0 #IP to listen on, this is fine in our docker container
  port: 9091 #This needs to match the docker-compose port
  path: ""
  read_buffer_size: 8192 #think the default is 4096
  write_buffer_size: 8192 #think the default is 4096
  enable_pprof: false
  enable_expvars: false
  disable_healthcheck: false #Provide infomation if something is wrong
  tls:
    key: ""
    certificate: ""

log:
  level: debug #This can be changed to info if required

totp:
  issuer: milpixel.com #your authelia top-level domain
  disable: false
  period: 30 #how many days a token is valid for
  skew: 1 #how much skew is allowed on that period

authentication_backend:
  disable_reset_password: false #I want to control user access
  refresh_interval: 5m
  file:
    path: /config/users_database.yml #this is where your authorized users are stored
    password:
      algorithm: argon2id
      iterations: 1
      key_length: 32
      salt_length: 16
      memory: 1024
      parallelism: 8

access_control:
  default_policy: deny
  rules:
    ## bypass rule
    - domain: 
        - "auth.milpixel.com" #This should be your authentication URL
      policy: 'bypass'
    - domain: 
        - "1-factor-WebSite.milpixel.com" #The 1st Web Site
      policy: 'one_factor'
    - domain:
        - "2-factor-WebSite.milpixel.com" #The 2nd Web Site
      policy: 'two_factor'
      #any time you add a new subdomain, you will need to restart the Authelia container to recognize the new settings/rules

session:
  name: authelia_session
  expiration: 3600  # 1 hour
  inactivity: 300  # 5 minutes
  cookies:
    - domain: 'milpixel.com'
      authelia_url: 'https://auth.milpixel.com'
      name: 'authelia_session'
  redis:
    host: auth_redis
    port: 6379

regulation:
  max_retries: 3
  find_time: 10m
  ban_time: 12h

storage:
  postgres:
    address: 'tcp://auth_database:5432'
    database: authelia
    username: authelia

notifier:
  disable_startup_check: false #true/false
  smtp:
    username: JoeBloggs-Sample@gmail.com #your email to use as sender
    host: smtp.gmail.com #email smtp server
    port: 587 #email smtp port
    sender: JoeBloggs-Sample@gmail.com #your email to use as sender
    identifier: localhost
    subject: "[Authelia] {title}" #email subject
    startup_check_address: admin@milpixel.com #your email to receive 
    disable_require_tls: false
    disable_html_emails: false
    tls:
      skip_verify: false
      minimum_version: TLS1.2

Authelia.com has details of all these option as you would expect. I highly recommend reviewing any sections that may not be too clear.

I believe the comments in the file above probably cover most questions, but on the notifier section, if you use gmail, you will need to generate an application password and use that in the SMTP_PASSWORD file. I have set up a new gmail account just to use it for service related emails etc. I would advise this a good approach so they you can receive notification of authentication requests and MFA codes, password resets etc.

I intend to do a short blog about that and will link here when I've completed it. Either way, a quick search should point you in the right direction.

Starting Authelia docker stack with Redis and Postgres

We now have all the elements to deploy the stack to protect our services. The following files and directories should be ready for deployement:

./redis
./postgres
./config
./config/user_database.yml
./config/secrets
./config/secrets/JWT_SECRET
./config/secrets/SMTP_PASSWORD
./config/secrets/SESSION_SECRET
./config/secrets/STORAGE_PASSWORD
./config/secrets/STORAGE_ENCRYPTION_KEY
./config/secrets/REDIS_PASSWORD
./config/configuration.yml
./docker-compose.yaml

After confirming we have all the necessary files in place for our stack, we can deploy:

docker compose up

Once the docker has pulled the required images etc it should all start up and end up with something similar to below listening on port 9091.

authelia       | level=info msg="Authelia v4.38.8 is starting"
authelia       | level=info msg="Log severity set to debug"
authelia       | level=info msg="Storage schema is being checked for updates"
authelia       | level=info msg="Storage schema is already up to date"
authelia       | level=debug msg="Create Server Service (metrics) skipped"
authelia       | level=info msg="Startup complete"
authelia       | level=info msg="Listening for non-TLS connections on '[::]:9091' path '/'" server=main service=server

Confirm the Containers are running

Issuing a docker ps command, we can see the containers are running:

CONTAINER ID   IMAGE                      COMMAND                  CREATED              STATUS                  PORTS                                       NAMES
cbbea3df4065   authelia/authelia:latest   "/app/entrypoint.sh"     3 hours ago         Up 2 hours (healthy)   0.0.0.0:9091->9091/tcp, :::9091->9091/tcp   authelia
b3ffd4b3d811   postgres:15                "docker-entrypoint.s…"   3 hours ago         Up 2 hours             5432/tcp                                    auth_database
1ea07c6a02e4   redis:7                    "docker-entrypoint.s…"   3 hours ago         Up 2 hours             6379/tcp                                    auth_redis

Now have Authelia configured, up and running we can go to the next step of this blog series.

  1. This page: Authelia for MFA on docker
  2. Deploying test hosts for Authelia MFA authentication
  3. Configuring Nginx reverse proxy in front of Authelia
  4. Configuring Caddy reverse proxy in front of Authelia
  5. Logging in to Authelia