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.
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 :-)
Zur TYPO3-Dokumentation (Services API): https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Services/Developer/ServiceApi.html#services-developer-service-api
Kommentare (7)
Uwe Michelfelder
am 19.05.2020Klaus Hörmann-Engl
am 20.05.2020Marc Willmann
am 20.05.2020Eine 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
am 20.05.2020Theodor Klamm
am 26.10.2020sehr 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
am 15.12.2021danke 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
am 03.06.2022sorry, 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.