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)

Sidey

Hallo Rudi,

`$hash->{AuthenticateFn}` hatte ich nicht so richtig auf dem Schirm.
Vielen Dank für diesen Hinweis.

Ich habe über den Vorschlag nachgedacht und denke, ein eigenes Modul wäre hier durchaus möglich.
Eventuell implementiert auch noch mal jemand (oder ich) OIDC.

Ich werde jetzt sowas wie `98_headerAuth.pm` erstellen.
Ich fokussiere mich also auf Authentifizierung.


Das scheint mir die saubere Lösung zu sein und die Autorisierung in allowed zu belassen.


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

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

Sidey

Kurzes Update zum Stand von WebAuth:
Das Modul ist inzwischen grundlegend funktional und läuft bei mir in der Praxis.
Aktuell liegt es aber noch nur auf GitHub und ist noch nicht in SVN, obwohl ich schon kurz vor dem Checkin stand:
https://github.com/fhem/WebAuth

Der Hauptgrund ist nicht die eigentliche Funktion, sondern dass ich bei zwei Punkten vor einer Veröffentlichung noch unsicher bin:

Die Sprache für headerAuthPolicy:
Aktuell ist das eine JSON-basierte Policy-Beschreibung für Header-Matching. Das funktioniert technisch gut und vermeidet direkt ausführbaren Perl-Code, aber ich bin unsicher, ob das im FHEM-Kontext als passend/intuitiv genug empfunden wird, weil an anderen Stellen ja durchaus auch Perl-Ausdrücke als Eingabe erlaubt sind.

Das Zusammenspiel mit allowed:
Mit strict=0 kann WebAuth bei fehlenden relevanten Headern an einen nachgelagerten Authentikator wie allowed mit basicAuth erlauben, mit strict=1 wird direkt abgelehnt. Das ist technisch umsetzbar, aber ich bin unsicher, ob dieses Verhalten aus Nutzersicht wirklich intuitiv genug ist.
mit strict=0 und ohne nachgelagertes allowed steht die Instanz wieder offen, daher ist default natürlich strict=1

Warum das ganze? Ich habe eine FHEMWEB Instanz, wenn ich dort über meinen Proxy zugreife, dann soll die headerauth verwendet werden. Wenn ich meinen Proxy umgehe, will ich keine offene FHEMWEB Instanz haben.
Lösbar wäre es natürlich, indem getrennte FHEMWEB Instanzen verwendet werden.



Bevor ich das nach SVN bringe, hätte ich deshalb gern noch etwas Feedback, ob noch etwas grundlegend anders aufziehen sollte. Und ich wollte jetzt auch nicht einfach ein faules Ei in die Osternester legen :)



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

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

CoolTux

Kann ich das irgendwie mit meinen Keycloak verbinden?
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Sidey

Zitat von: CoolTux am 03 April 2026, 21:03:35Kann ich das irgendwie mit meinen Keycloak verbinden?

Ja das sollte gehen.
Ich habe es mit Authelia und Caddy als Proxy im Einsatz.

Keycloak kann meines Wissens nach auch forward-Auth.
Hauptsache die Header kommen an FHEM an.
Das Modul kann aber derzeit kein OIDC.
Dafür wäre noch etwas mehr notig.

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

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

betateilchen

Zitat von: Sidey am 03 April 2026, 20:39:07Aktuell liegt es aber noch nur auf GitHub und ist noch nicht in SVN, obwohl ich schon kurz vor dem Checkin stand:
https://github.com/fhem/WebAuth

Der Hauptgrund ist nicht die eigentliche Funktion, sondern dass ich bei zwei Punkten vor einer Veröffentlichung noch unsicher bin:

Dann checke das doch vorläufig im ./contrib Zweig (z.B. unter ./contrib/Sidey) ein, dann hat man als FHEM User wenigstens die Möglichkeit, das auch mal "einfach" zu testen, anstatt immer mehr github repos abonnieren zu müssen.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Sidey

Zitat von: betateilchen am 04 April 2026, 10:56:08anstatt immer mehr github repos abonnieren zu müssen.


Du musst nichts abonieren.
update all <controls datei und das Modul wird einmalig installiert.
Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem,zigbee2mqtt

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