Skip to main content

Single-Sign-On using Traefik 3, Authelia 4 and SSL

·10 mins
Gerald Gnaegy
Author
Gerald Gnaegy
I love solving problems and blog here about Python, Docker, IT automation and technology.
Table of Contents

Introduction
#

In this tutorial, we will set up a secure Single Sign-On (SSO) infrastructure using Docker, Traefik, Authelia, and SSL certificates from Let’s Encrypt.

With this setup, you’ll have a powerful combination of tools that ensure your applications are secure, scalable, and easy to manage. Let’s dive in and start building our project!

What is Traefik?
#

Traefik is a modern reverse proxy and load balancer that makes deploying microservices easy. It automatically discovers services and updates its configuration, simplifying the management of dynamic environments. Traefik supports multiple backends, such as Docker and Kubernetes, and handles SSL termination, load balancing, and HTTP routing.

What is Authelia?
#

Authelia is an open-source authentication and authorization server, providing two-factor authentication and single sign-on (SSO) for your applications.

SSO is an authentication process that allows a user to access multiple applications with one set of login credentials. It enhances security by adding an additional layer of protection to your services.

Prerequisites
#

  • Docker running on a Linux Server
  • A domain name with the ability to change DNS entries

Setting DNS records
#

We start by creating wildcard DNS records for our domain example.com. If you are following this guide, replace example.com with your own domain name.

Type Name Content
A * your IPv4 address
AAAA * your IPv6 address
A @ your IPv4 address
AAAA @ your IPv6 address

Creating the Project Structure
#

Let’s create our project structure:

mkdir -p $HOME/docker-projects/{traefik-authelia,hello-world}
mkdir $HOME/docker-projects/traefik-authelia/{config,data,ssl}
touch $HOME/docker-projects/traefik-authelia/ssl/acme.json
chmod 600 $HOME/docker-projects/traefik-authelia/ssl/acme.json

Configure Traefik and Authelia
#

Setting up docker-compose.yml
#

Create a docker-compose.yml file inside docker-projects/traefik-authelia:

services:
  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    volumes:
      - ./ssl/acme.json:/acme.json
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - docknet
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.api.rule=Host(`traefik.example.com`)'
      - 'traefik.http.routers.api.entrypoints=https'
      - 'traefik.http.routers.api.service=api@internal'
      - 'traefik.http.routers.api.tls=true'
      - 'traefik.http.routers.api.tls.certresolver=letsencrypt'
    depends_on:
      authelia-service:
        condition: service_healthy
    ports:
      - 80:80
      - 443:443
    command:
      - '--api'
      - '--providers.docker=true'
      - '--providers.docker.exposedByDefault=false'
      - '--entrypoints.http=true'
      - '--entrypoints.http.address=:80'
      - '--entrypoints.http.http.redirections.entrypoint.to=https'
      - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
      - '--entrypoints.https=true'
      - '--entrypoints.https.address=:443'
      - '--certificatesResolvers.letsencrypt.acme.email=ggn@example.com'
      - '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
      - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
      - '--log=true'
      - '--log.level=INFO'
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
  authelia-service:
    image: authelia/authelia:4
    container_name: authelia-service
    restart: unless-stopped
    environment:
      - TZ=Europe/Berlin
    volumes:
      - ./config:/config
      - ./data:/data
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.authelia.rule=Host(`auth.example.com`)'
      - 'traefik.http.routers.authelia.entrypoints=https'
      - 'traefik.http.routers.authelia.tls=true'
      - 'traefik.http.routers.authelia.tls.certresolver=letsencrypt'
      - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia-service:9091/api/verify?rd=https://auth.example.com'
      - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
      - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email'
    depends_on:
      - authelia-redis
    networks:
      - docknet
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
  authelia-redis:
    image: redis:7
    container_name: authelia-redis
    restart: unless-stopped
    environment:
      - TZ=Europe/Berlin
    networks:
      - docknet
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

networks:
  docknet:
    name: docknet

Update the traefik.http.routers.authelia.rule=Host() labels with your domain name and Europe/Berlin with the timezone you’re living in.

Setting up configuration.yml
#

Create two secrets using openssl rand -hex 24:

openssl rand -hex 24
6c6934b9f93d3ef49122b5014ed5e2f48b8af18564d29f4b
openssl rand -hex 24
bb1a9d0395252126a854d32c4d176c405c4e94ac14aac161

The configuration.yml file is used by Authelia and should be stored in the folder docker-projects/traefik-authelia/config.

Add the secrets to identity_validation.reset_password.jwt_secret and encryption_key:

server.address: 0.0.0.0:9091
log:
  level: debug

identity_validation.reset_password.jwt_secret: 6c6934b9f93d3ef49122b5014ed5e2f48b8af18564d29f4b

default_redirection_url: https://public.example.com
totp:
  issuer: example.com

authentication_backend:
  file:
    path: /config/users_database.yml

access_control:
  default_policy: deny
  rules:
    - domain: public.example.com
      policy: bypass
    - domain: one-factor.example.com
      policy: one_factor
    - domain: two-factor.example.com
      policy: two_factor
    - domain: example.com
      policy: bypass

session:
  name: authelia_session
  secret: unsecure_session_secret
  expiration: 3600
  inactivity: 300
  domain: example.com

  redis:
    host: authelia-redis
    port: 6379

regulation:
  max_retries: 3
  find_time: 120
  ban_time: 300

storage:
  encryption_key: bb1a9d0395252126a854d32c4d176c405c4e94ac14aac161
  local:
    path: /data/db.sqlite3

ntp:
  address: 'udp://time.cloudflare.com:123'
  version: 3
  max_desync: '3s'
  disable_startup_check: true
  disable_failure: true

notifier:
  disable_startup_check: false
  filesystem:
    filename: '/config/notification.txt'

#  smtp:
#    username: apikey
#    password: secret
#    host: smtp.sendgrid.net
#    port: 587
#    sender: no-reply@mydomain.com

While the filesystem notifier is okay for a simple testing setup, I would suggest adding an SMTP notifier before deploying the setup to production. Just uncomment the SMTP section at the end of the file. You will notice the disadvantages when adding an OTP secret to your account in one of the next steps.

Setting up users_database.yml
#

Run the following command to hash your password:

docker run --rm authelia/authelia:4 authelia crypto hash generate argon2 --password top-secret-password
Digest: $argon2id$v=19$m=65536,t=3,p=4$70wyNzdFDfhMO1hV6Hkx4w$i8i7oQZBix8xpXxYTWOWzU4SUbJwAoRis4/93D6XBTo

Replace the placeholders with your own credentials and store the file users_database.yml in /docker-projects/traefik-authelia/config/:

users:
  <username>:
    displayname: <display_name>
    password: <hashed_password>
    email: <email>
    groups:
      - admins
      - dev

Start the containers
#

Now it’s time to start the containers in docker-projects/traefik-authelia using docker compose up -d:

docker compose up -d
[+] Running 3/3
 ✔ Container authelia-redis     Started    0.5s 
 ✔ Container authelia-service   Healthy   31.2s 
 ✔ Container traefik            Started   31.6s 

As you can see, this takes a while. The container authelia-service needs some time to start and as specified in the docker-compose.yml, the container traefik waits for authelia-service until its state is service-healty.

Use your favorite web browser and open https://traefik.example.com. You should see the web interface of Traefik:

Screenshot of the Traefik web interface

Now, open https://auth.example.com in your browser. You should see a Sign in page, provided by Authelia. Try to sign in using your credentials you have defined in docker-projects/traefik-authelia/config/users_database.yml.

Screenshot of the Authelia sign in page

Setting up one-time-passwords (OTP)
#

Once you successfully logged in, you should add a One-Time-Password (OTP) by clicking on Register device:

Screenshot of the Authelia One-Time-Password page

Click on Add in the One-Time-Password section:

Screenshot of the Authelia One-Time-Password page

Screenshot of the Authelia Identity Verification page

It’s required to verify your identity. As we don’t configured email server settings, Authelia will not send us an email, but the email text was stored in docker-projects/traefik-authelia/config/notification.txt.

cat docker-projects/traefik-authelia/config/notification.txt
Date: 2024-07-18 12:54:25.92379532 +0200 CEST m=+505.360960681
Recipient: {Gerald <redacted>}
Subject: Confirm your identity
Hi Gerald,

This email has been sent to you in order to validate your identity. Purpose: Confirm your identity.

If you did not initiate the process your credentials might have been compromised and you should:
	1. Visit the revocation link
	2. Reset your password or other login credentials
	3. Contact an Administrator

To confirm your identity please use the following single-use code: YR9GYGAY

This email was generated by a user with the IP 172.80.32.1.

The following link can be utilized to revoke the code (this is a logged event): https://auth.example.com/revoke/one-time-code?id=<redacted>

Paste the single-use code into the form and simply follow the steps:

Screenshot of the Authelia One-Time-Password page

Screenshot of the Authelia One-Time-Password page

Register your One-Time-Password by scanning the QR-code with your Smartphone and enter the displayed OTP in your app:

Screenshot of the Authelia One-Time-Password page

Screenshot of the Authelia One-Time-Password page

Congratulations, you have successfully created the Traefik + Authelia setup and configured One-Time-Passwords in your Authelia account.

Secure Traefik Dashboard with two-factor authentication (Important!)
#

Since the URL https://traefik.example.com is accessible to anyone, it is important to secure the Traefik Dashboard with two-factor authentication.

Add the following line to the labels section in your docker-projects/traefik-authelia/docker-compose.yml:

services:
  traefik:
    [...]
    labels:
      [...]
      - 'traefik.http.routers.api.middlewares=authelia@docker'

Add the following lines to the access_control/rules section in your docker-projects/traefik-authelia/config/configuration.yml:

access_control:
  default_policy: deny
  rules:
    [...]
    - domain: traefik.example.com
      policy: two_factor

Restart your containers:

docker compose down
docker compose up -d

Open https://traefik.example.com again and see if you need to authenticate using a second factor.

Create some other containers
#

This step is optional, but used to demonstrate the functionality of our setup.

Create Dockerfile
#

Create a Dockerfile in docker-projects/hello-world/:

FROM busybox
WORKDIR /usr/share/httpd
CMD sh -c 'echo "$APP_TITLE" > index.html && /bin/httpd -p 80 -f'

The only job of the container is to display a message specified by the APP_TITLE environment variable.

Create docker-compose.yml
#

We are creating three services:

  • one-factor-service
    Only accessible if a user is authenticated with one factor (e.g., password)
  • two-factor-service
    Only accessible if a user is authenticated with two factors (e.g., password and OTP)
  • public-service
    Accessible for unauthenticated and authenticated users

Create a docker-compose.yml file in docker-projects/hello-world/:

services:
  one-factor-service:
    build: .
    container_name: one-factor-service
    restart: unless-stopped
    environment:
      - APP_TITLE=One Factor Service
    networks:
      - docknet
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.one-factor.rule=Host(`one-factor.example.com`)'
      - 'traefik.http.routers.one-factor.entrypoints=https'
      - 'traefik.http.routers.one-factor.tls=true'
      - 'traefik.http.routers.one-factor.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.one-factor.middlewares=authelia@docker'
      - 'traefik.http.routers.one-factor.service=one-factor-service'
      - 'traefik.http.services.one-factor-service.loadbalancer.server.port=80'
    expose:
      - 80
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
  two-factor-service:
    build: .
    container_name: two-factor-service
    restart: unless-stopped
    environment:
      - APP_TITLE=Two Factor Service
    networks:
      - docknet
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.two-factor.rule=Host(`two-factor.example.com`)'
      - 'traefik.http.routers.two-factor.entrypoints=https'
      - 'traefik.http.routers.two-factor.tls=true'
      - 'traefik.http.routers.two-factor.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.two-factor.middlewares=authelia@docker'
      - 'traefik.http.routers.two-factor.service=two-factor-service'
      - 'traefik.http.services.two-factor-service.loadbalancer.server.port=80'
    expose:
      - 80
    logging: 
      driver: "json-file"
      options:
        max-size: "1m"
  public-service:
    build: .
    container_name: public-service
    restart: unless-stopped
    environment:
      - APP_TITLE=Public Service
    networks:
      - docknet
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.public.rule=Host(`public.example.com`)'
      - 'traefik.http.routers.public.entrypoints=https'
      - 'traefik.http.routers.public.tls=true'
      - 'traefik.http.routers.public.tls.certresolver=letsencrypt'
      - 'traefik.http.routers.public.middlewares=authelia@docker'
      - 'traefik.http.routers.public.service=public-service'
      - 'traefik.http.services.public-service.loadbalancer.server.port=80'
    expose:
      - 80
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

networks:
  docknet:
    name: docknet
    external: true

Start the containers again in the docker-projects/hello-world folder using docker compose up -d:

docker compose up -d
[+] Running 3/3
 ✔ Container two-factor-service  Started    0.7s 
 ✔ Container public-service      Started    0.7s 
 ✔ Container one-factor-service  Started    0.7s 

If you run docker ps, you should see the following containers:

docker ps
CONTAINER ID   IMAGE                            COMMAND                  CREATED         STATUS                 PORTS                                                                      NAMES
ac54255c2725   hello-world-one-factor-service   "sh -c '/bin/httpd -…"   4 minutes ago   Up 4 minutes           80/tcp                                                                     one-factor-service
a4a6d306a315   hello-world-public-service       "sh -c '/bin/httpd -…"   4 minutes ago   Up 4 minutes           80/tcp                                                                     public-service
eed87878fc20   hello-world-two-factor-service   "sh -c '/bin/httpd -…"   4 minutes ago   Up 4 minutes           80/tcp                                                                     two-factor-service
ac7214f84b83   traefik:v3.1                     "/entrypoint.sh --ap…"   2 hours ago     Up 2 hours             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   traefik
31d6d7edce07   authelia/authelia:4              "/app/entrypoint.sh"     2 hours ago     Up 2 hours (healthy)   9091/tcp                                                                   authelia-service
2d89a25f8e0f   redis:7                          "docker-entrypoint.s…"   2 hours ago     Up 2 hours             6379/tcp                                                                   authelia-redis

Testing our setup
#

Accessing the public service
#

Open your web browser and go to https://public.example.com. As you can see, you can access the public service without any authentication:

Screenshot of the Authelia sign in page

Accessing the one-factor service
#

Open your web browser and go to https://one-factor.example.com. You’ll probably see the sign in screen of Authelia again:

Another screenshot of the Authelia sign in page

After signing in, you should see the one-factor service:

Screenshot of the one factor service

Accessing the two-factor service
#

Open your web browser and go to https://two-factor.example.com. As you are already logged in, you won’t see the sign-in page, but you will have to authenticate yourself using OTP to access the site:

Screenshot of the two factor service

Another screenshot of the two factor service

Conclusion
#

In this tutorial, we’ve successfully set up a secure Single Sign-On (SSO) infrastructure using Docker, Traefik, Authelia, and Let’s Encrypt SSL certificates. We’ve configured Traefik as our reverse proxy and load balancer, ensuring secure and efficient routing of traffic to our services. Authelia provided robust authentication and authorization features, enhancing the security of our applications with SSO and two-factor authentication.

By following this guide, you now have a powerful and scalable setup that simplifies the management of dynamic environments, improves security, and provides a seamless user experience. Whether you’re deploying in a development environment or moving towards production, this combination of tools offers a flexible and reliable solution for modern web applications.

Remember, this setup can be further customized and extended based on your specific requirements. For example, you can integrate additional services, configure different authentication backends, or fine-tune the access control rules in Authelia.

Next Steps
#

  • Expand and Customize: Add more services and refine your configurations to match your specific needs.
  • Monitor and Maintain: Regularly monitor your services and ensure that your configurations are up-to-date.
  • Explore Advanced Features: Look into advanced features of Traefik and Authelia for even more powerful capabilities.

Troubleshooting
#

General
#

Docker logs are a valuable resource for identifying issues with your containers. Use the following command to view the log of a container:

docker logs <container_name, e.g., traefik>

You can see a list of all Docker containers with this command:

docker ps

Common Issues and Solutions
#

Bad Gateway Error
#

  • Cause: Traefik cannot establish a connection to a Docker container.

  • Solution: Ensure the container is running and the port specified in the docker-compose.yml file matches the exposed port of the container. Verify the container status with:

    docker ps -a
    

SSL Certificate Issues
#

  • Cause: Problems with SSL certificates, permissions, or ACME challenges.

  • Solution: Check the permissions of acme.json:

    chmod 600 $HOME/docker-projects/traefik-authelia/ssl/acme.json
    

    Also, check Traefik logs for detailed error messages.

Service Health Check Failures
#

  • Cause: Containers not starting properly or health checks failing.
  • Solution: Review the container logs to identify the root cause. Ensure all dependencies (e.g.,, Redis for Authelia) are up and running.

Authentication Failures
#

  • Cause: Issues with Authelia’s configuration or user credentials.
  • Solution: Verify the Authelia configuration files (configuration.yml and users_database.yml). Ensure secrets and hashed passwords are correctly set.

Network Issues
#

  • Cause: Misconfigured Docker networks.
  • Solution: Ensure all services are connected to the correct Docker network. Verify network configurations in the docker-compose.yml file. Check that IPv6 is properly configured on your system and in the Docker config file /etc/docker/daemon.json.

Thank you
#

I appreciate your feedback on this tutorial. If you have suggestions, improvements, or encounter any bugs, please don’t hesitate to reach out. I’ll strive to keep this guide updated with the latest best practices and features.

Thank you for following along, and I hope this tutorial has been helpful in setting up your secure SSO infrastructure. Happy deploying!