Einer der beliebtesten Artikel, die ich auf meinem alten Blog veröffentlicht habe, war eine Anleitung, wie man Frontend-Benutzer gegen andere Datenquellen authentifizieren kann. Dieser Beitrag ist natürlich in die Jahre gekommen, weshalb ich diese Anleitung gerne erneuere und zeige, wie man das mit einem aktuellen TYPO3 (9LTS, 10LTS) realisieren kann. 

TYPO3 Frontent-Benutzer gegen externe Datenquelle authentifizieren

Frontend-Benutzer gegen andere Datenquelle authentifizieren

TYPO3 bringt von Haus aus eine flexible und leistungsfähige Möglichkeit der Benutzerauthentifikation gegen die lokale Datenbank mit. Auch Gruppenrechte sind damit problemlos möglich.

Manchmal ist es aber notwendig oder gewünscht, dass die Websitebesucher sich nicht gegen die lokale Datenbank anmelden, sondern ein anderer Dienst genutzt werden soll. Das kann eine andere Datenbank, ein Login-Service oder ein Webservice sein (oder natürlich auch etwas ganz anderes). Wie bringt man nun aber TYPO3 bei, dass diese Loginquelle benutzt wird?

Auth Services

Zunächst einmal müssen wir verstehen, wie TYPO3 bei einem Login eines Frontend-Users vorgeht. In der Datenbanktabelle fe_users sind die relevanten Informationen gespeichert; diese werden beim Login in einen User-Datensatz geladen, der dann wiederum über die bekannten Mechanismen von allen Plugins und Extensions genutzt werden kann. 

Wir müssen also sicherstellen, dass die Informationen unseres Logins ebenfalls zu einem solchen User-Datensatz führen und die notwendigen Informationen zur Verfügung stellen.

Das bedeutet insbesondere, dass in dieser Datenbanktabelle für jeden Benutzer auch ein Datensatz stehen muss, auch wenn wir gegen eine andere Datenbank oder gegen einen Webservice authentifizieren.

In medias res

Um einen neuen Auth-Service zu erstellen, müssen wir eine Extension programmieren. Dank der sauberen API, die TYPO3 mitbringt, ist diese allerdings nicht sonderlich kompliziert. 

In der ext_localconf.php registrieren wir den neuen Service:

 

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addService(
    // Extension Key
    'my_authentication',
    // Service type
    'auth',
    // Service key
    'tx_myauthentication_login',
    array(
        'title' => 'My Authentication',
        'description' => 'An alternative way to login frontend users',

        'subtype' => '',

        'available' => true,
        'priority' => 60,
        'quality' => 80,

        'os' => '',
        'exec' => '',

        'className' => \MarcWillmann\MyAuthentication\Service\MyAuthenticatorService::class
    )
);

 

Über die Parameter "priority" und "quality" wird die Reihenfolge festgelegt. Der Service mit der höchsten Priorität (maximal 100) wird zuerst abgefragt; danach ggf. (darauf kommen wir später zurück) die anderen. Werden mehrere Services mit der gleichen Priorität registriert, "gewinnt" derjenige mit der höchsten Qualität.

Nun da wir den Service im System angemeldet haben, müssen wir ihn natürlich auch implementieren. Hierzu erstellen wir also eine Klasse, die von der Klasse \TYPO3\CMS\Core\Authentication\AbstractAuthenticationService erbt. Bevor wir dies tun, überlegen wir aber noch kurz, wie kompliziert unsere Lösung sein muss:

Keine Personalisierten Inhalte

In manchen Szenarien genügt es, einer Benutzergruppe Zugang zu erlauben oder eben zu verweigern. Das ist immer dann der Fall, wenn im Zugangsbeschränkten Bereich keine personalisierten Texte ausgegeben werden sollen und wenn keine speziellen Rechte vergeben werden. In diesem einfachen Szenario geht es also nur um die Frage, ob ein Benutzer die Inhalte sehen darf oder nicht. 

Hier genügt es, einen einzigen Datensatz in der FE-Usertabelle zu haben und abhängig von der Datenquelle auf diesen Benutzer zu mappen (der Besucher darf "eintreten") oder eben nicht. 

Oder volle Flexibilität

Wollen wir den Benutzer später persönlich ansprechen oder Gruppenrechte übernehmen, müssen wir für jeden Benutzer einen eigenen Datensatz in der FE-Usertabelle erzeugen. Hier sollte man sich dann auch überlegen, wie wir diese Einträge aus der Tabelle wieder entfernen, wenn sie nicht mehr benötigt oder abgelaufen sind. 

Ein einfacher Auth-Service

Nun geht es aber an den Code. Wir erstellen eine neue Datei Authenticator.php im Verzeichnis Classes/Services/ und implementieren unseren Authentication Service: 

 

<?php
namespace MarcWillmann\MyAuthentication\Service

use \TYPO3\CMS\Core\Authentication\AbstractAuthenticationService;

class MyAuthenticatorService extends AbstractAuthenticationService 
{
   public function init() : boolean
   {
      // do all necessary stuff to enable the connection to external service 
      // and check availability of all necessary tools.

      // returns true if all dependencies are fulfilled, else false.
   }

   public function reset() : void
   {
      // If needed: clean up and forget values of former authentications
   }

   public function authUser(array $user): int
   {
      // decide, if the given $user is allowed to acccess or not
   }

   /**
    * returns false or the user array
    **/ 
   public function getUser() : mixed
   {
      // is called to get additional information after login.
   }
}

 

Die init-Methode wird aufgerufen, wenn der Service initialisiert wird. Hier sollte darauf geprüft werden, ob der Service arbeiten kann - sind alle Werkzeuge auf dem System vorhanden? Ist der Webservice/die Datenbank/was-auch-immer erreichbar? Falls alles passt, liefert diese Methode true zurück, andernfalls false.

Wird ein Service mehr als einmal aufgerufen, wird keine neue Instanz erzeugt. Die reset-Methode ist der richtige Ort, wenn bei einem mehrfachen Aufruf Aufräumarbeiten erledigt werden müssen.

Die authUser()-Methode ist der eigentliche Check, ob der Benutzer Zugriff bekommt oder nicht. Wichtig: Diese Methode liefert keinen Boolean-Wert zurück, sondern einen Integer! Der Rückgabecode ist wichtig für die Frage, ob noch weitere Authentication-Services angefragt werden, für den Fall, dass der Benutzer nicht eingeloggt werden konnte. (Rückgabecode 0 (oder kleiner): Login failed, es werden auch keine weiteren Services mehr gefragt. Rückgabecode >= 200: Login hat geklappt, Benutzer ist angemeldet. Rückgabecode 100 <= x < 200: Service ist nicht zuständig, Benutzer ist nicht angemeldet: es sollen aber der nächste Service in der Reihe übernehmen). 

Hier sieht man auch sehr schön, dass die Priorität der Services durchaus wichtig ist.

Wichtig: Wie oben erwähnt ist es wichtig, dass in der fe_user-Tabelle ein passender Datensatz vorhanden ist. Sollen personalisierte FE-User-Datensätze verwendet werden, genügt es NICHT, auf einen bestehenden User zu mappen - in diesem Fall muss der Datensatz in der authUser()-Methode (aus der externen Datenquelle) zuerst in die fe_user-Tabelle geschrieben werden.

function getUser()

Diese Funktion wird von TYPO3 aufgerufen, wenn Details zu dem eingeloggten Benutzer abgefragt werden. Auch hier kann natürlich die externe Datenquelle gefragt, in der fe_user-Tabelle nachgeschlagen werden (wenn der Datensatz dort eingetragen wurde) oder ein Standarddatensatz zurückgegeben werden.

Damit ist das Grundgerüst für einen eigenen Auth-Service auch schon fertig. Viel Spaß beim implementieren der eigenen Datenquelle :-)

Kommentare (7)

  • Uwe Michelfelder
    Uwe Michelfelder
    am 19.05.2020
    Vielen Dank für diese kurze und verständliche Einführung!
  • Klaus Hörmann-Engl
    Klaus Hörmann-Engl
    am 20.05.2020
    Hallo Marc. Funktioniert das auch in TYPO3 10? Oder brauchst da eine Middleware? Danke LG Klaus
    • Marc Willmann
      Marc Willmann
      am 20.05.2020
      Hi. Die hier vorgestellte Lösung ist State-of-the-Art für 9LTS UND 10LTS.

      Eine Middleware hilft an dieser Stelle nicht wirklich; TYPO3 fragt an vielen vielen Stellen nach eingeloggten Usern und den entsprechenden Gruppen; die willst Du nicht alle abfangen. Genau aus dem Grund gibt’s ja die Service API und die Authentication Services. ?
    • Klaus Hörmann-Engl
      Klaus Hörmann-Engl
      am 20.05.2020
      Super. Danke. Ich habe nämlich auch so einen Service für SAML implementiert. Funzt sehr gut unter 9. Jetzt mach ich bald ein Update auf 10. Dann muss ich hier nichts ändern. Danke. Super Blog Post.
  • Theodor Klamm
    Theodor Klamm
    am 26.10.2020
    Hallo Marc,

    sehr schöner Artikel. Ich möchte aktuell eine Oauth2 Schnittstele „befragen“ und wollte eigentlich einen AuthenticationService dafür erstellen, aber ich habe bisher nicht herausgefunden, wie ich das ganze aufbauen muss. Ich möchte demnach kein Formular mit User + Passwort haben, sondern anhand des Redirects (Get-Parameter mit state und code) nachschlagen, ob ein user existiert. Dann werden aber die Funktionen nicht „gefeuert“ das scheint nur zu passieren, wenn ich ein Formular mit den Daten abschicke (authUser()). Kannst du dazu vielleicht etwas helfen?
    Vielen Dank. LG Theodor
  • Martin Weymayer
    Martin Weymayer
    am 15.12.2021
    Hallo,
    danke für den Artikel. Leider verstehe ich nicht, wie man bei der einfachen Variante den User "von der Datenquelle auf diesen Benutzer zu mappen"
    Danke für die Hilfe!
    Martin
    • Marc Willmann
      Marc Willmann
      am 03.06.2022
      Hi Martin,

      sorry, Dein Kommentar ist mir leider durchgerutscht. Das "mappen" klingt nur kompliziert; im Wesentlichen hast Du einen (!) FE-User-Datensatz in der Tabelle "Max Mustermann" mit der UID 1 (oder was auch immer) und Dein getUser() liefert die Informationen aus diesem Datensatz zurück.

      Damit kannst Du dann eben keine individuelle Ansprache machen (der User heisst eben IMMER "Max Mustermann"), aber Zugriff auf einzelne Bereiche kannst Du damit gut und einfach abbilden.

Neue Antwort auf Kommentar schreiben