No description
Find a file
Friederich Loheide cc88f145f2
Some checks are pending
CI / PHP lint (7.4) (pull_request) Waiting to run
CI / PHP lint (8.0) (pull_request) Waiting to run
CI / PHP lint (8.1) (pull_request) Waiting to run
CI / PHP lint (8.2) (pull_request) Waiting to run
CI / PHP lint (8.3) (pull_request) Waiting to run
CI / PHP lint (8.4) (pull_request) Waiting to run
CI / WordPress Coding Standards (pull_request) Waiting to run
CI / WordPress.org Plugin Check (pull_request) Waiting to run
Match accounts by mail address and user principal name
Sign-in and sync only used the first address (the mail attribute). A
WordPress account that carries the user principal name while the
Microsoft mail differs was not found at sign-in ("no WordPress account")
and the sync created a duplicate account for it.

Both now try the mail address and then the user principal name (UPN
only for members, only with the UPN fallback on, and only within the
e-mail domain allow-list). Privileged accounts keep the stricter rule;
a matching UPN is sufficient there as well.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 04:23:02 +00:00
.github Move the settings screen to its own top-level menu 2026-09-22 17:07:53 +00:00
.wordpress-org Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
assets Fix the findings of a full second security audit 2026-09-23 17:10:30 +00:00
bin Add Microsoft 365 user sync with roles, profile fields and deprovisioning 2026-09-23 16:34:30 +00:00
docs Let administrators link Microsoft accounts whose UPN differs from mail 2026-09-24 03:57:45 +00:00
includes Match accounts by mail address and user principal name 2026-09-24 04:23:02 +00:00
languages Let administrators link Microsoft accounts whose UPN differs from mail 2026-09-24 03:57:45 +00:00
.distignore Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
.editorconfig Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
.gitattributes Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
.gitignore Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
CHANGELOG.md Match accounts by mail address and user principal name 2026-09-24 04:23:02 +00:00
composer.json Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
LICENSE Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
m365-login.php Add Microsoft 365 user sync with roles, profile fields and deprovisioning 2026-09-23 16:34:30 +00:00
phpcs.xml.dist Add M365 Login plugin: Microsoft Entra ID sign-in for existing users 2026-09-22 14:21:10 +00:00
README.md Match accounts by mail address and user principal name 2026-09-24 04:23:02 +00:00
readme.txt Let administrators link Microsoft accounts whose UPN differs from mail 2026-09-24 03:57:45 +00:00
uninstall.php Let administrators link Microsoft accounts whose UPN differs from mail 2026-09-24 03:57:45 +00:00

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 Der Login legt keine Benutzer an. Nur wer schon ein WordPress-Konto mit derselben E-Mail hat, kommt rein.
🔄 Benutzer-Sync (optional) Importiert Microsoft-365-Benutzer als WordPress-Konten mit Standardrolle, zusätzlichen Rollen per Gruppen-Zuordnung, wählbaren Profilfeldern und Profilbild. In Microsoft 365 deaktivierte oder gelöschte Konten werden in WordPress deaktiviert oder gelöscht.
🎨 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 und/oder Mitglieder bestimmter Gruppen ausschließen. 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.

Zusätzlich für den Benutzer-Sync
Berechtigung (Anwendung, mit Administratorzustimmung) Wofür
User.Read.All Benutzer, Kontostatus, Profilfelder und Profilbilder lesen
GroupMember.Read.All Nur nötig, wenn Sync-Gruppen oder Rollen-Zuordnungen verwendet werden

Außerdem muss im Tab Verbindung die Tenant-GUID eingetragen sein (nicht organizations/common).

Wichtig: Jeder Benutzer, der sich per Microsoft anmelden soll, braucht in WordPress dieselbe E-Mail-Adresse wie in Microsoft 365 von Hand angelegt oder vom Benutzer-Sync importiert.


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 Neben dem email-Claim wird auch der User Principal Name gesucht wichtig, wenn WordPress-Konten den UPN statt der Mailadresse tragen. Der Abgleich läuft in der Reihenfolge: gebundene Objekt-ID → zugewiesener UPN → Mailadresse → UPN.
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.

Ausgeschlossene Entra-Gruppen (gleicher Tab, darunter): Mitglieder dieser Gruppen können sich nie per Microsoft anmelden auch wenn sie in einer erlaubten Gruppe sind (Ausschluss hat Vorrang, verschachtelte Mitgliedschaften zählen).

  1. Steht eine ausgeschlossene Gruppe im groups-Claim → sofort abgelehnt.
  2. Sonst wird immer Microsoft Graph gefragt (checkMemberGroups, Berechtigung User.Read.All), denn ein groups-Claim kann in der App-Registrierung gefiltert sein und beweist nicht, dass jemand kein Mitglied ist.
  3. Schlägt die Graph-Prüfung fehl → Anmeldung abgelehnt (fail closed).

Die Passwort-Anmeldung betrifft das nicht; wer auch die sperren will, kombiniert es mit dem Nur-Button-Modus.

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).
  • Unterschieden wird nach Zugangsdaten, nicht nach Anfrage-Typ: Das normale Passwort wird überall abgelehnt auch über XML-RPC und in Login-Handlern anderer Plugins, die in xmlrpc.php oder einer REST-Anfrage laufen. Application Passwords (REST, XML-RPC) und WP-CLI funktionieren weiter; API-Anfragen bekommen aber nie ein Login-Cookie.
  • Falsches und richtiges Passwort erhalten dieselbe Meldung (kein Passwort-Orakel).
  • Einzelne Ausnahmen per Filter m365_login_block_password_login.
  • Bleibt aktiv, auch wenn die Verbindung zu Microsoft kaputtgeht (abgelaufenes Zertifikat, rotierte Salts) dann gibt es einen roten Hinweis im Backend, und nur der Fallback-Link oder die Konstante helfen. Passwort-Logins schalten sich nie stillschweigend wieder ein.

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.

Benutzer-Sync

Privilegierte Konten (Administratoren, Redakteure mit unfiltered_html, alle mit Rechten an Benutzern, Plugins oder Themes auf irgendeiner Site des Netzwerks sowie deaktivierte Konten, die solche Rollen zurückbekämen) werden nie über das frei setzbare mail-Attribut verknüpft. Siehe Administrator-Konten verknüpfen. Ihre E-Mail-Adresse ändert der Sync nie automatisch. Bei allen anderen Konten informiert WordPress die alte Adresse über eine Änderung.

Tab Benutzer-Sync. Legt WordPress-Konten für Microsoft-365-Benutzer an und hält sie aktuell manuell per Knopfdruck, automatisch per WP-Cron (stündlich, zweimal täglich, täglich) oder per WP-CLI.

Welche Benutzer? Ohne Auswahl alle Mitglieder des Tenants; optional nur Mitglieder bestimmter Gruppen (verschachtelte Mitgliedschaften zählen). Gäste (B2B) nur auf Wunsch. Die Domain-Allowlist aus dem Tab Sicherheit gilt auch hier.

Was passiert pro Benutzer?

Situation Ergebnis
Kein WordPress-Konto vorhanden Konto wird angelegt: Benutzername aus der E-Mail, Zufallspasswort, keine E-Mail an den Benutzer, Standardrolle + zugeordnete Rollen. Die Anmeldung läuft über den Microsoft-Button.
Konto mit derselben E-Mail (Mailadresse oder UPN) existiert schon Wird mit der Microsoft-Objekt-ID verknüpft, Profilfelder werden aktualisiert. Rollen bleiben unangetastet, außer „Auch die Rollen von Konten verwalten, die schon vor dem Sync existierten“ ist aktiv.
Bereits verknüpft E-Mail-Adresse, Profilfelder, Profilbild und (bei importierten Konten) Rollen werden aktualisiert.
In Microsoft 365 deaktiviert Wahlweise nichts tun, WordPress-Konto deaktivieren oder löschen.
In Microsoft 365 gelöscht Wahlweise nichts tun, deaktivieren oder löschen.
Nicht mehr in den Sync-Gruppen Wahlweise nichts tun, deaktivieren oder löschen.
Wieder aktiv in Microsoft 365 Vom Sync deaktivierte Konten werden automatisch reaktiviert (von Hand deaktivierte nicht).

Rollen. Jeder importierte Benutzer bekommt die Standardrolle. Darunter lassen sich Microsoft-365-Gruppen per Suche auswählen und je einer WordPress-Rolle zuordnen (z. B. „Redaktion“ → Redakteur). Zwei Modi:

  • Zusätzlich zur Standardrolle der Benutzer hat danach mehrere Rollen.
  • Anstelle der Standardrolle die erste passende Gruppe der Liste gewinnt (Reihenfolge per ↑).

Verlässt jemand eine Gruppe, wird die Rolle beim nächsten Lauf entfernt. Die Rollen importierter Konten verwaltet der Sync vollständig manuelle Änderungen werden überschrieben.

Profilfelder. Frei wählbar: Anzeigename, Vor- und Nachname, Profilbild, Position, Abteilung, Firma, Büro, Personalnummer, Telefon (geschäftlich/mobil), Adresse, Sprache. Namen landen in den normalen WordPress-Feldern, alles andere in User-Meta mit dem Präfix m365_ (z. B. m365_department) und wird auf der Profilseite angezeigt. Das Profilbild wird nach wp-content/uploads/m365-login-avatars/ geladen (Dateiname mit gesalzenem Hash statt Objekt-ID) und ersetzt überall den Gravatar. Achtung: Avatare sind öffentlich sichtbar, wo WordPress sie anzeigt.

Microsoft 365 hat immer Vorrang, bei jedem Lauf:

In Microsoft 365 … … in WordPress
Feld geändert Feld wird überschrieben.
Feld geleert Feld wird geleert (m365_*-Meta gelöscht; ein leerer Anzeigename wird nicht übernommen).
Profilbild geändert Neues Bild wird geladen, das alte gelöscht; die Avatar-URL ändert sich, damit Browser nicht das alte Bild zeigen.
Profilbild gelöscht Bild wird gelöscht, der Avatar fällt auf Gravatar zurück.

Die Bildversionen werden per Graph-$batch geprüft (20 Benutzer pro Anfrage); heruntergeladen werden nur geänderte Bilder, höchstens 500 pro Lauf, der Rest folgt im nächsten (Filter m365_login_sync_photo_limit, Prüfintervall per m365_login_sync_photo_interval). Bei einem Fehler von Microsoft bleibt das vorhandene Bild erhalten gelöscht wird nur, wenn Microsoft ausdrücklich „kein Foto“ meldet. Abgewählte Felder und Bilder werden beim nächsten Lauf aus den Profilen entfernt (Vor-, Nach- und Anzeigename bleiben stehen); wird ein Benutzer in WordPress gelöscht, wird auch sein Bild gelöscht.

Deaktivierte Konten bekommen ein Zufallspasswort, verlieren ihre Rolle auf der Site (sie wird gemerkt und bei der Reaktivierung zurückgegeben) und alle Application Passwords so bleiben sie auch gesperrt, wenn das Plugin einmal deaktiviert wird. Sie können sich überhaupt nicht mehr anmelden weder per Microsoft noch per Passwort, Anwendungspasswort oder bestehender Session (alle Sessions werden beendet). In der Benutzerliste zeigt die Spalte Microsoft 365 den Status; per Zeilenaktion lassen sich Konten auch von Hand deaktivieren und reaktivieren.

Löschen braucht einen Benutzer, der die Beiträge übernimmt. Ohne Auswahl wird stattdessen deaktiviert es gehen nie Inhalte verloren.

Schutzmechanismen

  • Testlauf: zeigt vollständig, was angelegt, geändert, deaktiviert oder gelöscht würde ohne etwas zu ändern.
  • Sicherheitsstopp: Würde ein Lauf mehr als 20 % der verknüpften Konten (mindestens 5) deaktivieren oder löschen, passiert gar nichts (Filter m365_login_sync_deprovision_limit).
  • Fehler = Abbruch: Schlägt eine Graph-Anfrage fehl, bricht der Lauf ab, bevor irgendein Konto deaktiviert wird. „Gelöscht“ gilt ein Konto nur, wenn Graph für genau diese Objekt-ID 404 liefert.
  • Geschützte Konten: Administratoren, die schon vor dem Sync existierten, und das eigene Konto werden nie deaktiviert, gelöscht oder umgestuft (Filter m365_login_sync_protect_user).
  • Sperre gegen Parallelläufe, Protokoll der letzten Ausführung im Backend.

WP-CLI empfehlenswert für große Verzeichnisse oder exakte Zeiten per System-Cron:

wp m365-login sync --dry-run   # Testlauf
wp m365-login sync             # echter Lauf

Administrator-Konten verknüpfen

Das mail-Attribut in Entra ID kann jeder Benutzer- oder Exchange-Administrator des Tenants frei setzen wer es auf die Adresse eines WordPress-Admins setzt, dürfte sonst dessen Konto übernehmen. Privilegierte Konten werden deshalb nur auf einem dieser Wege mit einem Microsoft-Konto verknüpft (per Anmeldung oder Sync):

Weg Wann sinnvoll
Selbst verknüpfen: Profil → Microsoft 365 → „Mit Microsoft-Konto verknüpfen“. Die Person ist in WordPress angemeldet (beweist das WordPress-Konto) und meldet sich einmal bei Microsoft an (beweist das Microsoft-Konto). Immer auch wenn UPN und Mailadresse völlig verschieden sind. Im Nur-Button-Modus vorher über den Fallback-Link mit Passwort anmelden.
Zuweisen: Ein Administrator trägt beim Bearbeiten des Benutzers unter Microsoft 365 den UPN ein (Zugewiesenes Microsoft-Konto). Anmeldung und Sync verknüpfen genau dieses Konto. Mehrere Admins einrichten, ohne dass jeder selbst klicken muss.
Automatisch: UPN eines Mitglieds (kein Gast) = WordPress-E-Mail. Wenn UPN und Mailadresse bei euch gleich sind.

Nach der Verknüpfung findet die Anmeldung das Konto über die unveränderliche Objekt-ID E-Mail-Adresse oder UPN dürfen sich danach ändern. Eine bestehende Verknüpfung kann nur ein Administrator aufheben (Verknüpfung mit dem Microsoft-Konto aufheben im Profil); eine Person kann ihr Konto nicht selbst auf ein anderes Microsoft-Konto umhängen.


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 Der Login legt keine Konten an; optionale Domain-Allowlist, optionale Gruppen-Beschränkung (fail closed). Konten entstehen nur durch den explizit gestarteten bzw. aktivierten Benutzer-Sync.
Ausgeschiedene Mitarbeitende Benutzer-Sync deaktiviert oder löscht Konten, die in Microsoft 365 deaktiviert/gelöscht wurden; deaktivierte Konten verlieren sofort alle Sessions und jeden Anmeldeweg.
Massen-Deprovisionierung durch Fehlkonfiguration Testlauf, Sicherheitsstopp (> 20 % / min. 5), Abbruch bei jedem Graph-Fehler, „gelöscht“ nur bei 404 für die konkrete Objekt-ID, geschützte Administratoren.
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. 300 Login-Starts pro IP und 10 Minuten; Proxy-Header per M365_LOGIN_CLIENT_IP_HEADER am besten ein einwertiger Header wie HTTP_CF_CONNECTING_IP oder HTTP_X_REAL_IP (bei X-Forwarded-For zählt der rechte, vom Proxy geschriebene Eintrag).
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' );

Benutzer-Sync

// Weiteres Graph-Attribut anbieten (landet in User-Meta "m365_cost_center")
add_filter( 'm365_login_sync_attributes', function ( $attributes ) {
    $attributes['costCenter'] = array( 'label' => 'Kostenstelle', 'target' => 'm365_cost_center' );
    return $attributes;
} );

// Rollen pro Person anpassen (erste Rolle = Hauptrolle)
add_filter( 'm365_login_sync_roles', function ( array $roles, $oid ) {
    return $roles;
}, 10, 2 );

// Daten für neu angelegte Konten (Argumente für wp_insert_user)
add_filter( 'm365_login_sync_new_user_data', function ( array $data, array $person ) {
    $data['user_login'] = strtolower( $person['userPrincipalName'] );
    return $data;
}, 10, 2 );

// Weitere Konten vom Sync ausnehmen
add_filter( 'm365_login_sync_protect_user', function ( $protected, WP_User $user ) {
    return $protected || in_array( 'shop_manager', $user->roles, true );
}, 10, 2 );

// Sicherheitsstopp anheben (Standard: 20 % der verknüpften Konten, mindestens 5)
add_filter( 'm365_login_sync_deprovision_limit', fn( $limit, $linked ) => max( 10, $linked * 0.5 ), 10, 2 );

// Weitere: m365_login_sync_email, m365_login_sync_photo_limit, m365_login_sync_photo_interval, m365_login_is_privileged_user
// Actions: m365_login_sync_user_created, m365_login_sync_finished, m365_login_user_disabled, m365_login_user_enabled

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. Bei ausgeschlossenen Gruppen ist Graph immer nötig.
… member of a group that is not allowed to sign in here. Benutzer ist Mitglied einer ausgeschlossenen Gruppe (auch verschachtelt).
This account has been deactivated. Das Konto wurde vom Benutzer-Sync oder von Hand deaktiviert. Benutzer → Zeilenaktion „Reaktivieren“ ist die Person in Microsoft 365 noch deaktiviert, deaktiviert der nächste Sync sie wieder.
Meldung im Sync-Protokoll Ursache & Lösung
Microsoft Graph refused the request … Anwendungsberechtigung User.Read.All (und bei Gruppen GroupMember.Read.All) fehlt oder keine Administratorzustimmung.
The user sync needs a pinned tenant ID … Im Tab Verbindung die Tenant-GUID statt organizations/common eintragen.
Safety stop: … accounts would be deactivated or deleted … Sync-Gruppen oder Tenant prüfen, Testlauf ansehen; bei gewollter Massenänderung das Limit per Filter anheben.
… is protected … and was not changed. Bestehender Administrator oder eigenes Konto bewusst ausgenommen.
Zeitüberschreitung beim Klick auf Jetzt synchronisieren Der Lauf geht serverseitig weiter; Seite später neu laden. Für große Verzeichnisse wp m365-login sync verwenden.

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, Paging, Benutzer/Gruppen/Fotos, checkMemberGroups
  class-m365-login-sync.php     Benutzer-Sync, Rollen, Profilfelder/-bilder, Deaktivierung, WP-CLI
  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? Ja, mit dem Benutzer-Sync: Er importiert alle (oder ausgewählte) Microsoft-365-Benutzer vorab als WordPress-Konten. Der Login selbst legt weiterhin nie Konten an wer nicht importiert oder von Hand angelegt wurde, kommt nicht rein.
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, dürfen aber **nur von Super-Admins** geändert werden: Sie entscheiden, welche Microsoft-Identität sich als welcher (netzwerkweite) WordPress-Benutzer anmelden darf. Ein Site-Admin könnte sonst einen eigenen Tenant eintragen und sich als Super-Admin anmelden. Der Benutzer muss Mitglied der Site (oder Super-Admin) sein.
Was passiert beim Deinstallieren? Einstellungen, Caches (Transients), Sync-Protokoll, Cron-Termin, gespeicherte Profilbilder und die pro Benutzer gespeicherten Plugin-Daten (Objekt-ID, Deaktivierungs-Status) werden entfernt auch in Multisite. Importierte Konten und übernommene Profilfelder (m365_*) bleiben erhalten. Deaktivierte Konten bleiben ohne Rolle, mit Zufallspasswort und ohne Application Passwords der Deaktivierungs-Vermerk selbst wird entfernt.
Benutzer-Sync und Multisite? Der Sync arbeitet pro Site: Neue Konten werden zur aktuellen Site hinzugefügt, „Löschen“ entfernt das Konto nur aus dieser Site. Die Deaktivierung gilt netzwerkweit, weil sie am Benutzer hängt.
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.