Bisher war im WordPress-Core keine Funktion vorhanden, mit der sich externe Dienste einfach bei der Nutzung der REST-API einer WordPress-Website authentifizieren konnten. Mit WordPress 5.6 und der Integration des Application-Passwords-Plugins in den Core hat sich das geändert und es steht ein User-Flow zur Verfügung, um Dritt-Services bei einer WordPress-Website für spätere Anfragen zu autorisieren.
Ablauf der Autorisierung
Auf make.wordpress.org gibt es einen Guide zur Nutzung der Anwendungspasswörter-Funktion, an dem ich mich orientiert habe. Der Prozess kann in mehrere Schritte unterteilt werden, die aus Sicht des Dienstes ausgeführt werden müssen, der auf eine WordPress-Installation zugreifen möchte:
- Prüfung, ob die WordPress-Installation Anwendungspasswörter überhaupt unterstützt.
- Weiterleitung des Users zu dem Endpunkt der WordPress-Installation, der für das Anlegen eines neuen Anwendungspassworts vorgesehen ist. Um die Autorisierungsanfrage zu bearbeiten, muss der User sich natürlich auf der WordPress-Site anmelden.
- Prüfung und Speicherung der Daten.
- Nutzung der Daten.
Prüfung auf Unterstützung der Anwendungspasswörter-Funktion
Um sicherzustellen, dass die WordPress-Site, bei der sich der Dienst anmelden soll, überhaupt die Voraussetzungen dafür erfüllt, können wir eine Anfrage an den Root-REST-Endpunkt der Website schicken, der unter https://example.com/wp-json/
erreichbar ist. Die erste Voraussetzung ist, dass wir hier Informationen zurückbekommen und dass die REST-API nicht von einem Plugin komplett deaktiviert wurde.
Wenn wir eine JSON-Antwort bekommen, ist über den Pfad authentication.application-passwords.endpoints.authorization
die URL angegeben, an die wir den User weiterleiten müssen, um ein Anwendungspasswort zu erstellen – in meinem Fall wäre das https://florianbrinkmann.com/wp-admin/authorize-application.php
. Wenn das JSON-Objekt die Information nicht enthält, ist entweder die Funktion deaktiviert oder es liegt eine WordPress-Version älter als 5.6 vor. In letzterem Fall kann die Funktion über das Application-Passwords-Plugin nachgerüstet werden. Auch bei einem Aufruf über http
statt https
würde die Information nicht ausgegeben werden.
User an den Autorisierungs-Endpunkt weiterleiten
Wenn wir eine URL für den Authorisierungs-Endpunkt aus der ersten Abfrage erhalten haben, können wir den User an die URL weiterleiten, nachdem wir sie mit ein paar GET-Parametern ausgestattet haben:
app_name
ist der Name des Dienstes, der in der Liste der Anwendungspasswörter eines WordPress-Users angezeigt wird. Dieser Wert kann vor dem Erstellen des Anwendungspassworts vom User geändert werden.app_id
(optional) ist eine UUID, die beispielsweise von dem Website-Admin dazu genutzt werden kann, nach einem Datenleck bei einem Dritt-Service alle Anwendungspasswörter mit der UUID dieses Dienstes zu löschen. Damit das funktionieren kann, müsste der externe Dienst natürlich bei jeder Autorisierung dieselbeapp_id
nutzen.success_url
(optional) ist die URL, an die der User nach der erfolgreichen Erstellung eines Anwendungspassworts weitergeleitet wird, wobei an die URL die drei GET-Parametersite_url
,user_login
undpassword
angehängt werden. Falls nicht angegeben, wird das erstellte Passwort dem User angezeigt, sodass er es manuell beim Dritt-Service eintragen kann.reject_url
(optional) ist die URL, an die der User weitergeleitet wird, wenn er die Erstellung des Anwendungspassworts ablehnt. Ist die URL nicht angegeben, wird an diesuccess_url
mit dem Parameter?success=false
weitergeleitet. Liegt diese ebenfalls nicht vor, wird zum WordPress-Dashboard umgeleitet.
Sowohl die success_url
als auch reject_url
müssen https://
oder ein App-spezifisches Protokoll nutzen, http://
ist nicht erlaubt.
Da der User-Flow ohne die success_url
etwas halbgar ist, haben wir im einfachsten Fall eine URL nach dem folgenden Schema, an die wir die User weiterleiten:
Code-Sprache: Bash (bash)https://example.com/wp-admin/authorize-application.php?app_name=Beispielname&success_url=https://app.example.com/success
Im Erfolgsfall kommt der User über die nachstehende URL zu unserem Dienst zurück:
Code-Sprache: Bash (bash)https://app.example.com/success?site_url=https://example.com&user_login=username&password=abcd EFGH 1234 ijkl MNOP 6789
Der Wert von user_login
entspricht dem WordPress-Loginnamen des Users, das Passwort ist 24 Zeichen lang und enthält keine Sonderzeichen. Zur besseren Lesbarkeit ist das Passwort in Vierergruppen aufgeteilt – bei den Anfragen an die REST-API kann es sowohl mit als auch ohne Leerzeichen genutzt werden.
Prüfung und Speicherung der Anwendungspasswörter
Um sicherzugehen, dass mit den übertragenen Daten alles stimmt, habe ich bei dem Projekt, für das ich Anwendungspasswörter brauche, das folgende Vorgehen gewählt:
- Der User muss vor der Autorisierung natürlich seine URL eingeben, sonst kann ich schlecht prüfen, ob die Website Anwendungspasswörter unterstützt und ihn im Erfolgsfall zu seiner Website leiten.
- Die URL speichere ich als Website-Eintrag in der Datenbank.
- Der User wird nach der Autorisierung auf eine URL umgeleitet, aus der ich den entsprechenden Website-Eintrag ableiten kann. Auf die URL hat der User nur Zugriff, wenn er angemeldet ist und wenn ihm der Website-Eintrag gehört.
- Ich prüfe, ob die URL aus dem
site_url
-Parameter mit der URL in der Datenbank übereinstimmt. - Ich mache einen Test-Request an den REST-Endpunkt
/wp-json/wp/v2/users/me?context=edit
der WordPress-Installation mit den von der Autorisierung zurückgegebenen Anmeldedaten.
Wenn alles klappt und ich eine REST-Antwort mit den Daten des Accounts bekomme, kann ich innerhalb dieser Antwort gleich noch die Berechtigungen des Users prüfen, was in meinem Fall notwendig ist.
Nach diesen Prüfungen kann ich die Daten speichern. Hier kommt es jetzt zu einem kleinen Problem: um die Daten an die REST-API schicken zu können, müssen sie im Klartext vorliegen. Wir müssen also in der Lage sein, die Passwörter nach dem verschlüsselten Speichern wieder zu entschlüsseln, nicht so wie bei normalen Passwörtern, wo keine Entschlüsselung notwendig ist.
In dem Beitrag zur Implementierung der Funktion wird in dem Zusammenhang auf libsodium und Vault verwiesen. Wenn allerdings jemand unbefugt auf den Server des Dienstes kommt, hat er Zugriff auf alles, um die Daten zu entschlüsseln.
Nach Einlesen in libsodium und Vault bin ich aktuell auf dem Stand, dass Vault für meinen Anwendungszweck etwas zu viel ist, und ich mir eher mit libsodium etwas bauen werde. Mein aktueller Ansatz ist:
- ein eigener Server für die Verschlüsselung und Entschlüsselung der Anwendungspasswörter sowie eine Datenbank für die Anwendungspasswörter, auf die nur dieser Server zugreift. In der Datenbank werden keine URLs von Websites gespeichert, sodass nur mit dieser Datenbank die Anwendungspasswörter keinen Websites zugeordnet werden können.
- eine kleine API, mit der mein Service über den Server mit Zugriff auf die Anwendungspasswörter Anfragen an die REST-API schicken kann.
- Eventuell werde ich bei den Anfragen eine Info mitschicken, die für die Entschlüsselung der Anwendungspasswörter gebraucht wird, sodass jemand Zugriff auf beide Server bräuchte, um die Anwendungspasswörter entschlüsseln zu können.
So würden auf dem Server des eigentlichen Dienstes, der das User-Interface bereitstellt, die Anwendungspasswörter nur einmal nach der Autorisierung verarbeitet, und es würde von diesem Server aus keine Möglichkeit bestehen, Anwendungspasswörter auszulesen und zu entschlüsseln.
Vielleicht komme ich aber noch auf eine bessere Lösung während der Umsetzung, mal schauen.
Nutzung der Anwendungspasswörter
Um die Anwendungspasswörter bei Anfragen an die REST-API zu nutzen, kann die Basic-Auth-Methode genutzt werden. Eine Beispielanfrage aus dem Guide auf make.wordpress.org sieht so aus:
curl --user "USERNAME:PASSWORD" https://example.com/wp-json/wp/v2/users?context=edit
Code-Sprache: Bash (bash)
In Laravel könnte die Anfrage beispielhaft so aussehen:
<?php
$response = Http::withBasicAuth(
'USERNAME',
'PASSWORD'
)->get('https://example.com/wp-json/wp/v2/users?context=edit');
Code-Sprache: PHP (php)
Ausblick auf geplante Funktionen
Bisher ist es so, dass durch Anmeldung mit einem Anwendungspasswort dieselben Berechtigungen einhergehen, die der User hat, der das Anwendungspasswort eingerichtet hat. Welche User Anwendungspasswörter nutzen können, kann jetzt bereits mit dem Filter wp_is_application_passwords_available
gesteuert werden.
Es soll zukünftig aber beispielsweise auch möglich sein, Anwendungspasswörter nur auf die Bearbeitung von Inhalten einzuschränken, oder auf die Rechte bestimmter niedrigerer Rollen, sodass Redakteur:innen auch Anwendungspasswörter mit Autor:innen-Berechtigungen erstellen könnten.
Fazit
Für mich ist die Anwendungspasswörter-Funktion genau zur richtigen Zeit in den Core integriert worden. Ich hatte schon einen Umweg gebastelt, um REST-Anfragen zu authentifizieren, aber das war doch recht umständlich. Mit den Anwendungspasswörtern geht es deutlich leichter, abgesehen – in meinem Fall – von dem Problem der Speicherung.
Wie sieht es bei euch aus, habt ihr vielleicht bereits bestehende Projekte, die ihr jetzt auf die Nutzung der Anwendungspasswörter umstellen wollt, oder welche, die ihr nun umsetzt, wo die Funktion verfügbar ist?
Cool, danke für die Einführung. Muss ich auch mal dran machen. Die Passworter verschlüsselt in der Datenbank zu speicher ist für dich keine Option? z.B. mit github.com/spatie/crypto wenn du Laravel nutzt?
Danke für den Tipp. Ich habe es gerade mal überflogen. Auch da sieht es für mich so aus, dass wenn jemand Zugriff auf den Server hat, würde er an alle Infos kommen, um die Passwörter zu entschlüsseln, oder?
Jop aber wenn er nur Zugriff auf die Datenbank z.B. durch eine SQL-Injection hat bringt es ihn nicht weiter.
Okay, ja, das wäre bei den anderen Lösungen auch so. Ich weiß nicht, ob mir das reicht, vielleicht denke ich da aber auch zu viel 😀
Sind sicherlich keine falschen Gedanken aber irgendwie muss es auch noch praktikabel sein. Wünschenswert wäre auf jeden Fall die Rechte einschränken zu können.
Das stimmt, wobei ich bei meiner Anwendung ziemlich viele Rechte brauche …
Arbeite recht viel mit APIs und habe gefühlt schon alles gesehen. Neuste Kopf-gegen-Wand-Erfahrung ist eine API mit
oAuth 2.0 Auth aber das war dem Anbieter scheinbar nicht sicher genug und ich muss trotzdem noch mein Benutzernamen + Passwort mitschicken 🤬
Doppelt hält besser 😬🙈
Tolle Ausarbeitung. Sie hat mir deutlich mehr die Augen geöffnet als der Codex es konnte. Nicht, dass der Codex da undeutlich ist 😅, aber es hilft mir immer, die Theorie auch mal in der Praxis zu sehen.
Dennoch zur Sicherheit die Frage: Wenn die Authentifizierung erfolgreich war, kann ich ganz normal mit WordPress-eigenen Funktionen wie is_user_logged_in(), user_has_cap() u.ä. arbeiten, korrekt?
Freut mich, dass der Artikel hilfreich war 🙂
Ich bin in meiner Umsetzung zwar noch nicht ganz so weit, aber ich gehe davon aus, dass es genau so funktioniert, ja 🙂
Super Einführung.
Für ein paar Projekte ist die Funktion in Zukunft sehr interessant.
Am meisten reizt es mich meine App http://www.isypress.info anzupassen. Dazu fehlt mir aktuell nur die Zeit.
Freut mich, dass die Einführung hilfreich war!
Die isypress.info wirft bei mir eine Sicherheitswarnung (Firefox auf Windows 10), weil das Zertifikat wohl selbst signiert wurde.