Anwendungs­passwörter in WordPress 5.6

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:

  1. Prüfung, ob die WordPress-Installation Anwendungspasswörter überhaupt unterstützt.
  2. 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.
  3. Prüfung und Speicherung der Daten.
  4. Nutzung der Daten.

Prüfung auf Unterstützung der Anwendungs­passwö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 dieselbe app_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-Parameter site_url, user_login und password 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 die success_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:

https://example.com/wp-admin/authorize-application.php?app_name=Beispielname&success_url=https://app.example.com/success
Code-Sprache: Bash (bash)

Im Erfolgsfall kommt der User über die nachstehende URL zu unserem Dienst zurück:

https://app.example.com/success?site_url=https://example.com&user_login=username&password=abcd EFGH 1234 ijkl MNOP 6789
Code-Sprache: Bash (bash)

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 Anwendungs­passwö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?

20 Reaktionen zu »Anwendungs­passwörter in WordPress 5.6«

          1. 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 🤬

  1. 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?

    1. Freut mich, dass der Artikel hilfreich war 🙂

      »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?«

      Ich bin in meiner Umsetzung zwar noch nicht ganz so weit, aber ich gehe davon aus, dass es genau so funktioniert, ja 🙂

Reposts

Mentions

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.