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> |
||
|---|---|---|
| .github | ||
| .wordpress-org | ||
| assets | ||
| bin | ||
| docs | ||
| includes | ||
| languages | ||
| .distignore | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| composer.json | ||
| LICENSE | ||
| m365-login.php | ||
| phpcs.xml.dist | ||
| README.md | ||
| readme.txt | ||
| uninstall.php | ||
M365 Login
Anmeldung an WordPress mit dem Microsoft 365 / Entra ID-Konto – sicher, schlank, gestaltbar.
Inhalt
- Auf einen Blick
- Screenshots
- So funktioniert es
- Installation
- Einrichtung in Microsoft Entra ID
- Einstellungen im Backend
- Sicherheitskonzept
- Shortcode & Hooks
- Fehlerbehebung
- Entwicklung
- Einreichung bei WordPress.org
- FAQ
- Lizenz
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 |
|---|---|
![]() |
![]() |
| M365 Login – Verbindung | M365 Login – Button |
|---|---|
![]() |
![]() |
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ürlocalhost).
Einrichtung in Microsoft Entra ID
Schritt für Schritt (ca. 5 Minuten)
- 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 Permalinkshttps://deine-seite.tld/?m365-login=callback). - App registrieren. Microsoft Entra Admin Center → App-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
- IDs übernehmen. Auf der Übersichtsseite Anwendungs-ID (Client) und Verzeichnis-ID (Mandant) kopieren → in WordPress eintragen.
- 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.
- E-Mail-Claim aktivieren (empfohlen). Tokenkonfiguration → Optionalen Anspruch hinzufügen → ID →
email. - 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 | 0–50 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 Sicherheit → Erlaubte Entra-Gruppen:
- Gruppenname eintippen (oder Objekt-ID einfügen) → Suchen.
- Treffer mit Hinzufügen übernehmen – sie erscheinen als Chips mit Name und ID.
- Speichern. Ab jetzt darf sich nur anmelden, wer in mindestens einer dieser Gruppen ist (verschachtelte Mitgliedschaften zählen).
Prüfreihenfolge beim Login:
- Enthält das ID-Token einen
groups-Claim → Abgleich direkt im Token. - Sonst (oder bei Overage) → Microsoft Graph
checkMemberGroups. - 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 Sicherheit → Button-only mode:
- Blendet Benutzername/Passwort-Felder und den „Passwort vergessen?“-Link aus.
- Sperrt Passwort-Logins serverseitig, nicht nur per CSS – auf
wp-login.phpund 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 Button → Eigene 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.4–8.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 Hookm365_login_allow_user nicht nachrüsten – das wäre ein anderes Sicherheitsmodell.
Funktioniert es mit privaten Microsoft-Konten (outlook.com)?
Ja, Tenant aufconsumers 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.




