↓ Skip to main content
  1. Posts/
  2. IAM and IdP Solutions/

Authelia in Docker: ForwardAuth Protection for Services Behind Traefik

··1474 words·7 mins· loading · loading · ·
Stilicho2011
Author
Stilicho2011
Writing about homelab, self-hosting, automation and open-source solutions
Table of Contents
IAM Solutions - This article is part of a series.
Part : This Article

Of all the SSO/2FA solutions I’ve covered on the channel, Authelia is the lightest one by far. Where Authentik and Keycloak are full-blown IAM platforms with their own user database and web console, Authelia is more of a “gatekeeper at the front door”: it doesn’t replace your services or store anything about them - it just sits in front of Traefik (or NGINX/Caddy) and answers one question - let this particular person through, or ask for a password and a code from an authenticator app first. For a detailed comparison of Authelia with Authentik, Keycloak, and ZITADEL, see the separate article.

If this article or the video were useful, you can support the channel on Boosty - link in the contacts.

What it can do
#

  • Protect any web app by domain or a specific URI - point by point, not all-or-nothing.
  • 2FA: TOTP, Duo, WebAuthn.
  • Users from a plain YAML file, LDAP, or Active Directory, your choice.
  • ForwardAuth integration with Traefik, NGINX, Caddy.
  • ACL - access rules by domain, group, network.

It has fundamentally no web UI for managing anything - everything lives in YAML, and users are added by hand or with a script. For some people that’s a downside (Authentik is friendlier if a non-technical family member needs to manage their own account), but for me it’s more of a plus: the config lives in git, changes show up in a diff, and I don’t need to click through a panel to figure out what’s configured where.

CharacteristicAutheliaAuthentikKeycloakZITADEL
Ease of installation⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
2FA✅✅✅✅
SSO✅✅✅✅
Open Source✅✅✅✅
Web UI❌ (config only)✅✅✅
Resource usageLowMediumHighMedium
ACL✅✅Limited❌

(The star ratings are my subjective impression after setting up all four, not a benchmark.)

If you want maximum simplicity and minimal resource use on a homelab server that isn’t the beefiest - Authelia is a solid pick. If you want a web console for non-technical family members - look at Authentik instead.

What you’ll need
#

  • Docker and Docker Compose.
  • An already-configured reverse proxy - mine is Traefik.
  • Domains with HTTPS (Let’s Encrypt or Cloudflare, doesn’t matter which).
  • Basic YAML knowledge - the config is all text, there’s no magic hiding in a UI.

Project structure
#

authelia/
├── configuration.yml
├── users.yml
├── docker-compose.yml
└── secrets
services:
    authelia:
        image: "authelia/authelia:4.39.20" # don't use :latest - pin the version explicitly, check the current one at https://github.com/authelia/authelia/releases
        container_name: "authelia"
        volumes:
            - "./secrets:/secrets:ro"
            - "./config:/config"
            - "./logs:/var/log/authelia/"
        networks:
            proxy:
        labels:
            - "traefik.enable=true"
            - "traefik.http.routers.authelia.rule=Host(`authelia.stilicho.ru`)"
            - "traefik.http.routers.authelia.entrypoints=https"
            - "traefik.http.routers.authelia.tls=true"
            - "traefik.http.middlewares.authelia.forwardAuth.address=http://authelia:9091/api/verify?rd=https://authelia.stilicho.ru"
            - "traefik.http.middlewares.authelia.forwardAuth.trustForwardHeader=true"
            - "traefik.http.middlewares.authelia.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
            - "traefik.http.services.authelia.loadbalancer.server.port=9091"
        environment:
            TZ: "Europe/Moscow"
            AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: "/secrets/JWT_SECRET" # tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 > ./secrets/JWT_SECRET
            AUTHELIA_SESSION_SECRET_FILE: "/secrets/SESSION_SECRET" # tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 > ./secrets/SESSION_SECRET
            AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: "/secrets/STORAGE_ENCRYPTION_KEY" # tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 > ./secrets/STORAGE_ENCRYPTION_KEY

    #whoami-secure:
    #    image: "traefik/whoami"
    #    restart: "unless-stopped"
    #    container_name: "whoami-secure"
    #    labels:
    #        - "traefik.enable=true"
    #        - "traefik.http.routers.whoami-secure.rule=Host(`whoami-secure.stilicho.ru`)"
    #        - "traefik.http.routers.whoami-secure.entrypoints=https"
    #        - "traefik.http.routers.whoami-secure.middlewares=authelia@docker"
    #    networks:
    #        proxy:

networks:
    proxy:
        external: true
users:
    stilicho: ## Username
        displayname: "stilicho"
        ## WARNING: This is a default password for testing only!
        ## IMPORTANT: Change this password before deploying to production!
        ## Generate a new hash using the instructions at:
        ## https://www.authelia.com/reference/guides/passwords/#passwords
        ## Password is 'authelia'
        password: "$argon2id$v=19$m=65536,t=3,p=4$uSPUUUh/a5U7pNso6g2cMA$YJECeQHkv/qXZDB3W9ADkWj7DMSJRWcn/pVHTUvCbtI"
        email: "authelia@authelia.com"
        groups:
            - "admin"
            - "dev"
server:
    address: tcp://0.0.0.0:9091/
log:
    level: debug
theme: dark
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
#jwt_secret:
default_redirection_url: https://authelia.stilicho.ru
totp:
    issuer: authelia.com

# duo_api:
#  hostname: api-123456789.example.com
#  integration_key: ABCDEF
#  # This secret can also be set using the env variables AUTHELIA_DUO_API_SECRET_KEY_FILE
#  secret_key: 1234567890abcdefghifjkl

authentication_backend:
    file:
        path: /config/users.yml
        password:
            algorithm: argon2
            # Recommended Parameters
            # Uses 2 GiB memory, then immediately releases it.
            # See https://www.authelia.com/reference/guides/passwords/#recommended-parameters-argon2
            # See https://www.rfc-editor.org/rfc/rfc9106.html#section-4 for details on tuning the parameters for your hardware.
            # After saving configuration file, password hash can be generated by running: docker run -v ./configuration.yml:/configuration.yml --rm authelia/authelia:latest authelia crypto hash generate --config /configuration.yml --password 'yourpassword'
            argon2:
                variant: argon2id
                iterations: 1
                memory: 2097152
                parallelism: 4
                key_length: 32
                salt_length: 16
            # Recommended Parameters when constrained by low memory or low powered hardware. Uses 64 KiB memory, then immediately releases it.
            # argon2:
            #   variant: argon2id
            #   iterations: 3
            #   memory: 65536
            #   parallelism: 4
            #   key_length: 32
            #   salt_length: 16

access_control:
    default_policy: deny
    rules:
        # Rules applied to everyone
        - domain: traefik-dashboard.stilicho.ru
          policy: two_factor
        #- domain: portainer.stilicho.ru #portainer has oidc set up
        #  policy: two_factor
        - domain: nginx.stilicho.ru
          policy: two_factor

session:
    name: authelia_session
    # This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
    #secret:
    expiration: 14400 # 4 hour
    inactivity: 14400 # 4 hour
    domain: stilicho.ru # Should match whatever your root protected domain is

    # redis:
    #   host: redis
    #   port: 6379
    #   # This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD_FILE
    #   # password: authelia

regulation:
    max_retries: 3
    find_time: 120
    ban_time: 300

storage:
    #encryption_key: /secrets/STORAGE_ENCRYPTION_KEY # Now required
    local:
        path: /config/db.sqlite3

#password_policy:
#  zxcvbn:
#    enabled: true
#    min_score: 4

#identity_providers:
#  oidc:
## The other portions of the mandatory OpenID Connect 1.0 configuration go here.
## See: https://www.authelia.com/c/oidc
#    clients:
#      - client_id: 'portainer'
#        client_name: 'Portainer'
#        client_secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng'  # The digest of 'insecure_secret'.
#        public: false
#        authorization_policy: 'two_factor'
#        require_pkce: false
#        pkce_challenge_method: ''
#        redirect_uris:
#          - 'https://portainer.stilicho.ru'
#        scopes:
#         - 'openid'
#          - 'profile'
#          - 'groups'
#          - 'email'
#        response_types:
#          - 'code'
#        grant_types:
#          - 'authorization_code'
#        access_token_signed_response_alg: 'none'
#        userinfo_signed_response_alg: 'none'
#        token_endpoint_auth_method: 'client_secret_post'

#log:
#  level: info
#  format: text
#  file_path: /logs/authelia.log
#  keep_stdout: false

notifier:
    # smtp:
    #   username: test
    #   # This secret can also be set using the env variables AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE
    #   password: password
    #   host: mail.example.com
    #   port: 25
    #   sender: admin@example.com
    filesystem:
        filename: /config/notification.txt

A few things worth calling out here:

  • The three _FILE secrets (JWT_SECRET, SESSION_SECRET, STORAGE_ENCRYPTION_KEY) - generated with the same command from the comment in the compose file (tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 > ./secrets/NAME), one run per file. Without these Authelia simply won’t start - this isn’t an advanced optional setting, it’s the bare minimum.
  • The password hash in users.yml - the example shows a test hash for the password authelia. Don’t leave it as-is even “just to poke around” - generate your own with the command in the comment (authelia crypto hash generate); saving those five minutes isn’t worth it.
  • log: level: debug in configuration.yml - handy while you’re setting things up so you can see what happens on every request, but once it’s working, switch it back to info - otherwise the log grows fast for no real benefit day to day.
  • access_control.rules - this is where you describe which domains are protected and what level of access is needed (two_factor, one_factor, bypass). default_policy: deny means anything not explicitly covered by a rule is blocked by default. Rules are evaluated top to bottom, and the first match wins - order matters.
  • session.domain - has to match the root domain you’re protecting services under (mine is stilicho.ru), otherwise Authelia’s session cookie won’t be visible to the subdomains of the services you’re protecting, and login won’t survive moving between them.
  • The commented-out whoami-secure block in the compose file - a test service (traefik/whoami) left there specifically for verification: uncomment it to confirm the forwardAuth middleware actually asks for a password before showing the page, before you attach the protection to a real service.

Verifying it works
#

Bring the stack up:

docker compose up -d

Open a protected domain in your browser - it should redirect you to Authelia’s login form instead of straight to the service. Enter the login/password from users.yml, then a TOTP code if you’ve set that up. After a successful login, Authelia redirects you back to the original address, and further requests to that domain go through without logging in again within session.expiration.

If you land straight on the protected service instead of a login form - the forwardAuth middleware isn’t attached to that service’s router (traefik.http.routers.<service>.middlewares=authelia@docker needs to be in its labels) - it’s not a problem with Authelia itself.

Bottom line
#

Authelia isn’t a replacement for a full IAM platform - it’s a simple, lightweight gatekeeper in front of your services, for when you don’t need your own user database with a web console or OIDC integrations for every little thing. The config is all plain text, it barely uses any resources, and once you’ve dealt with the secrets and access_control once, adding a new protected domain is a one-line rule plus one label on a container.

IAM Solutions - This article is part of a series.
Part : This Article

Related