Stopping Brute-force: Deploying Authelia as SSO and 2FA for Web Systems

Security tutorial - IT technology blog
Security tutorial - IT technology blog

A 2 AM Wake-up Call

At exactly 2 AM, my phone buzzed incessantly on the desk. Alerts from Grafana were flooding in: tens of thousands of suspicious requests were hitting the admin page of an internal dashboard. Checking the logs, I saw an overseas IP tirelessly brute-forcing the admin account at a rate of over 50,000 requests per hour.

Despite having a complex password, watching an attacker try every key to my front door was anything but comfortable. After auditing security for over 10 server systems, I realized a fatal flaw: fragmentation. Every application like Nextcloud, Portainer, or Grafana used its own set of credentials. Some supported 2FA, some didn’t. If just one weak link is bypassed, the entire infrastructure is exposed to hackers.

Where is the Problem?

The issue isn’t just about how many characters are in your password. When you self-host multiple services, managing access quickly becomes a nightmare:

  • Disjointed management: Each app has its own login style, making it hard to control who is accessing what.
  • Offboarding risks: If a team member leaves, you have to log into every app to delete their account. Forgetting one spot leaves a deadly vulnerability.
  • Legacy app vulnerabilities: Many older open-source tools don’t support two-factor authentication, making them prime targets for credential stuffing attacks.

Options Often Ruled Out Immediately

Before deciding on Authelia, I considered several options, but each had its own set of inconveniences:

  1. Using a VPN (WireGuard/OpenVPN): Very secure but extremely inconvenient. Every time you need to quickly check a dashboard on your phone, you have to toggle the VPN and wait for a connection.
  2. Nginx Basic Auth: Too primitive. It lacks a password reset interface, has no 2FA, and is incredibly difficult to manage as the team grows.
  3. Cloudflare Access: Quite good, but you’re dependent on their cloud. For sensitive data that needs to stay entirely on-premise, this isn’t the preferred choice.

Authelia – A Powerful SSO and 2FA Gatekeeper

Authelia acts as a centralized authentication gateway in front of your applications. It provides Single Sign-On (SSO) and multi-factor authentication (TOTP, Duo, U2F). Simply put: instead of letting users hit the app directly, Nginx redirects them to Authelia to verify fingerprints, passwords, and OTP codes. Only if valid does the door open.

Step 1: Environment Preparation

I assume you already have Docker installed. The directory structure I usually deploy is as follows:

/opt/authelia/
├── docker-compose.yml
└── config/
    ├── configuration.yml
    └── users.yml

Step 2: Launching with Docker Compose

This file will run Authelia along with a Redis database to store sessions. Using Redis ensures a smoother experience, as users won’t have to log in repeatedly when you restart the container.

version: '3.8'
services:
  authelia:
    container_name: authelia
    image: authelia/authelia:latest
    volumes:
      - ./config:/config
    networks:
      - proxy
    environment:
      - TZ=Asia/Ho_Chi_Minh
    restart: unless-stopped

  redis:
    container_name: authelia-redis
    image: redis:alpine
    networks:
      - proxy
    restart: unless-stopped

networks:
  proxy:
    external: true

Step 3: Configuring the System’s Soul – configuration.yml

This is where all the operational rules are set. You need to generate random secret strings using the command openssl rand -hex 64.

server:
  host: 0.0.0.0
  port: 9091

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

access_control:
  default_policy: deny
  rules:
    - domain: "*.yourdomain.com"
      policy: two_factor

session:
  name: authelia_session
  domain: yourdomain.com
  expiration: 3600
  inactivity: 900
  redis:
    host: authelia-redis
    port: 6379

notifier:
  filesystem:
    filename: /config/notification.txt

Step 4: Setting Up Users

In the users.yml file, passwords must be encrypted. You can use an online tool or run the command docker run --rm authelia/authelia:latest authelia hash-password "your_password" to get the Argon2id hash string.

users:
  admin:
    displayname: "Administrator"
    password: "$argon2id$v=19$m=65536,t=3,p=4$your_hashed_password"
    email: [email protected]
    groups:
      - admins

Step 5: Connecting the Reverse Proxy (Nginx)

If you are using Nginx Proxy Manager, paste this code into the Advanced Custom Configuration section of the application you want to protect:

auth_request /authelia;
auth_request_set $target_url $scheme://$http_host$request_uri;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header Remote-User $user;

# Redirect to login page if not authenticated
error_page 401 =302 https://auth.yourdomain.com/?rd=$target_url;

Real-world Experience: Don’t Get Locked Out

After several hard-earned lessons during real-world deployments, I’ve identified 3 critical points:

  • Time Synchronization: OTP codes (TOTP) are time-based. If the server and phone clocks are off by more than 30 seconds, you’ll be denied access even with the correct code. Install chrony for continuous time sync.
  • HTTPS is Mandatory: Authelia and modern browsers won’t allow 2FA over insecure HTTP connections.
  • Cookie Domain: Ensure the session domain is set to yourdomain.com (root domain). This way, when you log in at auth.yourdomain.com, other subdomains like grafana.yourdomain.com will recognize the session.

Since implementing Authelia, I’ve been sleeping much better. An attacker might guess a password, but they can’t get the OTP code from my pocket. User management is now consolidated into a single file—secure and professional.

Share: