8

Autenticación para Self Hosted

Vistas: 0
Autenticación para Self Hosted

Has construido una puerta de entrada sólida con Traefik. Tienes rate limiting para frenar abusos, whitelist de IP para restringir accesos, cabeceras HTTP seguras para proteger a los navegadores. Tus servicios están detrás de un portero firme. Pero hay un problema. Muchos servicios self-hosted no tienen sistema de autenticación propio. O si lo tienen, es básico, sin doble factor, sin integración centralizada. Tienes Grafana con su propio login, Nextcloud con el suyo, Home Assistant con otro. Cada uno con su base de usuarios, su método de login, su política de contraseñas.Y gestionar eso es un caos.

Un usuario, diez servicios, diez contraseñas distintas. O peor: la misma contraseña en diez sitios. Si uno de los servicios tiene una vulnerabilidad y filtran la base de datos, todas tus cuentas están expuestas.

La solución es centralizar la autenticación a nivel de proxy inverso. Que sea Traefik quien decida quién entra y quién no, antes de que la petición llegue al servicio. El backend ni siquiera sabe que existe un sistema de login. Solo ve peticiones de usuarios ya autenticados.

En este capítulo vas a aprender las distintas formas de hacer autenticación con Traefik. Desde la básica con htpasswd hasta la integración con proveedores externos como Authelia y Authentik. Y al final montaremos la cadena completa de middlewares del Bloque 2, con el orden correcto para que todo funcione en armonía.

¿Por qué autenticar a nivel de proxy?

Pongamos un ejemplo concreto.

Tienes una aplicación web que desarrollaste hace años. No tiene login. Es una herramienta interna para ver estadísticas. La pusiste en un servidor con un puerto aleatorio y te olvidaste. Funciona.

Un día, alguien encuentra la URL. Empieza a hacer peticiones. Ve todos tus datos. No hay contraseña, no hay barrera. Solo datos expuestos.

Podrías añadir login a la aplicación. Pero eso implica tocar código, hacer un sistema de usuarios, gestionar sesiones, almacenar contraseñas de forma segura. Y si tienes cinco aplicaciones así, el trabajo se multiplica.

¿O podrías poner la autenticación en Traefik? Una configuración, un solo punto de gestión, y todas tus aplicaciones quedan protegidas sin tocar una línea de código.

La autenticación a nivel de proxy tiene ventajas claras:

  • Centralización: un solo lugar donde configurar usuarios, contraseñas y métodos de autenticación. No importa si tienes 3 servicios o 30.
  • Separación de responsabilidades: el proxy se encarga de la seguridad. La aplicación se encarga de su funcionalidad. Cada uno hace lo que sabe hacer.
  • Consistencia: todos los servicios usan el mismo método de login. El usuario no tiene que recordar credenciales diferentes para cada uno.
  • Auditoría unificada: los logs de autenticación están todos en Traefik. Sabes quién accedió a qué servicio y cuándo, sin tener que revisar los logs de cada aplicación.
  • Menos superficie de ataque: si un servicio tiene una vulnerabilidad en su sistema de login, no importa. El login está en Traefik, no en el servicio. El atacante ni siquiera llega a tocar el backend.

Y la principal desventaja: si Traefik cae, todos los servicios dejan de ser accesibles. Pero ya estás monitorizando Traefik, ¿verdad? Un solo punto que cuidar es mejor que diez puntos que cuidar.

BasicAuth: la opción integrada

Traefik incluye un middleware de autenticación básica HTTP. Es el método más simple y el que no requiere servicios externos.

Cómo funciona BasicAuth

El cliente envía el usuario y la contraseña en la cabecera Authorization de la petición HTTP. El formato es Basic base64(usuario:contraseña). El servidor (Traefik) decodifica, compara con su lista de usuarios, y si coincide, deja pasar la petición.

Cuando un cliente no envía credenciales, Traefik responde con un 401 Unauthorized y la cabecera WWW-Authenticate: Basic realm="realm". El navegador muestra entonces un diálogo de login.

Pero ojo: Base64 no es cifrado. Es codificación. Cualquiera que intercepte la petición puede decodificar la cabecera Authorization y obtener usuario y contraseña en texto plano. Por eso BasicAuth solo es seguro sobre HTTPS. Sin TLS, es como escribir la contraseña en una postal.

Generar usuarios con htpasswd

El middleware basicAuth espera los usuarios en formato htpasswd. Este formato almacena el hash de la contraseña, no la contraseña en texto plano.

Para generar un usuario:

htpasswd -nb lorenzo mi-contraseña-segura

El parámetro -n envía el resultado a stdout (no crea un archivo) y -b toma la contraseña del argumento en línea de comandos.

El resultado es algo como:

lorenzo:$apr1$abcdefgh$1234567890abcdef1234567890abcdef

Ese hash usa el algoritmo Apache MD5 (apr1). No es el más seguro del mundo (bcrypt sería mejor), pero para autenticación a nivel de proxy es suficiente. Si quieres usar bcrypt:

htpasswd -nBb lorenzo mi-contraseña-segura

La opción -B mayúscula usa bcrypt. Algo importante: bcrypt produce hashes más largos, pero Traefik los soporta sin problema. Mi recomendación es usar bcrypt siempre que puedas.

Si no tienes htpasswd instalado, puedes instalarlo con:

# Debian/Ubuntu
sudo apt install apache2-utils

# Arch
sudo pacman -S apache-tools

# macOS (ya viene incluido)

Configurar el middleware BasicAuth

Tienes dos opciones: pasar los usuarios directamente en la configuración o cargarlos desde un archivo.

Usuarios en línea (Docker labels)

labels:
  - "traefik.http.middlewares.auth-basica.basicauth.users=lorenzo:$$apr1$$abcdefgh$$1234567890abcdef1234567890abcdef"
  - "traefik.http.routers.mi-servicio.middlewares=auth-basica"

Fíjate en los $$ dobles en Docker labels. El $ tiene significado especial en Docker Compose (interpolación de variables). Para que Traefik reciba un $ literal, tienes que duplicarlo. Si usas el archivo dinámico YAML, solo necesitas un $.

Usuarios en archivo (archivo dinámico)

# traefik-dynamic.yml
http:
  middlewares:
    auth-basica:
      basicAuth:
        users:
          - "lorenzo:$apr1$abcdefgh$1234567890abcdef1234567890abcdef"
          - "admin:$2y$05$xxxxx..."
        realm: "Zona restringida"

El parámetro realm es el texto que ve el usuario en el diálogo de login del navegador. Pon algo descriptivo como «Acceso restringido» o «Dashboard Traefik».

Usuarios desde archivo externo

Si tienes muchos usuarios, no quieres tenerlos todos en el YAML. Puedes usar un archivo .htpasswd externo:

http:
  middlewares:
    auth-basica:
      basicAuth:
        usersFile: "/etc/traefik/htpasswd/users.htpasswd"
        realm: "Zona restringida"

Y el archivo /etc/traefik/htpasswd/users.htpasswd contiene:

lorenzo:$apr1$abcdefgh$1234567890abcdef1234567890abcdef
admin:$2y$05$xxxxx...

Para añadir un usuario nuevo, solo tienes que añadir una línea al archivo. Traefik lo recarga automáticamente. Sin reinicios.

BasicAuth para el dashboard

Vamos a asegurar el dashboard de Traefik con autenticación básica. Ya vimos la configuración básica en el capítulo 5, pero aquí la refinamos con bcrypt y realm personalizado.

# En docker-compose.yml, labels del contenedor Traefik
labels:
  # Router del dashboard
  - "traefik.http.routers.dashboard.rule=Host(`traefik.tudominio.com`)"
  - "traefik.http.routers.dashboard.service=api@internal"
  - "traefik.http.routers.dashboard.middlewares=whitelist-dashboard,auth-dashboard"
  - "traefik.http.routers.dashboard.tls=true"

  # Whitelist para el dashboard
  - "traefik.http.middlewares.whitelist-dashboard.ipwhitelist.sourcerange=10.0.0.0/8,192.168.1.0/24"

  # Autenticación básica para el dashboard
  - "traefik.http.middlewares.auth-dashboard.basicauth.users=lorenzo:$$2y$$05$$xxxx..."
  - "traefik.http.middlewares.auth-dashboard.basicauth.realm=Dashboard Traefik"

Y el orden de los middlewares: whitelist-dashboard primero, auth-dashboard segundo. Si la IP no está en la whitelist, ni siquiera llega al login. El atacante recibe un 403 seco sin saber que existe un formulario de autenticación.

Limitaciones de BasicAuth

BasicAuth es simple, pero tiene limitaciones importantes que debes conocer antes de usarlo en producción seria:

  • No hay 2FA: no puedes añadir un segundo factor de autenticación. Si la contraseña se filtra, cualquiera puede acceder.
  • No hay sesiones: el navegador envía las credenciales en cada petición. No hay concepto de «iniciar sesión» y «cerrar sesión». Para cerrar sesión… cierras el navegador. O borras las credenciales guardadas.
  • No hay gestión de usuarios: añadir o eliminar usuarios requiere editar archivos de configuración. No hay interfaz web, no hay registro de usuarios, no hay recuperación de contraseña.
  • No hay control de acceso granular: un usuario o tiene acceso a todo el servicio o no tiene acceso. No puedes dar permisos de solo lectura a unos y administración a otros.
  • La experiencia de usuario es pobre: el diálogo de login del navegador es feo. No puedes personalizarlo, no puedes poner tu logo, no puedes mostrar mensajes de error bonitos.

Para qué sirve BasicAuth entonces:

  • Protección rápida de un servicio interno (un panel de métricas, una herramienta de administración).
  • Autenticación para el dashboard de Traefik.
  • Capa adicional de seguridad antes de un servicio que ya tiene su propio login (doble autenticación).
  • Entornos de staging o desarrollo.

Para producción con usuarios reales, necesitas algo más completo. Ahí entra ForwardAuth.

DigestAuth: la alternativa olvidada

Antes de pasar a ForwardAuth, mencionemos DigestAuth. Es otro middleware de Traefik, similar a BasicAuth pero con una diferencia: la contraseña no viaja ni siquiera en Base64. Usa un desafío-response con MD5.

El servidor envía un nonce (número aleatorio), el cliente calcula un hash con el nonce, el usuario, la contraseña y otros datos, y devuelve ese hash. La contraseña nunca viaja por la red, ni siquiera codificada.

http:
  middlewares:
    auth-digest:
      digestAuth:
        users:
          - "lorenzo:realm-htpasswd:hash-md5"
        realm: "Zona restringida"

Pero DigestAuth tiene problemas prácticos:

  • El hash MD5 de la contraseña se almacena en texto plano en el servidor. Si alguien accede al archivo de configuración, tiene el hash y puede usarlo para autenticarse.
  • No es compatible con todos los clientes HTTP. Algunas librerías y herramientas no lo implementan correctamente.
  • La experiencia de usuario es idéntica a BasicAuth: el mismo diálogo feo del navegador.
  • Tampoco tiene 2FA, ni sesiones, ni gestión de usuarios.

Mi recomendación: no uses DigestAuth. Si necesitas algo más seguro que BasicAuth pero no quieres montar un proveedor externo, mejor combina BasicAuth con whitelist de IP y un rate limit estricto. O salta directamente a ForwardAuth.

ForwardAuth: el middleware que delega

ForwardAuth cambia las reglas del juego. En lugar de que Traefik compruebe las credenciales directamente, delega la autenticación a un servicio externo. Traefik envía la petición original a ese servicio, y el servicio decide si la petición es válida o no.

El flujo es así:

  1. El cliente hace una petición a app.tudominio.com.
  2. Traefik recibe la petición y, antes de enviarla al backend, la envía al servicio de ForwardAuth.
  3. El servicio de ForwardAuth comprueba si la petición está autenticada (cookie de sesión, token JWT, cabecera de autorización, etc.).
  4. Si la petición no está autenticada, el servicio de ForwardAuth redirige al cliente a su página de login.
  5. Si la petición está autenticada, el servicio responde con un 200 OK y Traefik deja pasar la petición al backend.

Además, el servicio de ForwardAuth puede añadir cabeceras a la petición antes de que llegue al backend. Por ejemplo, puede añadir X-Forwarded-User: lorenzo para que el backend sepa qué usuario ha iniciado sesión.

Configurar ForwardAuth en Traefik

ForwardAuth se configura como un middleware más. El parámetro clave es address, la URL del servicio de autenticación.

# traefik-dynamic.yml
http:
  middlewares:
    forwardauth-authelia:
      forwardAuth:
        address: "http://authelia:9091/api/authz/forward-auth"
        trustForwardHeader: true
        authResponseHeaders:
          X-Forwarded-User: username
          X-Forwarded-Groups: groups
          X-Forwarded-Email: email

Vamos a desglosar los parámetros:

  • address: la URL del endpoint de forward auth del proveedor. Aquí es donde Traefik envía la petición original para que sea validada.
  • trustForwardHeader: confía en las cabeceras X-Forwarded-* que ya pueda tener la petición. Necesario si hay otro proxy delante (Cloudflare).
  • authResponseHeaders: cuando el proveedor responde con 200 OK, puede incluir cabeceras en su respuesta. Traefik las añade a la petición antes de enviarla al backend. Así el backend sabe quién es el usuario.

El servicio de forward auth recibe la petición original de Traefik con cabeceras especiales que describen la petición original: X-Forwarded-Method, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Uri. Con esa información, el servicio decide si permite o deniega el acceso.

Para aplicar el middleware a un servicio:

services:
  app-protegida:
    image: nginx
    labels:
      - "traefik.http.routers.app.rule=Host(`app.tudominio.com`)"
      - "traefik.http.routers.app.middlewares=forwardauth-authelia"

¿Cuándo usar ForwardAuth?

ForwardAuth es la opción correcta cuando:

  • Necesitas un sistema de autenticación completo con 2FA.
  • Tienes servicios que no tienen autenticación propia.
  • Quieres centralizar el login de todos tus servicios en un solo sitio.
  • Necesitas gestión de usuarios: altas, bajas, roles, grupos.
  • Quieres usar un proveedor externo como Authelia, Authentik, OAuth2 Proxy, o Keycloak.

ForwardAuth es el puente entre Traefik y los proveedores de autenticación. Sin él, no podrías integrarlos. Con él, la integración es limpia y el servicio de autenticación ni siquiera necesita saber que está detrás de Traefik.

ForwardAuth con OAuth2 Proxy

Antes de entrar en Authelia y Authentik, mencionemos OAuth2 Proxy. Es una herramienta ligera que proporciona forward auth usando proveedores OAuth2 externos (Google, GitHub, GitLab, Microsoft, etc.).

Si quieres que tus usuarios se autentiquen con su cuenta de Google en lugar de tener un usuario local en tu servidor, OAuth2 Proxy es tu herramienta.

http:
  middlewares:
    forwardauth-oauth:
      forwardAuth:
        address: "http://oauth2-proxy:4180/oauth2/auth"
        authResponseHeaders:
          X-Forwarded-User: X-Forwarded-User
          X-Forwarded-Email: X-Forwarded-Email

OAuth2 Proxy es más ligero que Authelia pero tiene menos funcionalidades. No tiene 2FA, no tiene políticas de acceso, no tiene portal de usuario. Simplemente redirige al proveedor OAuth2 y deja pasar si la autenticación es correcta.

Para un uso personal o con un equipo pequeño, OAuth2 Proxy puede ser suficiente. Para un uso más serio, tira de Authelia o Authentik.

Pocket ID: el rey de la autenticación sin contraseñas

Y ahora llega mi favorito. El que uso en mi servidor y el que recomiendo siempre que alguien me pregunta por un sistema de autenticación para self-hosted.

Pocket ID es un proveedor OIDC (OpenID Connect) ligero que permite a tus usuarios autenticarse exclusivamente mediante passkeys. Sin contraseñas. Sin 2FA. Sin SMS. Solo tu dispositivo, tu huella o tu cara, y ya estás dentro.

Si has escuchado el podcast de atareao.es sobre autenticación sin contraseñas con passkeys, sabes de lo que hablo. Si no, te resumo: las passkeys son un par de claves criptográficas (como las de SSH, pero para autenticación web). Tu dispositivo genera una clave privada que nunca sale de él, y una pública que se guarda en el servidor. Para autenticarte, el servidor te desafía y tú respondes con tu clave privada, que solo tú tienes.

¿Por qué Pocket ID es tan bueno para self-hosted?

  • Sin contraseñas que gestionar: no hay riesgo de filtraciones de credenciales, no hay que recordar contraseñas complejas, no hay que cambiarlas cada X meses.
  • Passkeys como único método: no tienes que preocuparte de configurar 2FA porque el propio passkey ya es un factor de autenticación fuerte (algo que tienes + algo que eres, si usas huella o Face ID).
  • Ligero: corre con SQLite, no necesita Redis, no necesita base de datos externa. Un contenedor Docker con 64 MB de RAM y ya funciona.
  • Soporta OIDC nativo: servicios como Grafana, Nextcloud, Authentik (sí, puede integrarse con Authentik), y muchos más pueden usar Pocket ID como proveedor OIDC directamente.
  • Para servicios sin autenticación: igual que con Authelia, puedes usar OAuth2 Proxy + Pocket ID para añadir autenticación a cualquier servicio.

Integración con Traefik vía ForwardAuth

Pocket ID no tiene un middleware ForwardAuth propio como Authelia. En su lugar, se integra con Traefik a través de OAuth2 Proxy. El flujo es:

  1. Traefik recibe una petición a un servicio protegido
  2. El middleware ForwardAuth redirige a OAuth2 Proxy
  3. OAuth2 Proxy inicia el flujo OIDC con Pocket ID
  4. Pocket ID pide al usuario autenticarse con su passkey
  5. Si la autenticación es exitosa, Pocket ID devuelve un token OIDC
  6. OAuth2 Proxy valida el token y deja pasar la petición a Traefik
  7. Traefik reenvía al servicio con los headers de usuario autenticado

Configuración paso a paso

Primero, despliega Pocket ID con Docker Compose:

services:
  pocket-id:
    image: ghcr.io/pocket-id/pocket-id:latest
    container_name: pocket-id
    restart: unless-stopped
    volumes:
      - ./data:/app/backend/data
    environment:
      - PUBLIC_APP_URL=https://auth.tudominio.com
      - TRUSTED_PROXIES=172.16.0.0/12
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pocketid.rule=Host(`auth.tudominio.com`)"
      - "traefik.http.routers.pocketid.entrypoints=websecure"
      - "traefik.http.routers.pocketid.tls.certresolver=letsencrypt"
      - "traefik.http.services.pocketid.loadbalancer.server.port=80"

Luego, despliega OAuth2 Proxy como companion:

services:
  oauth2-proxy:
    image: quay.io/oauth2-proxy/oauth2-proxy:latest
    container_name: oauth2-proxy
    restart: unless-stopped
    command:
      - --provider=oidc
      - --oidc-issuer-url=https://auth.tudominio.com
      - --client-id=tu-client-id
      - --client-secret=tu-client-secret
      - --redirect-url=https://tudominio.com/oauth2/callback
      - --cookie-secret=un-secreto-muy-largo-de-32-bytes
      - --cookie-domain=.tudominio.com
      - --email-domain=*
      - --upstream=static://202
      - --http-address=0.0.0.0:4180
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.oauth2-proxy.rule=Host(`tudominio.com`) && PathPrefix(`/oauth2`)"
      - "traefik.http.routers.oauth2-proxy.entrypoints=websecure"
      - "traefik.http.routers.oauth2-proxy.tls.certresolver=letsencrypt"
      - "traefik.http.services.oauth2-proxy.loadbalancer.server.port=4180"

Y el middleware ForwardAuth en Traefik:

http:
  middlewares:
    pocketid-auth:
      forwardAuth:
        address: http://oauth2-proxy:4180/oauth2/auth
        trustForwardHeader: true
        authResponseHeaders:
          X-Forwarded-User: X-Forwarded-User
          X-Forwarded-Email: X-Forwarded-Email

Ahora, para proteger cualquier servicio, solo tienes que añadir el middleware en el router:

labels:
  - "traefik.http.routers.mi-servicio.middlewares=pocketid-auth"

Cuando un usuario no autenticado intente acceder, será redirigido a OAuth2 Proxy, que a su vez lo redirigirá a Pocket ID para que se autentique con su passkey. Una vez autenticado, vuelve al servicio. Sin contraseñas, sin fricción.

Servicios con soporte OIDC nativo

Si el servicio que quieres proteger ya soporta OIDC (Grafana, Nextcloud, Authentik, MinIO, etc.), puedes configurarlo para que use Pocket ID directamente, sin necesidad de OAuth2 Proxy. La configuración varía según el servicio, pero el patrón es siempre el mismo:

  1. En Pocket ID, crea una aplicación y obtén el Client ID y Client Secret
  2. En el servicio, configura el proveedor OIDC con la URL de Pocket ID (https://auth.tudominio.com)
  3. El servicio redirige a Pocket ID para autenticación
  4. Pocket ID devuelve un token OIDC
  5. El servicio valida el token y deja pasar

¿Pocket ID vs Authelia vs Authentik?

Cada uno tiene su sitio:

  • Pocket ID: mejor para uso personal o equipos pequeños. Lo más sencillo de configurar. Autenticación con passkeys, punto. Sin 2FA (no lo necesita, el passkey ya es MFA). Sin políticas de acceso complejas. Si lo que quieres es «autentícate y entra», Pocket ID es tu herramienta.
  • Authelia: mejor si necesitas 2FA con TOTP o WebAuthn, políticas de acceso por ruta, o gestionar múltiples usuarios con roles. Más complejo de configurar, pero más potente.
  • Authentik: el más completo de los tres. IdP completo con catálogo de aplicaciones, flujos personalizables, integración con LDAP, y un largo etcétera. Si necesitas un sistema de identidad empresarial auto-hospedado, Authentik es la opción. Pero pagas en complejidad.

Mi recomendación: empieza con Pocket ID. Si te quedas corto (cosa poco probable en un homelab), migras a Authelia. Si necesitas un IdP completo, Authentik.

¿Por qué Pocket ID es mi favorito? Porque resuelve el 90% de los casos de uso con el 10% de la complejidad. Y porque las passkeys son el futuro de la autenticación: más seguras que las contraseñas, más cómodas que el 2FA, y no dependes de servicios externos.

Integración con Authelia

Authelia es un servidor de autenticación open source diseñado específicamente para integrarse con proxies inversos como Traefik. Ofrece:

  • Autenticación con usuario y contraseña.
  • Segundo factor (2FA) con TOTP (Google Authenticator, Authy), WebAuthn (llave de seguridad física), o código de respaldo.
  • Políticas de acceso por usuario, grupo, ruta, o método HTTP.
  • Portal de autenticación personalizable.
  • Registro de usuarios (opcional).
  • Olvido de contraseña y recuperación de 2FA.

La integración con Traefik se hace mediante ForwardAuth. Authelia expone un endpoint específico para ello: /api/authz/forward-auth.

Configuración de Traefik para Authelia

Este no es un tutorial completo de Authelia (eso será en el Bloque 3). Aquí te doy la configuración justa para que Traefik y Authelia hablen entre sí.

Primero, el middleware ForwardAuth que apunta a Authelia:

# traefik-dynamic.yml
http:
  middlewares:
    auth-authelia:
      forwardAuth:
        address: "http://authelia:9091/api/authz/forward-auth"
        trustForwardHeader: true
        authResponseHeaders:
          X-Forwarded-User: username
          X-Forwarded-Groups: groups
          X-Forwarded-Email: email
          X-Forwarded-Preferred-Username: preferred_username

Segundo, el router del propio Authelia. Authelia tiene que ser accesible públicamente para que los usuarios puedan hacer login. Pero también debe estar protegido por sí mismo (se autentica contra sí mismo, vaya).

services:
  authelia:
    image: authelia/authelia:latest
    labels:
      # Router para el portal de login de Authelia
      - "traefik.http.routers.authelia.rule=Host(`auth.tudominio.com`)"
      - "traefik.http.routers.authelia.middlewares=chain-seguridad"
      - "traefik.http.routers.authelia.tls=true"

      # Router para el endpoint de forward auth (solo interno, no público)
      - "traefik.http.routers.authelia-api.rule=Host(`auth.tudominio.com`) && PathPrefix(`/api/authz/forward-auth`)"
      - "traefik.http.routers.authelia-api.middlewares=auth-authelia"
      - "traefik.http.routers.authelia-api.tls=true"

Fíjate en el segundo router: el endpoint /api/authz/forward-auth está protegido por el mismo middleware auth-authelia. Suena a recursivo, pero no lo es. Cuando Authelia recibe la petición de forward auth, ya tiene la cookie de sesión del usuario. Si el usuario no ha iniciado sesión, Authelia lo redirige al portal de login. Si ha iniciado sesión, responde con 200 OK y las cabeceras del usuario.

Tercero, aplicar el middleware a los servicios que quieres proteger:

services:
  app-protegida:
    image: nginx
    labels:
      - "traefik.http.routers.app.rule=Host(`app.tudominio.com`)"
      - "traefik.http.routers.app.middlewares=auth-authelia,chain-seguridad"
      - "traefik.http.routers.app.tls=true"

Y ya está. Cuando un usuario no autenticado acceda a app.tudominio.com, Traefik enviará la petición a Authelia. Authelia verá que no hay cookie de sesión, responderá con un 302 redirigiendo al portal de login en auth.tudominio.com. El usuario inicia sesión (con 2FA si está configurado), Authelia establece la cookie de sesión, y redirige al usuario de vuelta a app.tudominio.com. Ahora con la cookie, Authelia responde 200 OK, y Traefik deja pasar al backend.

Políticas de acceso en Authelia

Authelia permite definir políticas de acceso basadas en el recurso solicitado. Por ejemplo:

# configuration.yml de Authelia
access_control:
  default_policy: deny

  rules:
    # La página de login de Authelia es pública
    - domain: "auth.tudominio.com"
      policy: bypass

    # Servicio público sin autenticación
    - domain: "public.tudominio.com"
      policy: bypass

    # Servicio con autenticación de un factor
    - domain: "app.tudominio.com"
      policy: one_factor

    # Servicio con doble factor
    - domain: "admin.tudominio.com"
      policy: two_factor

    # Solo un usuario concreto
    - domain: "sensibles.tudominio.com"
      policy: two_factor
      subject:
        - "user:lorenzo"

La política bypass salta la autenticación completamente. one_factor requiere usuario y contraseña. two_factor requiere además el segundo factor.

Authelia aplica estas políticas en el momento de la autenticación. Si un servicio tiene política one_factor y el usuario ya ha hecho login con 2FA en otro servicio, la sesión es válida y no se pide 2FA de nuevo.

Sesiones en Authelia

Authelia gestiona las sesiones mediante cookies. Cuando el usuario inicia sesión, Authelia establece una cookie HTTP-only segura. Mientras esa cookie sea válida, el usuario no tiene que volver a autenticarse. Por defecto, las sesiones duran 1 hora y se renuevan automáticamente si el usuario está activo.

La cookie se llama authelia_session y es específica del dominio. Si usas auth.tudominio.com para Authelia, la cookie solo es válida para ese dominio. Pero Traefik y Authelia trabajan juntos: cuando el usuario accede a app.tudominio.com, Authelia recibe la petición de forward auth y, como la cookie de sesión corresponde a auth.tudominio.com, el navegador la envía automáticamente. Todo funciona sin que el usuario tenga que hacer nada raro.

Integración con Authentik

Authentik es otro servidor de autenticación open source, similar a Authelia pero con un enfoque diferente. Mientras Authelia se centra en ser un añadido para proxies inversos, Authentik es más un Identity Provider (IdP) completo, comparable a Keycloak pero más moderno y ligero.

Diferencias principales entre Authelia y Authentik:

  • Authelia es más simple de configurar. Pensado específicamente para self-hosted con Traefik, Nginx o Caddy. Tiene lo justo: autenticación, 2FA, políticas de acceso. No es un IdP completo.
  • Authentik es más completo. Tiene gestión de usuarios y grupos, flujos de autenticación personalizables (stages), integración con LDAP, OAuth2, SAML, SCIM, y un portal de aplicaciones donde el usuario ve todos los servicios disponibles.

Para la integración con Traefik, el mecanismo es el mismo: ForwardAuth. Authentik expone un endpoint específico, al igual que Authelia.

Configuración de Traefik para Authentik

# traefik-dynamic.yml
http:
  middlewares:
    auth-authentik:
      forwardAuth:
        address: "http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
        trustForwardHeader: true
        authResponseHeaders:
          X-Forwarded-User: username
          X-Forwarded-Groups: groups
          X-Forwarded-Email: email
          X-Forwarded-Preferred-Username: preferred_username
          X-Authentik-Username: username
          X-Authentik-Groups: groups
          X-Authentik-Email: email
          X-Authentik-Outpost: outpost
          X-Authentik-Meta-Authorization: authorization
          X-Authentik-Meta-JWT: jwt

Fíjate en la URL del endpoint: /outpost.goauthentik.io/auth/traefik. Authentik usa el concepto de «outpost», que es un componente que se encarga de la comunicación con el proxy inverso. Puedes tener un outpost para Traefik, otro para Nginx, otro para Cloudflare. Cada outpost tiene su propia configuración.

También hay más cabeceras de respuesta disponibles. Authentik puede pasar mucha información al backend: nombre de usuario, grupos, email, el token JWT completo, y metadatos de autorización.

El router de Authentik

Authentik necesita su propio router, igual que Authelia:

services:
  authentik-server:
    image: ghcr.io/goauthentik/server:latest
    labels:
      - "traefik.http.routers.authentik.rule=Host(`auth.tudominio.com`)"
      - "traefik.http.routers.authentik.middlewares=chain-seguridad"
      - "traefik.http.routers.authentik.tls=true"

  authentik-worker:
    image: ghcr.io/goauthentik/worker:latest
    # El worker no necesita router, solo está en la red interna

Y en Authentik, creas un outpost de tipo «Traefik» que apunte a la URL de forward auth. Authentik genera automáticamente la configuración del middleware para que solo tengas que copiarla.

Diferencias prácticas

¿Cuál elegir, Authelia o Authentik?

Para un servidor personal con pocos servicios, Authelia es más sencillo. La configuración es más directa, consume menos recursos, y hace exactamente lo que necesitas: proteger servicios con login y 2FA.

Para un servidor con varios usuarios, equipos, o si necesitas integración con LDAP/Active Directory, Authentik es más adecuado. La gestión de usuarios es más completa y el portal de aplicaciones da una experiencia más profesional.

En ambos casos, la integración con Traefik es la misma: un middleware ForwardAuth que apunta al endpoint correspondiente. Cambia la URL, cambia las cabeceras de respuesta, pero el concepto es idéntico.

La cadena completa del Bloque 2

Has llegado al final del Bloque 2. Es hora de montar la cadena completa de middlewares que protege todos tus servicios.

Este es el orden que hemos construido a lo largo de los capítulos 5, 6, 7 y 8:

1. ipWhiteList      -> capítulo 6: solo IPs autorizadas
2. rateLimit        -> capítulo 6: frenar abusos
3. forwardAuth      -> capítulo 8: autenticación centralizada
4. headers          -> capítulo 7: cabeceras de seguridad
5. compress         -> capítulo 6: compresión gzip
6. backend          -> tu servicio

Cada middleware tiene su propósito y su orden está pensado para ser eficiente:

  • Whitelist primero: es la comprobación más rápida. Un simple match de IP. Sin ella, un atacante podría llegar a la autenticación y empezar a probar contraseñas. Con ella, ni siquiera llega.
  • Rate limit segundo: antes de gastar recursos en autenticación, aseguras que el cliente no está abusando del sistema. Un rate limit de 5 peticiones por segundo evita que un ataque de fuerza bruta tenga éxito, aunque la whitelist sea amplia.
  • ForwardAuth tercero: solo los clientes que han pasado los filtros anteriores llegan a pedir autenticación. Si están en la whitelist y no superan el rate limit, el siguiente paso es comprobar si tienen sesión válida.
  • Headers cuarto: las cabeceras de seguridad se aplican al contenido que va a viajar al cliente autenticado. No tiene sentido ponerlas antes, porque una respuesta 401 o 403 no necesita cabeceras de seguridad de contenido.
  • Compress quinto: la compresión se aplica al contenido final que va al cliente. Es el último paso antes del backend.

Configuración completa en traefik-dynamic.yml

# traefik-dynamic.yml
http:
  middlewares:

    # 1. IP Whitelist
    whitelist-vpn:
      ipWhiteList:
        sourceRange:
          - "10.0.0.0/8"          # VPN WireGuard
          - "192.168.1.0/24"      # Red local

    # 2. Rate Limiting
    ratelimit-global:
      rateLimit:
        average: 100
        burst: 200
        sourceCriterion:
          ipStrategy:
            depth: 1

    # Rate limit estricto para login
    ratelimit-login:
      rateLimit:
        average: 5
        burst: 10
        sourceCriterion:
          ipStrategy:
            depth: 1

    # 3. ForwardAuth (cambia según tu proveedor)
    auth-authelia:
      forwardAuth:
        address: "http://authelia:9091/api/authz/forward-auth"
        trustForwardHeader: true
        authResponseHeaders:
          X-Forwarded-User: username
          X-Forwarded-Groups: groups
          X-Forwarded-Email: email

    # 4. Headers de seguridad
    headers-seguridad:
      headers:
        strictTransportSecurity:
          maxAge: 31536000
          includeSubDomains: true
          preload: true
        customFrameOptionsValue: DENY
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: strict-origin-when-cross-origin
        customResponseHeaders:
          Content-Security-Policy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
          Permissions-Policy: "camera=(), microphone=(), geolocation=(), interest-cohort=()"
          Server: ""
          X-Powered-By: ""

    # 5. Compresión
    compress-gzip:
      compress:
        excludedContentTypes:
          - "text/event-stream"

    # Cadena completa
    chain-bloque2:
      chain:
        middlewares:
          - whitelist-vpn
          - ratelimit-global
          - auth-authelia
          - headers-seguridad
          - compress-gzip

    # Cadena para servicio sensible (login con rate limit estricto)
    chain-bloque2-login:
      chain:
        middlewares:
          - whitelist-vpn
          - ratelimit-login
          - auth-authelia
          - headers-seguridad
          - compress-gzip

Aplicar la cadena a un servicio

services:
  app-protegida:
    image: nginx
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.tudominio.com`)"
      - "traefik.http.routers.app.middlewares=chain-bloque2"
      - "traefik.http.routers.app.tls=true"

Con una sola línea de middleware (chain-bloque2), el servicio tiene whitelist, rate limit, autenticación, cabeceras de seguridad y compresión. Cinco middlewares en una referencia.

Si tienes un servicio público que no necesita autenticación, crea una cadena sin el middleware de ForwardAuth:

http:
  middlewares:
    chain-publico:
      chain:
        middlewares:
          - ratelimit-global
          - headers-seguridad
          - compress-gzip

Y si tienes un servicio interno que solo quieres que accedan desde la VPN, sin autenticación adicional:

http:
  middlewares:
    chain-interno:
      chain:
        middlewares:
          - whitelist-vpn
          - ratelimit-global
          - headers-seguridad
          - compress-gzip

La flexibilidad de las cadenas te permite tener perfiles de seguridad diferentes para cada tipo de servicio, manteniendo la configuración centralizada y reutilizable.

Verificación

Has configurado la autenticación. Pero, ¿cómo sabes que funciona realmente? Vamos a probarlo.

Probar una ruta autenticada sin credenciales

curl -v https://app.tudominio.com

Si todo está bien configurado, deberías ver una respuesta como esta:

* Connected to app.tudominio.com
> GET / HTTP/1.1
> Host: app.tudominio.com
>
< HTTP/1.1 401 Unauthorized
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: ...
< Content-Length: ...

O, si usas Authelia/Authentik, una redirección:

< HTTP/1.1 302 Found
< Location: https://auth.tudominio.com/?rd=https://app.tudominio.com/

El código 401 (BasicAuth) o 302 (ForwardAuth) indica que la autenticación está funcionando. Sin credenciales, no se pasa.

Probar con credenciales correctas (BasicAuth)

curl -v -u lorenzo:mi-contraseña https://app.tudominio.com

Deberías recibir un 200 OK con el contenido del servicio.

Probar con credenciales incorrectas (BasicAuth)

curl -v -u lorenzo:contraseña-mala https://app.tudominio.com

Respuesta esperada: 401 Unauthorized.

Probar con sesión válida (ForwardAuth)

Con ForwardAuth, la autenticación depende de una cookie de sesión. Para probarlo desde línea de comandos:

# Primero inicia sesión y captura la cookie
curl -v -c cookies.txt -L https://auth.tudominio.com 2>&1 | grep -i 'set-cookie'

# Luego usa la cookie para acceder al servicio protegido
curl -v -b cookies.txt https://app.tudominio.com

Verificar que la cadena completa funciona

Ahora prueba que la whitelist y el rate limit también están activos:

# Desde una IP no autorizada (por ejemplo, desde un VPS externo)
curl -v https://app.tudominio.com
# Deberías recibir un 403 Forbidden (whitelist)

# Haciendo muchas peticiones rápidas
for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" https://app.tudominio.com & done
# Deberías ver algún 429 Too Many Requests (rate limit)

Si ves 403 cuando pruebas desde una IP no autorizada, la whitelist funciona.
Si ves 429 cuando haces muchas peticiones seguidas, el rate limit funciona.
Si ves 401 o redirección al login cuando no envías credenciales, la autenticación funciona.
Si ves cabeceras de seguridad en la respuesta, los headers funcionan.

Artículos relacionados

Conclusión

En este capítulo has visto como centralizar la autenticación de tus servicios en Traefik.

  • BasicAuth: autenticación básica integrada en Traefik. Simple, sin dependencias externas, pero limitada: no tiene 2FA, no tiene sesiones, la experiencia de usuario es pobre. Ideal para proteger el dashboard o servicios internos.
  • Generación de usuarios con htpasswd: hashes MD5 (apr1) y bcrypt. Los dos formatos que soporta Traefik para almacenar contraseñas de forma segura.
  • DigestAuth: la alternativa olvidada. No la uses. BasicAuth con HTTPS es más práctica.
  • ForwardAuth: el middleware que delega la autenticación a un servicio externo. Traefik envía la petición al servicio, y este decide si la petición es válida. La clave para integrar Authelia, Authentik, OAuth2 Proxy, o cualquier otro proveedor.
  • Integración con Authelia: middleware ForwardAuth apuntando a /api/authz/forward-auth, router para el portal de login, políticas de acceso con uno o dos factores.
  • Integración con Authentik: mismo concepto, endpoint diferente (/outpost.goauthentik.io/auth/traefik), más cabeceras de respuesta y más opciones de configuración.
  • Cadena completa del Bloque 2: whitelist -> rate limit -> forward auth -> headers -> compress. El orden correcto para máxima eficiencia y seguridad.
  • Verificación: pruebas con curl para comprobar que la autenticación, whitelist y rate limit funcionan como esperas.

Deja una respuesta