Authelia & lldap: Authentication, SSO, User Management & Password Reset for Home Networks
This article explains how to set up a simple but modern user management and authentication system for services on your internal home network. The solution supports important security features like two-factor authentication and single sign-on, and only requires minimal maintenance due to self-service password reset. This article is part of my series on home automation, networking & self-hosting that shows how to install, configure, and run a home server with (dockerized or virtualized) services such as Home Assistant and ownCloud.
Preparation
I’m assuming that you’ve set up Docker and the Caddy container as described in the previous articles in this series.
lldap: User Directory
What is lldap?
Light LDAP (lldap) is a lightweight LDAP authentication server with a friendly web UI that aims to make user management easy. lldap is simple to set up, comes with “opinionated basic defaults”, and targets self-hosting servers.
Dockerized lldap Directory Structure
This is what the directory structure will look like when we’re done:
rpool/
└── encrypted/
└── docker/
└── lldap/
├── data/
└── lldap_config.toml
├── secrets/
├── JWT_SECRET
└── LDAP_USER_PASS
├── container-vars.env
└── docker-compose.yml
We’re placing the configuration on the encrypted ZFS dataset (rpool/encrypted
) because it contains secrets such as private keys and passwords.
Create the directories:
mkdir -p /rpool/encrypted/docker/lldap/data
mkdir -p /rpool/encrypted/docker/lldap/secrets
lldap Docker Compose File
Create docker-compose.yml
with the following content:
services:
lldap:
container_name: lldap
hostname: lldap
image: lldap/lldap:stable
restart: unless-stopped
networks:
- caddy_caddynet
expose:
- 3890 # LDAP
- 17170 # Web UI
env_file:
- container-vars.env
volumes:
- ./data:/data
- ./secrets:/secrets
networks:
caddy_caddynet:
external: true
lldap container-vars.env File
Create container-vars.env
with the following content:
LLDAP_LDAP_BASE_DN=dc=example,dc=com # replace with your domain
UID=0 # run as root
GID=0 # run as root
# Secrets: lldap reads them from the specified files.
# This way, the secrets are not part of any process' environment.
LLDAP_JWT_SECRET_FILE=/secrets/JWT_SECRET
LLDAP_LDAP_USER_PASS_FILE=/secrets/LDAP_USER_PASS
lldap Secrets Files
Generate random alphanumeric strings and store them in individual files in the secrets
directory:
cd /rpool/encrypted/docker/lldap/
tr -cd '[:alnum:]' < /dev/urandom | fold -w "64" | head -n 1 > ./secrets/JWT_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w "20" | head -n 1 > ./secrets/LDAP_USER_PASS
lldap TOML Configuration File
We’re using a customized version of lldap’s configuration template. Create data/lldap_config.toml
with the following content:
database_url = "sqlite:///data/users.db?mode=rwc"
key_file = "/data/private_key"
enable_password_reset = false
Start the lldap Container
Navigate into the directory with docker-compose.yml
and run:
docker compose up -d
Inspect the container logs for errors with the command docker compose logs --tail 30 --timestamps
.
lldap Let’s Encrypt Certificate via Caddy
Caddyfile
Add the following to Caddyfile
(details):
lldap.{$MY_DOMAIN} {
reverse_proxy lldap:17170
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}
DNS A Record
Add the following A record to your DNS domain:
lldap.home.yourdomain.com 192.168.0.4 # replace with your Docker host's IP address
Try to resolve the name on a machine in your network (e.g., nslookup lldap.home.yourdomain.com
). If that fails, you might need to work around DNS rebind protection in your router.
Reload Caddy’s Configuration
Instruct Caddy to reload its configuration by running:
docker exec -w /etc/caddy caddy caddy reload
You should now be able to access the lldap web interface at https://lldap.home.yourdomain.com
without getting a certificate warning from your browser.
lldap User Configuration via UI
In your browser, open https://lldap.home.yourdomain.com
. Log on with the user admin
and the password stored in the file /secrets/LDAP_USER_PASS
. Create a new admin user with your own name (e.g., anne
) by clicking Create a user and filling out the form. Add this personal user account to the group lldap_admin
.
Free Sendgrid Account To Send Email From Your Server
A service like Authelia needs to send emails, e.g., for password resets. Setting up your own SMTP server for the task is not a very good idea: those emails would be marked as spam by any self-respecting email application.
Instead, create a free account at Sendgrid (or an alternative service). At the time of writing, Sendgrid’s free plan allows you to send 100 emails per day, which should be more than enough for the occasional message from your home server.
When you set up your Sendgrid account, make sure to authenticate your sending address – or, better yet, your sending domain. The sending address/domain is configured via the environment variable AUTHELIA_NOTIFIER_SMTP_SENDER
(see below).
Redis: Persistent Session Storage
What is Redis?
Redis is an in-memory data store that can be used by Authelia as a session storage provider. By using an external service for session data, Authelia can be restarted without users having to re-authenticate.
Dockerized Redis Directory Structure
This is what the directory structure will look like when we’re done:
rpool/
└── encrypted/
└── docker/
└── redis/
├── data/
└── docker-compose.yml
Create the directory:
mkdir -p /rpool/encrypted/docker/redis/data
Redis Docker Compose File
Create docker-compose.yml
with the following content:
services:
redis:
container_name: redis
hostname: redis
image: redis:latest
restart: unless-stopped
networks:
- caddy_caddynet
expose:
- 6379
volumes:
- ./data:/data
networks:
caddy_caddynet:
external: true
Start the Redis Container
Navigate into the directory with docker-compose.yml
and run:
docker compose up -d
Inspect the container logs for errors with the command docker compose logs --tail 30 --timestamps
. Ignore the warning that recommends setting vm.overcommit_memory = 1
on the host.
Authelia: User Authentication & Authorization
What is Authelia?
Authelia is a lightweight identity and access management (IAM) server. It provides single sign-on (SSO) and multi-factor authentication (MFA) via a friendly web portal. Authelia is typically used in conjunction with reverse proxies such as Caddy.
Dockerized Authelia Directory Structure
This is what the directory structure will look like when we’re done:
rpool/
└── encrypted/
└── docker/
└── authelia/
├── config/
└── configuration.yml
├── secrets/
├── JWT_SECRET
├── SESSION_SECRET
├── STORAGE_ENCRYPTION_KEY
├── NOTIFIER_SMTP_PASSWORD
└── AUTHENTICATION_BACKEND_LDAP_PASSWORD
├── container-vars.env
└── docker-compose.yml
We’re placing the configuration on the encrypted ZFS dataset (rpool/encrypted
) because it contains secrets such as private keys and passwords.
Create the directories:
mkdir -p /rpool/encrypted/docker/authelia/config
mkdir -p /rpool/encrypted/docker/authelia/secrets
Authelia Docker Compose File
Create docker-compose.yml
with the following content:
services:
authelia:
container_name: authelia
hostname: authelia
image: authelia/authelia:4
restart: unless-stopped
networks:
- caddy_caddynet
expose:
- 9091
env_file:
- container-vars.env
volumes:
- ./config:/config
- ./secrets:/secrets
networks:
caddy_caddynet:
external: true
Authelia container-vars.env File
Create container-vars.env
with the following content:
# Custom variables to be used with the template functionality
COOKIE_SESSION_DOMAIN=home.yourdomain.com # replace with your domain
AUTHELIA_TOTP_ISSUER=home.yourdomain.com # replace with your domain
AUTHELIA_WEBAUTHN_DISPLAY_NAME=home # replace with name of your home
AUTHELIA_NOTIFIER_SMTP_ADDRESS=submission://smtp.sendgrid.net:587
AUTHELIA_NOTIFIER_SMTP_USERNAME=apikey # replace with your SMTP server's username
AUTHELIA_NOTIFIER_SMTP_SENDER="Your Name <[email protected]>" # replace with your name/email
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDRESS=ldap://lldap:3890
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN=dc=example,dc=com # must be identical to what is configured in lldap
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_USER=uid=admin,ou=people,dc=example,dc=com # must be the DN of your admin configured in lldap
# Secrets: Authelia reads them from the specified files.
# This way, the secrets are not part of any process' environment.
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE=/secrets/JWT_SECRET
AUTHELIA_SESSION_SECRET_FILE=/secrets/SESSION_SECRET
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE=/secrets/STORAGE_ENCRYPTION_KEY
AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/NOTIFIER_SMTP_PASSWORD
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE=/secrets/AUTHENTICATION_BACKEND_LDAP_PASSWORD
# Enable the template filter
X_AUTHELIA_CONFIG_FILTERS=template
Authelia Secrets Files
Generate random alphanumeric strings and store them in individual files in the secrets
directory:
cd /rpool/encrypted/docker/authelia/
tr -cd '[:alnum:]' < /dev/urandom | fold -w "64" | head -n 1 > ./secrets/JWT_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w "64" | head -n 1 > ./secrets/SESSION_SECRET
tr -cd '[:alnum:]' < /dev/urandom | fold -w "64" | head -n 1 > ./secrets/STORAGE_ENCRYPTION_KEY
Create the file /secrets/NOTIFIER_SMTP_PASSWORD
and paste the password of your SMTP server’s user into it.
Create the file /secrets/AUTHENTICATION_BACKEND_LDAP_PASSWORD
and paste the lldap admin password from lldap/secrets/LDAP_USER_PASS
into it.
Authelia YAML Configuration File
We’re using a customized version of Authelia’s configuration template. Create config/configuration.yml
with the following content:
---
theme: auto
default_2fa_method: totp
server:
address: tcp://0.0.0.0:9091/
log:
level: info
totp:
disable: false
algorithm: sha1
digits: 6
period: 30
skew: 1
secret_size: 32
webauthn:
disable: false
timeout: 60s
attestation_conveyance_preference: indirect
user_verification: preferred
authentication_backend:
password_reset:
disable: false
refresh_interval: 5m
ldap:
implementation: custom
timeout: 5s
start_tls: false
attributes:
username: uid
display_name: displayName
group_name: cn
mail: mail
additional_users_dn: ou=people
users_filter: "(&({username_attribute}={input})(objectClass=person))"
additional_groups_dn: ou=groups
groups_filter: "(member={dn})"
password_policy:
standard:
enabled: false
min_length: 8
max_length: 0
require_uppercase: true
require_lowercase: true
require_number: true
require_special: false
access_control:
default_policy: two_factor
session:
name: authelia_session
cookies:
- domain: '{{ env "COOKIE_SESSION_DOMAIN" }}'
authelia_url: 'https://auth.{{ env "COOKIE_SESSION_DOMAIN" }}'
redis:
host: redis
port: 6379
regulation:
max_retries: 3
find_time: 2m
ban_time: 5m
storage:
local:
path: /config/db.sqlite3
notifier:
disable_startup_check: false
...
Start the Authelia Container
Navigate into the directory with docker-compose.yml
and run:
docker compose up -d
Inspect the container logs for errors with the command docker compose logs --tail 30 --timestamps
.
Authelia Let’s Encrypt Certificate via Caddy
Caddyfile
Add the following to Caddyfile
(details):
auth.{$MY_DOMAIN} {
reverse_proxy authelia:9091
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}
DNS A Record
Add the following A record to your DNS domain:
auth.home.yourdomain.com 192.168.0.4 # replace with your Docker host's IP address
Try to resolve the name on a machine in your network (e.g., nslookup auth.home.yourdomain.com
). If that fails, you might need to work around DNS rebind protection in your router.
Reload Caddy’s Configuration
Instruct Caddy to reload its configuration by running:
docker exec -w /etc/caddy caddy caddy reload
You should now be able to access the Authelia web interface at https://authelia.home.yourdomain.com
without getting a certificate warning from your browser.
Add a Protected Endpoint to Authelia via Caddy
To protect an endpoint with Authelia’s authentication (and optionally enable SSO), add a forward_auth
directive to the endpoint’s Caddy configuration. The configuration for the whoami
container we used as an example in the previous article looks as follows with the added forward_auth
directive:
whoami.{$MY_DOMAIN} {
forward_auth authelia:9091 {
uri /api/verify?rd=https://auth.{$MY_DOMAIN}/
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy whoami:80
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
}
Authelia & OpenID Connect
Authelia’s support for OpenID Connect (OIDC) is marked as “beta”. It’s not yet feature-complete but should work well enough in most scenarios. Authelia’s OpenID Connect roadmap documents the progress in detail. Authelia’s OpenID Connect documentation has example configurations for popular services such as Nextcloud.
See this follow-up article for a detailed description of how to set up OpenID Connect authentication to Authelia in ownCloud Infinite Scale.
Changelog
2024-10-13
- Migrated the cookie domain config setting from the deprecated
AUTHELIA_SESSION_DOMAIN
to the new template functionality with the custom environment variableCOOKIE_SESSION_DOMAIN
.
2024-04-14
- Removed the
version
fromdocker-compose.yml
; a warning mentions that it’s obsolete.
2024-03-17
Updated Configuration for Authelia 4.38
The following Authelia settings need to be changed or updated in config/configuration.yml
:
- Migrate
server.host
andserver.port
toserver.address
. - Migrate
ldap.*_attribute
toldap.attributes.*
. - Remove
same_site
,expiration
,remember_me_duration
,inactivity
because we’re using the defaults.
The following Authelia settings need to be changed or updated in container-vars.env
:
- Rename
AUTHELIA_AUTHENTICATION_BACKEND_LDAP_URL
toAUTHELIA_AUTHENTICATION_BACKEND_LDAP_ADDRESS
. - Rename
AUTHELIA_JWT_SECRET_FILE
toAUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE
. - Migrate
AUTHELIA_NOTIFIER_SMTP_HOST
andAUTHELIA_NOTIFIER_SMTP_PORT
toAUTHELIA_NOTIFIER_SMTP_ADDRESS
.
2022-12-17
- Added Redis as persistent Authelia session storage so that Authelia restarts don’t force users to re-authenticate.
1 Comment
How do you go about putting authelia infront of jellyfin, whilst also allowing the mobile and tv clients work? I have a setup configured for access over a web browser but i am struggling to get access via jellyfin’s android and tv client. I reckon authelia’s config file needs an addition to bypass the specific url that the apps use but i’m not sure how to go about it