wp-m365-login/README.md
Friederich Loheide b0b319e73a Fix unreadable README badges
Drop the CI badge pointing at a non-existent GitHub repo and darken the
PHP and Plugin Check badge colours for sufficient contrast with white text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 19:57:32 +00:00

23 KiB
Raw Permalink Blame History

M365 Login

Anmeldung an WordPress mit dem Microsoft 365 / Entra ID-Konto sicher, schlank, gestaltbar.

WordPress PHP License Plugin Check

Login-Seite mit Microsoft-Button, Farb-Presets und Sicherheitsmerkmalen

Inhalt


Auf einen Blick

🔑 Login per Microsoft Ein Klick auf der Anmeldeseite, Anmeldung bei Microsoft, zurück in WordPress fertig.
📧 Zuordnung über die E-Mail-Adresse Es werden keine Benutzer angelegt. Nur wer schon ein WordPress-Konto mit derselben E-Mail hat, kommt rein.
🎨 Gestaltbarer Button Text, Icon (Microsoft-Logo oder eigenes Bild), Farben, Hover-Farbe, Rahmen, Eckenradius, Position mit Live-Vorschau und Presets.
👥 Entra-Gruppen Optional nur Mitglieder ausgewählter Gruppen zulassen. Gruppen werden direkt im Backend gesucht und ausgewählt.
🚪 Nur-Button-Modus Passwortfelder ausblenden und Passwort-Logins sperren mit geheimem Fallback-Link als Notausgang.
🔏 Secret oder Zertifikat Wahlweise Client Secret oder zertifikatsbasierte Authentifizierung (RFC 7523). Zertifikat mit einem Klick im Backend erzeugen, nur der öffentliche Teil geht zu Microsoft.
🛡️ Sicher by default OpenID Connect + PKCE, Signaturprüfung, Tenant-Pinning, Konto-Bindung, verschlüsseltes Secret, Security-Audit.
🌍 Übersetzbar Englische Basis, deutsche Übersetzung (du & Sie) enthalten.
📦 WordPress.org-ready readme.txt, Lizenz, Uninstall, Plugin Check in CI, Build-Script.

Screenshots

Vorschauen, gerendert aus dem echten Plugin-Markup und -CSS in einer nachgebauten WordPress-Oberfläche.

Login-Seite Nur-Button-Modus
Login-Seite mit Microsoft-Button Login-Seite im Nur-Button-Modus
M365 Login Verbindung M365 Login Button
Tab Verbindung Tab Button mit Live-Vorschau
M365 Login Sicherheit (Gruppen-Auswahl, Nur-Button-Modus, Fallback-Link)

Tab Sicherheit


So funktioniert es

sequenceDiagram
    autonumber
    participant B as Browser
    participant WP as WordPress<br/>(M365 Login)
    participant MS as Microsoft Entra ID
    participant G as Microsoft Graph<br/>(optional)

    B->>WP: Klick auf „Login mit Microsoft“
    WP->>WP: state, nonce, PKCE-Verifier erzeugen<br/>State-Cookie setzen (HttpOnly)
    WP-->>B: Redirect zu Microsoft (code_challenge, state, nonce)
    B->>MS: Anmeldung beim Microsoft-Konto
    MS-->>B: Redirect zurück mit code + state
    B->>WP: /m365-login/callback?code=…&state=…
    WP->>WP: State einmalig einlösen, Cookie prüfen
    WP->>MS: Code + code_verifier + Client Secret (Server-zu-Server)
    MS-->>WP: ID-Token
    WP->>MS: Signaturschlüssel (JWKS, gecacht)
    WP->>WP: Signatur, Issuer, Audience, Tenant, exp, Nonce prüfen
    opt Gruppen-Beschränkung aktiv
        WP->>G: checkMemberGroups(oid, erlaubte Gruppen)
        G-->>WP: Treffer / kein Treffer
    end
    WP->>WP: Benutzer per E-Mail suchen, Objekt-ID abgleichen
    WP-->>B: WordPress-Session, Redirect ins Dashboard

Tokens laufen ausschließlich zwischen deinem Server und Microsoft. Der Browser sieht nur einen Autorisierungscode, der ohne den serverseitigen PKCE-Verifier und das Client Secret wertlos ist.


Installation

Variante A manuell (empfohlen, solange das Plugin nicht im Verzeichnis ist)

git clone https://github.com/friloo/wp-m365-login.git
cd wp-m365-login
bash bin/build-zip.sh          # erzeugt build/m365-login.zip

Dann in WordPress unter Plugins → Installieren → Plugin hochladen das ZIP hochladen und aktivieren. Alternativ den Repo-Inhalt als Ordner m365-login nach wp-content/plugins/ kopieren.

Variante B WordPress.org (nach der Freigabe): Plugins → Installieren → „M365 Login“.

Voraussetzungen: WordPress ≥ 6.0, PHP ≥ 7.4 mit OpenSSL-Erweiterung, HTTPS auf der Website (Microsoft akzeptiert http:// nur für localhost).


Einrichtung in Microsoft Entra ID

Schritt für Schritt (ca. 5 Minuten)
  1. Redirect-URI kopieren. In WordPress den Menüpunkt M365 Login öffnen; die URI steht in der Seitenleiste (https://deine-seite.tld/m365-login/callback, bei einfachen Permalinks https://deine-seite.tld/?m365-login=callback).
  2. App registrieren. Microsoft Entra Admin CenterApp-Registrierungen → Neue Registrierung
    • Name: z. B. „WordPress Login“
    • Unterstützte Kontotypen: Nur Konten in diesem Organisationsverzeichnis (Single Tenant)
    • Umleitungs-URI: Plattform Web, URI aus Schritt 1
  3. IDs übernehmen. Auf der Übersichtsseite Anwendungs-ID (Client) und Verzeichnis-ID (Mandant) kopieren → in WordPress eintragen.
  4. Authentifizierung wählen.
    • Zertifikat (empfohlen): In WordPress Zertifikat erzeugen.cer herunterladen → in Entra ID Zertifikate & Geheimnisse → Zertifikate → Zertifikat hochladen. Thumbprint vergleichen.
    • Client Secret: Zertifikate & Geheimnisse → Neuer geheimer Clientschlüssel → den Wert (nicht die Geheimnis-ID) in WordPress eintragen. Ablaufdatum notieren.
  5. E-Mail-Claim aktivieren (empfohlen). Tokenkonfiguration → Optionalen Anspruch hinzufügen → ID → email.
  6. Speichern und mit Tenant testen prüfen, ob Microsoft erreichbar ist.
Zusätzlich für die Gruppen-Beschränkung

Damit das Backend Gruppen suchen und beim Login die Mitgliedschaft prüfen kann, braucht die App-Registrierung Anwendungsberechtigungen (nicht delegiert) für Microsoft Graph, jeweils mit Administratorzustimmung:

Berechtigung Wofür
GroupMember.Read.All Gruppen im Backend suchen
User.Read.All Mitgliedschaft beim Login prüfen (checkMemberGroups, inkl. verschachtelter Gruppen)

Directory.Read.All deckt beides ab, ist aber weiter gefasst.

Ohne Graph-Berechtigungen geht es auch: Unter Tokenkonfiguration → Gruppenanspruch hinzufügen den groups-Claim für ID-Tokens aktivieren (am besten Der Anwendung zugewiesene Gruppen oder Sicherheitsgruppen). Dann prüft das Plugin die Mitgliedschaft direkt im Token. Gruppen-IDs lassen sich im Backend auch von Hand einfügen. Bei mehr als 200 Gruppen pro Benutzer liefert Microsoft keinen groups-Claim mehr („Overage“); dann greift das Plugin automatisch auf Graph zurück.

Wichtig: Jeder Benutzer, der sich per Microsoft anmelden soll, braucht in WordPress dieselbe E-Mail-Adresse wie in Microsoft 365.


Einstellungen im Backend

Menüpunkt M365 Login drei Tabs (auch als Untermenüs erreichbar), ein Formular, ein Speichern-Button.

Verbindung

Feld Beschreibung
Verzeichnis-ID (Tenant) GUID des Tenants (empfohlen, aktiviert Tenant-Pinning) oder organizations / common / consumers. Im Multi-Tenant-Modus wird nur der UPN zur Zuordnung verwendet (siehe Audit H-1).
Anwendungs-ID (Client) GUID der App-Registrierung.
Authentifizierung Client Secret oder Zertifikat (empfohlen). Für beide Wege gibt es im Backend eine Schritt-für-Schritt-Anleitung.
Client Secret Wird verschlüsselt gespeichert und nie wieder angezeigt. Leer lassen = behalten.
Zertifikat Zertifikat erzeugen legt ein 3072-Bit-RSA-Schlüsselpaar mit selbstsigniertem Zertifikat (2 Jahre) an. Der private Schlüssel bleibt verschlüsselt auf dem Server; die .cer-Datei wird in Entra ID unter Zertifikate & Geheimnisse → Zertifikate hochgeladen. Alternativ eigenes PEM-Paar einfügen.
Kontoauswahl select_account (Standard), none (bestehende Microsoft-Sitzung nutzen) oder login (immer Anmeldedaten verlangen).
Tenant testen Lädt die OpenID-Konfiguration des Tenants prüft ID und ausgehende Verbindung.

Button

Option Beschreibung
Button-Text Standard „Sign in with Microsoft“ / „Login mit Microsoft“ (max. 80 Zeichen).
Trennlinien-Text Standard „or“ / „oder“; leer = keine Trennlinie.
Icon Microsoft-Logo (eingebettet) oder eigenes Bild aus der Mediathek (PNG, SVG, JPG, WebP). Ein-/ausblendbar.
Farben Hintergrund, Hintergrund (Hover), Text, Rahmen mit Farbwähler.
Eckenradius 050 px.
Position Unter dem Login-Formular (Standard) oder darüber.
Presets Microsoft dunkel, Microsoft hell, Azure-Blau, WordPress-Blau.

Alles wird live in der Vorschau angezeigt, bevor du speicherst.

Sicherheit

Option Standard Beschreibung
Konto an Microsoft-Objekt-ID binden an Beim ersten Login wird die oid gespeichert; danach muss sie übereinstimmen. Schützt vor Übernahme, wenn eine E-Mail-Adresse in Microsoft neu vergeben wird.
UPN-Fallback an Fehlt der email-Claim, wird der User Principal Name verwendet, sofern er eine gültige E-Mail-Adresse ist.
Angemeldet bleiben aus 14-Tage-Session statt Browser-Session.
Erlaubte E-Mail-Domains leer Kommagetrennte Liste, z. B. contoso.com, contoso.de.

Gruppen-Beschränkung

Im Tab SicherheitErlaubte Entra-Gruppen:

  1. Gruppenname eintippen (oder Objekt-ID einfügen) → Suchen.
  2. Treffer mit Hinzufügen übernehmen sie erscheinen als Chips mit Name und ID.
  3. Speichern. Ab jetzt darf sich nur anmelden, wer in mindestens einer dieser Gruppen ist (verschachtelte Mitgliedschaften zählen).

Prüfreihenfolge beim Login:

  1. Enthält das ID-Token einen groups-Claim → Abgleich direkt im Token.
  2. Sonst (oder bei Overage) → Microsoft Graph checkMemberGroups.
  3. Schlägt beides fehl → Anmeldung abgelehnt (fail closed), Meldung „Gruppenmitgliedschaft konnte nicht geprüft werden“.

Leere Liste = keine Beschränkung.

Nur-Button-Modus & Fallback

Im Tab SicherheitButton-only mode:

  • Blendet Benutzername/Passwort-Felder und den „Passwort vergessen?“-Link aus.
  • Sperrt Passwort-Logins serverseitig, nicht nur per CSS auf wp-login.php und in jedem eigenen Login-Formular (authenticate-Filter).
  • Application Passwords, REST API, XML-RPC, WP-CLI und Cron sind nicht betroffen. Einzelne Ausnahmen per Filter m365_login_block_password_login.
  • Wird erst aktiv, wenn die Verbindung vollständig konfiguriert ist.

Fallback (Notausgang): Beim Speichern erzeugt das Plugin einen geheimen Schlüssel und zeigt den Fallback-Link an:

https://deine-seite.tld/wp-login.php?m365_fallback=AbC…xYz

Wer den Link öffnet, sieht für 30 Minuten in diesem Browser wieder das normale Formular und kann sich mit Passwort anmelden. Der Schlüssel landet nicht im Cookie (nur ein HMAC davon), Fehlversuche werden pro IP gedrosselt (10 Versuche / 15 Minuten), und über die Checkbox Neuen Schlüssel beim Speichern erzeugen lässt er sich jederzeit rotieren.

Notschalter ohne Backend-Zugang: In wp-config.php

define( 'M365_LOGIN_DISABLE_BUTTON_ONLY', true );

schaltet den Modus komplett ab. Alternativ das Plugin-Verzeichnis per FTP umbenennen.

⚠️ Vor dem Aktivieren sicherstellen, dass dein eigenes Admin-Konto per Microsoft funktioniert, und den Fallback-Link sicher ablegen.

Eigene Login-Seite

Das Plugin funktioniert auch, wenn die Anmeldung nicht über wp-login.php läuft.

Baustein Was passiert
wp_login_form() (Themes, viele Login-Plugins) Der Button wird automatisch unter dem Formular eingefügt, inklusive Fehlermeldungen darüber. Im Nur-Button-Modus werden die Passwortfelder ausgeblendet. Abschaltbar im Tab Button.
Shortcode [m365_login_button redirect="/dashboard/" divider="yes" messages="yes"] für Block-Editor und Page Builder (Elementor, Divi, …).
Template-Funktion m365_login_button( array( 'redirect' => '/dashboard/' ) ); in Theme-Dateien, m365_login_messages(); für die Meldungen an anderer Stelle.
URL der Login-Seite (Tab ButtonEigene Login-Seite) Fehlermeldungen nach einem gescheiterten Microsoft-Login, der Fallback-Link und die Weiterleitung nach dem Abmelden zeigen auf diese Seite statt auf wp-login.php.
Nur-Button-Modus Greift serverseitig für alle Passwort-Logins, also auch für Formulare von Page Buildern, die das Plugin nicht ausblenden kann.

Plugins, die wp-login.php umbenennen (z. B. WPS Hide Login), sind kompatibel, weil das Plugin durchgehend wp_login_url() verwendet.


Sicherheitskonzept

Bedrohung Gegenmaßnahme
Abfangen von Tokens im Browser Authorization Code Flow mit PKCE (S256); ID-Token wird serverseitig geholt, response_mode=query ohne Token.
CSRF / Login-CSRF state ist zufällig (256 Bit), einmalig verwendbar, 10 Min. gültig und per HttpOnly-/SameSite-Cookie an den startenden Browser gebunden.
Token-Replay nonce wird im ID-Token geprüft und mit dem State-Datensatz verworfen.
Gefälschte Tokens Signaturprüfung gegen Microsofts JWKS (RS256 only; alg=none/HMAC werden abgelehnt), Schlüssel-Rollover wird automatisch nachgeladen. iss, aud, tid, exp, nbf, iat werden geprüft.
Fremde Tenants Bei konfigurierter Tenant-GUID Tenant-Pinning; sonst Issuer-Konsistenz mit tid.
Kontoübernahme per E-Mail-Recycling Bindung an die Objekt-ID (oid) beim ersten Login.
Unbefugte Konten Kein Provisioning, optionale Domain-Allowlist, optionale Gruppen-Beschränkung (fail closed).
Secret-Diebstahl aus der Datenbank AES-256-GCM, Schlüssel per HKDF aus AUTH_KEY/SECURE_AUTH_KEY; ohne wp-config.php ist der Datensatz wertlos. Gilt für Client Secret und privaten Zertifikatsschlüssel.
Secret-Abfluss im Transport Zertifikatsmodus: es wird nie ein Geheimnis übertragen, nur eine 5 Minuten gültige, signierte Client Assertion (RFC 7523).
Kontoübernahme im Multi-Tenant-Modus email-Claim fremder Tenants wird ignoriert (nur UPN mit verifizierter Domain oder xms_edov).
Flooding der State-Tabelle Max. 30 Login-Starts pro IP und 10 Minuten; Proxy-Header per M365_LOGIN_CLIENT_IP_HEADER.
Offene Redirects redirect_to läuft durch wp_validate_redirect, alle Redirects über wp_safe_redirect.
Fehler-Reflektion Fehlermeldungen sind Codes → feste, übersetzte Texte; Details nur ins Log (WP_DEBUG_LOG).
Rate Limiting Fallback-Key 10 Fehlversuche pro IP / 15 Min.

Die Klassen für JWT-Prüfung, Verschlüsselung, Zertifikate und die Login-Sperre haben isolierte Tests (manipulierte Signaturen, abgelaufene Tokens, falsche Audience/Tenant/Issuer, alg=none, fremde Schlüssel, gefälschte Fallback-Cookies, schwache RSA-Schlüssel). Der vollständige Bericht mit Bedrohungsmodell, Befunden und Betriebsempfehlungen: docs/security-audit.md.


Shortcode & Hooks

Shortcode für eigene Login-Seiten:

[m365_login_button redirect="/mein-konto/"]

Filter & Actions

// Button z. B. nur im Intranet zeigen
add_filter( 'm365_login_show_button', function ( $show ) {
    return $show && 'intranet.example.com' === $_SERVER['HTTP_HOST'];
} );

// domain_hint mitschicken, damit Microsoft direkt die Firmenanmeldung zeigt
add_filter( 'm365_login_authorize_params', function ( $params ) {
    $params['domain_hint'] = 'contoso.com';
    return $params;
} );

// E-Mail vor dem Lookup umschreiben (z. B. Alias-Domain)
add_filter( 'm365_login_match_email', function ( $email, $claims ) {
    return str_replace( '@alt.contoso.com', '@contoso.com', $email );
}, 10, 2 );

// Eigene Zusatzprüfung nach allen Plugin-Checks
add_filter( 'm365_login_allow_user', function ( $allowed, WP_User $user, array $claims ) {
    return $allowed && ! in_array( 'subscriber', $user->roles, true );
}, 10, 3 );

// Nach erfolgreichem Login, z. B. Anzeigenamen synchronisieren
add_action( 'm365_login_success', function ( WP_User $user, array $claims ) {
    if ( ! empty( $claims['name'] ) ) {
        wp_update_user( array( 'ID' => $user->ID, 'display_name' => $claims['name'] ) );
    }
}, 10, 2 );

// Ein vertrauenswürdiges Plugin darf trotz Nur-Button-Modus per Passwort anmelden
add_filter( 'm365_login_block_password_login', function ( $block, WP_User $user ) {
    return doing_action( 'my_membership_plugin_login' ) ? false : $block;
}, 10, 2 );

// Redirect-URI anpassen (z. B. hinter einem Reverse Proxy)
add_filter( 'm365_login_redirect_uri', fn( $uri ) => 'https://www.example.com/m365-login/callback' );

Fehlerbehebung

Meldung auf der Login-Seite Ursache & Lösung
Microsoft login is not configured yet. Tenant-ID, Client-ID oder Secret fehlt.
The login request expired or was invalid. State abgelaufen (> 10 Min.), Cookie blockiert oder Seite doppelt geladen. Erneut versuchen; Cookies für die Domain erlauben.
Could not complete the sign-in with Microsoft. Token-Tausch fehlgeschlagen meist falsches/abgelaufenes Client Secret oder Redirect-URI stimmt nicht exakt mit Entra überein. Details im Log.
The Microsoft sign-in could not be verified. ID-Token abgelehnt (Tenant, Audience, Signatur). Tenant-ID prüfen; Serverzeit prüfen (NTP).
Your Microsoft account did not provide an e-mail address. email-Claim fehlt und UPN-Fallback ist aus oder UPN ist keine E-Mail. Claim in der Tokenkonfiguration hinzufügen.
No WordPress account exists for your Microsoft e-mail address. E-Mail in WordPress stimmt nicht mit Microsoft überein.
This WordPress account is linked to a different Microsoft account. Objekt-ID weicht ab. Wenn gewollt (neues Microsoft-Konto): User-Meta _m365_login_oid beim Benutzer löschen.
… not a member of a group that is allowed … Benutzer ist in keiner der ausgewählten Gruppen.
Your group membership could not be verified. Graph nicht erreichbar oder Berechtigung fehlt (User.Read.All) oder groups-Claim aktivieren.

Logging: Mit WP_DEBUG und WP_DEBUG_LOG schreibt das Plugin Fehlerdetails mit Präfix [M365 Login] nach wp-content/debug.log. Es werden nie Tokens oder Secrets geloggt.


Entwicklung

m365-login.php                  Plugin-Header & Bootstrap
includes/
  class-m365-login.php          Verdrahtung der Komponenten
  class-m365-login-settings.php Defaults, Sanitizing, Redirect-URI, Fallback-Key
  class-m365-login-crypto.php   AES-256-GCM für das Client Secret
  class-m365-login-jwt.php      RS256-Verifikation, JWKS → PEM
  class-m365-login-auth.php     OAuth-Flow, Callback, Benutzerzuordnung, Nur-Button-Modus
  class-m365-login-graph.php    Client-Credentials-Token, Gruppensuche, checkMemberGroups
  class-m365-login-button.php   Ausgabe auf wp-login.php, Shortcode
  class-m365-login-admin.php    Einstellungsseite, AJAX
assets/                         CSS/JS für Login-Seite und Backend (unminifiziert)
languages/                      .pot, de_DE, de_DE_formal
bin/                            build-zip.sh, make-pot.py, compile-mo.py
docs/                           Einreichungs-Checkliste
composer install              # PHPCS + WordPress Coding Standards + PHPCompatibility
composer lint                 # php -l für alle Dateien
composer phpcs                # Coding-Standards-Prüfung (phpcs.xml.dist)
python3 bin/make-pot.py       # Strings extrahieren (oder: wp i18n make-pot . languages/m365-login.pot)
python3 bin/compile-mo.py     # .po → .mo
bash bin/build-zip.sh         # build/m365-login.zip

Die CI (.github/workflows/ci.yml) prüft Syntax unter PHP 7.48.4, führt PHPCS aus und lässt den offiziellen WordPress Plugin Check über das Build-Verzeichnis laufen.


Einreichung bei WordPress.org

Das Plugin bringt alles mit, was das Review-Team verlangt: readme.txt mit External services-Abschnitt, GPL-Lizenz, uninstall.php, eindeutige Präfixe, keine externen Assets, Übersetzungen, Verzeichnis-Icon. Die komplette Checkliste (inkl. Slug-/Marken-Hinweisen und SVN-Schritten nach der Freigabe) steht in docs/wordpress-org-einreichung.md.


FAQ

Kann ich Benutzer automatisch anlegen lassen? Nein, bewusst nicht. Der Admin entscheidet, wer ein Konto hat. Wer Auto-Provisioning braucht, kann es über den Hook m365_login_allow_user nicht nachrüsten das wäre ein anderes Sicherheitsmodell.
Funktioniert es mit privaten Microsoft-Konten (outlook.com)? Ja, Tenant auf consumers oder common stellen. Microsoft erlaubt dann keine Query-Strings in Redirect-URIs, deshalb müssen sprechende Permalinks aktiv sein (Callback ohne ?).
Multisite? Ja. Einstellungen gelten pro Site; der Benutzer muss Mitglied der Site (oder Super-Admin) sein.
Was passiert beim Deinstallieren? Einstellungen, Caches (Transients) und die pro Benutzer gespeicherte Objekt-ID werden entfernt auch in Multisite.
Ich habe mich ausgesperrt. Fallback-Link öffnen. Kein Link zur Hand? define( 'M365_LOGIN_DISABLE_BUTTON_ONLY', true ); in die wp-config.php oder den Plugin-Ordner per FTP umbenennen.

Lizenz

GPL-2.0-or-later siehe LICENSE. „Microsoft“, „Microsoft 365“ und das Microsoft-Logo sind Marken der Microsoft Corporation; das Plugin ist ein unabhängiges Community-Projekt und steht in keiner Verbindung zu Microsoft.