Hinzufügen einer neuen „managed“-Authentifizierungsmethode zu Discourse

Fortsetzung von Future Social Authentication Improvements

Wir befinden uns derzeit im Prozess, alle Informationen zu „verbundenen Konten“ in eine einzelne Datenbanktabelle zu überführen. Dies hilft, doppelte Logik erheblich zu reduzieren und ermöglicht eine schnellere Entwicklung in der Zukunft. Zum Beispiel hat die Migration unserer Kern-Twitter-Logik auf das neue System die Anzahl der Codezeilen von 136 auf nur 24 reduziert :tada:.

Dieser Beitrag ist nicht als Schritt-für-Schritt-Anleitung zum Hinzufügen eines neuen Authentifizierungsanbieters gedacht, sondern soll einen Überblick geben und bei Bedarf auf den relevanten Quellcode verweisen.

Implementierung eines Authenticators

Jeder Authenticator muss eine Unterklasse von Auth::Authenticator implementieren. Um die neue gemeinsame Logik zu nutzen, kann der Authenticator stattdessen Auth::ManagedAuthenticator erweitern. Ein Beispiel für eine schlichte Implementierung findet sich im Kern-Facebook-Authenticator:

name, enabled? und register_middleware müssen von den implementierenden Klassen überschrieben werden.

:information_source: Randnotiz: Für die Multi-Site-Kompatibilität ist es wichtig, dass standortspezifische Informationen Omniauth in einem setup-Lambda bereitgestellt werden, anstatt sie zum Zeitpunkt der Definition festzulegen. Alle Kern-Authenticators enthalten Beispiele hierfür.

Die gesamte Logik zum Verknüpfen externer Konten mit Discourse-Konten wird von Auth::ManagedAuthenticator verarbeitet. Dies setzt voraus, dass der Omniauth-Anbieter Daten im in deren Dokumentation definierten Format zurückgibt. Falls eine Manipulation dieser Daten erforderlich ist, können Authenticators die after_authenticate-Methode überschreiben und den auth_token entsprechend manipulieren. Zum Beispiel entfernt der Kern-Twitter-Authenticator alle extra-Informationen aus dem Token:

Die Daten werden in der Datenbanktabelle user_associated_accounts gespeichert. provider_uid, info, credentials und extra werden direkt aus den von Omniauth zurückgegebenen Daten übernommen.

Sobald eine Authenticator-Klasse definiert wurde, muss sie registriert werden. Dies muss früh im Lebenszyklus der Anwendung erfolgen und kann nicht innerhalb der after_initialize-Methode eines Plugins stattfinden. Die minimale Registrierung kann einfach einen Verweis auf den Authenticator enthalten. In einem Plugin kann die Registrierung mit der auth_provider-Funktion erfolgen. Zum Beispiel:

auth_provider authenticator: OpenIDConnectAuthenticator.new()

Im Kerncode findet die Registrierung in discourse.rb statt. Eine vollständige Liste der möglichen AuthProvider-Optionen findet sich hier. Textinhalte können mit diesen Optionen definiert werden, es ist jedoch besser, lokalisierte Zeichenketten in client.en.yml gemäß den Standard-Schlüsseln bereitzustellen. Zum Beispiel:

Zusätzliche Hinweise zu ManagedAuthenticator von @fantasticfears

ManagedAuthenticator im Detail

Möglicherweise müssen Sie an etwas Besonderem für die Authentifizierung arbeiten. Und Sie möchten mehr über ManagedAuthenticator erfahren. Grundsätzlich verfügt er über mehrere Operationen, Optionen und steuert, wie die Daten verwendet werden.

Discourse verwaltet Benutzerinformationen mit zwei Controllern. Users::OmniauthCallbacksController verwaltet die Nutzlast, sobald die OAuth2-Authentifizierung abgeschlossen ist. after_authenticate wird hier aufgerufen. can_connect_existing_user? wird ebenfalls hier verwendet.
Es gibt einige private Methoden, die Sie lesen können, um zu verstehen, wie die verschiedenen Datenfelder funktionieren.

if authenticator.can_connect_existing_user? && current_user
  @auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
else
  @auth_result = authenticator.after_authenticate(auth)
end

UsersController hat revoke_account, das can_revoke? und revoke verwendet. Damit die revoke-Methode jedoch remote funktioniert, müssen Sie Ihre eigene Implementierung erstellen.

UserAuthenticator ist eine Service-Klasse, die bei der Authentifizierung (Verifizierung der E-Mail-Bestätigung oder des OAuth2-Pfads) von Benutzern hilft. after_create_account wird hier aufgerufen.

Die Kernlogik bleibt bei after_authenticate mit der Auth::Result-Datendatensklasse. Wir folgen hier der Datenstruktur. extra_data wird an after_create_account übergeben, um zugehörige Datensätze zu erstellen.

result.extra_data = {
  provider: auth_token[:provider],
  uid: auth_token[:uid],
  info: auth_token[:info],
  extra: auth_token[:extra],
  credentials: auth_token[:credentials]
}

Es wird versucht, mit einem bestehenden Konto zu übereinstimmen und sich damit zu verbinden.

Sie fragen sich vielleicht, warum die automatische Kontoerstellung möglich ist, es aber kein User.create gibt. Dies geschieht in UsersController#create.

authentication = UserAuthenticator.new(user, session)

Der Benutzer ist eine neue Instanz, die durch Sitzungsdaten befüllt wird, die vom Authentifizierungsanbieter vorbereitet wurden. Vertrau mir, es ist einfach Magie.


Migration auf das neue System

Um einen reibungslosen Wechsel auf das neue System zu ermöglichen, sollten die Daten vom alten Speicherort migriert werden. Für Kern-Authentifizierungsanbieter können dies dedizierte Tabellen sein. Für Plugins können dies plugin_store_rows oder oauth2_user_infos sein. Die minimalen Daten, die in einer user_associated_accounts-Zeile erforderlich sind, sind provider_name, provider_uid und user_id. Für ein Beispiel einer Migration siehe:

Sobald das ManagedAuthenticator-System mit v2.2.0 auf dem stabilen Zweig veröffentlicht wurde, werden wir mit der Migration offizieller Authentifizierungs-Plugins beginnen. Zu diesem Zeitpunkt wird hier ein Beispiel für eine plugin_store_row-Migration hinzugefügt.


Dieses Dokument wird versioniert verwaltet – schlagen Sie Änderungen auf github vor.

23 „Gefällt mir“

@david Die hier geleistete Arbeit ist super cool. Ich schätze sie sehr. Ich hatte auch die Gelegenheit, mit GitHub - discourse/discourse-development-auth: A discourse plugin which adds a fake authentication provider. For development purposes only. zu spielen, was sehr praktisch ist.

Nur eine Einschränkung: Die Funktion funktioniert nicht gut (öffnet nicht das Anmelde-Popup) mit dem Ember CLI lokal. Ich habe mir den Kopf zerbrochen, als ich ein Plugin zum Hinzufügen eines Authentifizierungsanbieters schrieb, und plötzlich fiel mir ein, NO_EMBER_CLI=1 zu verwenden, und alles funktionierte.

7 „Gefällt mir“

Ich würde gerne erfahren, ob die Implementierung eines Authentifikators der richtige Weg ist, um

Verstehe ich richtig, dass alle registrierten Authentifikatoren früh in der App aufgerufen werden, sodass ich dort testen könnte, ob ein Benutzername und ein Hinweis zur E-Mail-Authentifizierung in der URL enthalten sind und als Antwort ein Formular “Senden Sie mir einen Login-Link” rendern könnte?