Header-basierte Authentifizierung. Z.B. Forward Auth

Begonnen von Sidey, 27 März 2026, 09:11:09

Vorheriges Thema - Nächstes Thema

Sidey

Hallo Zusammen,

Ich habe eine Erweiterung vorbereitet, welche das `allowed` Modul um eine optionale Header-basierte Authentifizierung für FHEMWEB erweitert.

Motivation
In Reverse-Proxy-Setups mit vorgelagerter Authentifizierung, z.B. über nginx, Apache, Traefik ForwardAuth, Caddy, oauth2-proxy oder einen dedizierten Auth-Server wie Authelia, steht die Identität des Benutzers oft bereits in HTTP-Headern zur Verfügung. Solche Komponenten können z.B. per Forward-Auth entscheiden, ob ein Request zugelassen wird, und anschließend Benutzer- und Gruppeninformationen in Headern an das Backend weiterreichen.

Bisher kann `allowed` in FHEMWEB praktisch nur `basicAuth` direkt auswerten. Das ist für Direktzugriffe weiterhin sinnvoll, für Proxy-basierte Setups aber etwas unflexibel.

Die Erweitgerung ergänzt deshalb ein neues Attribut `headerAuthPolicy`, mit dem sich eine Header-Policy als JSON formulieren lässt. Unterstützt werden verschachtelte `AND`/`OR`-Gruppen und einfache Matcher wie `present`, `equals`, `notEquals`, `regex`, `contains`, `prefix` und `suffix`.

Beispiel
attr allowedWEB headerAuthPolicy {"op":"AND","items":[{"header":"X-Forwarded-User","match":"present"},{"op":"OR","items":[{"header":"X-Role","match":"equals","value":"fhem-admin"},{"header":"X-Forwarded-Groups","match":"contains","value":"admins"}]}]}
Schematische Darstellung

Browser
  |
  v
Reverse Proxy / Auth Layer
  |
  |-- z.B. nginx, Apache, Traefik, Caddy
  |-- optional mit vorgelagertem Auth-Server
  |     z.B. Authelia per Forward-Auth
  |-- authentifiziert Benutzer
  |-- setzt Header wie:
  |     X-Forwarded-User: alice
  |     X-Forwarded-Groups: admins,users
  |     X-Role: fhem-admin
  |
  v
FHEMWEB
  |
  v
allowed
  |
  |-- prüft noCheckFor
  |-- prüft headerAuthPolicy
  |-- optional Fallback auf basicAuth
  |
  v
Zugriff erlaubt / verweigert

Typische Szenarien
- nginx mit auth_request
- Apache mit vorgelagerter Authentifizierung
- Traefik mit ForwardAuth
- Caddy mit vorgelagerter Authentifizierung oder Header-Weitergabe
- Authelia als externer Auth-Server, z.B. per Forward-Auth vor FHEM
- oauth2-proxy

Warum die Erweiterung aus meiner Sicht sinnvoll ist
1. FHEM kann damit sauber hinter einem Reverse Proxy betrieben werden, ohne zusätzlich Browser-Basic-Auth erzwingen zu müssen.
2. Die Authentifizierungsentscheidung bleibt zentral im `allowed`-Modul, also dort, wo heute bereits `basicAuth`, `password`, `validFor` und die Frontend-Zuordnung liegen.
3. Benutzer sind nicht geneigt, die Authentifizierung in FHEM völlig zu deaktivieren, wenn der Proxy die Authentifizierung bereits übernimmt.
4. Die Funktion kann auch bei einer Zertifikats basierten Authentifizierung eingesetzt werden.
5. Mehrere Header und kombinierte Bedingungen lassen sich explizit und validierbar beschreiben, statt einzelne Sonderfälle in Perl-Ausdrücken nachzubauen.
6. Die Policy wird bereits beim Setzen des Attributs validiert. Fehlerhafte JSON- oder Regex-Angaben fallen also sofort auf und nicht erst zur Laufzeit.
7. API Zugriffe könnten in Zukunft ebenso davon profitieren.

Implementierungsszenario
Die eigentliche Policy-Auswertung liegt in einem kleinen Core-Modul `FHEM::Core::Authentication::HeaderPolicy`. `96_allowed.pm` nutzt dieses Modul dann als Integrationsschicht:
- Attribut `headerAuthPolicy` lesen und validieren
- bei FHEMWEB-Requests die Header gegen die Policy prüfen
- bei Erfolg authentifizieren
- bei Nicht-Match optional auf bestehendes `basicAuth` zurückfallen

Wichtig zur Rückwärtskompatibilität
Die bestehende `basicAuth`-Funktion wird nicht verändert. Der Patch ist bewusst additiv:
- Ohne `headerAuthPolicy` verhält sich `allowed` exakt wie bisher.
- Wenn nur `headerAuthPolicy` gesetzt ist, erfolgt die Authentifizierung ausschließlich darüber.
- Wenn `headerAuthPolicy` und `basicAuth` gesetzt sind, dann gilt:
  - Header-Match erfolgreich: Zugriff erlaubt
  - Header-Match nicht erfolgreich: der bisherige `basicAuth`-Pfad bleibt unverändert als Fallback aktiv

Damit bleiben bestehende Installationen unverändert, und Proxy-basierte Installationen bekommen einen zusätzlichen sauberen Authentifizierungsweg.

Verhalten bei Fehlern
Wenn nur `headerAuthPolicy` gesetzt ist und die Header nicht passen, wird der Zugriff mit `403 Forbidden` abgelehnt. Es wird dann bewusst kein `WWW-Authenticate: Basic` ausgelöst, weil in diesem Modus kein Browser-Basic-Auth-Dialog gewünscht ist.

Tests
Ich habe dazu auch bereits begonnen Unit-Tests für die Policy-Auswertung als auch FHEMWEB-Integrationstests zu erstellen:
- bestehendes `basicAuth` bleibt grün
- Header-Auth Erfolg
- Header-Auth Misserfolg mit `403 Forbidden`
- Fallback auf `basicAuth`, wenn beide Mechanismen konfiguriert sind
- Zusammenspiel mit `noCheckFor`
- Validierung ungültiger Policy beim Setzen des Attributs


Bevor ich den Patch und das Core Modul bereitstelle, gibt es noch Anforderungen die ich berücksichtigen soll/muss?

Grüße Sidey
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem,zigbee2mqtt

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

rudolfkoenig

ZitatIch habe eine Erweiterung vorbereitet, welche das `allowed` Modul um eine optionale Header-basierte Authentifizierung für FHEMWEB erweitert.
Waere es nicht besser ein eigenes Modul dafuer zu bauen, die eine $hash->{AuthorizeFn} implementiert?
Die Authorize() Funktion (was FHEMWEB, etc verwendet) "addiert" die Rueckgabewerte aller solcher Funktionen:
          ($r == 0 ? "dont care" : $r == 1 ? "allowed" : "prohibited");

rudolfkoenig

Sorry, den Namen verwechselt. es geht hier um Authentifizierung, ergo $hash->{AuthenticateFn}.

Aber auch hier gilt, die Authenticate Funktion addiert die Rueckgabewerte:
Zitat#####################################
# Return
# - 0 for authentication not needed
# - 1 for auth-ok 
# - 2 for wrong username/password
# - 3 authentication not needed this time (FHEMWEB special)
3 hat was mit dem noCheckFor Attribut zu tun (https://forum.fhem.de/index.php?topic=141561)