marcel-maqsood / session-auth-middleware
The SessionAuthMiddleware is a PSR-15 middleware that provides handling for sessions and logins in a Laminas/Mezzio application
Package info
github.com/marcel-maqsood/Mezzio-Session-Auth-Middleware
pkg:composer/marcel-maqsood/session-auth-middleware
Requires
- laminas/laminas-diactoros: ^3.3
- marcel-maqsood/database-connector: ^1.0
- marcel-maqsood/mezzio-abstract-request-handler: ^1.0
- mezzio/mezzio-authentication: ^1.9
- mezzio/mezzio-authentication-session: ^1.9
- mezzio/mezzio-helpers: ^5.16
- mezzio/mezzio-router: ^3.17
- mezzio/mezzio-session-ext: ^1.20
- mezzio/mezzio-template: ^2.10
- psr/container: ^1.0
- webmozart/assert: ^1.11
- dev-main
- v1.371-b
- v1.370-b
- v1.361
- v1.360
- v1.352
- v1.351
- v1.350
- v1.340
- v1.330
- v1.322
- v1.321
- v1.320
- v1.310
- v1.300
- v1.200
- v1.190
- v1.180
- v1.170
- v1.160
- v1.150
- v1.145
- v1.144
- v1.143
- v1.142
- v1.15
- 1.14
- v1.1.41
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1
- v1.0.9.9
- v1.0.9.8
- v1.0.9.7
- v1.0.9.6
- v1.0.9.5
- v1.0.9.4
- v1.0.9.3
- v1.0.9.2
- v1.0.9.1
- v1.0.9
- v1.0.8.7
- v1.0.8.6
- v1.0.8.5
- v1.0.8.4
- v1.0.8.3
- v1.0.8.2
- v1.0.8.1
- v1.0.8
- v1.0.7.4
- v1.0.7.3
- v1.0.7.2
- v1.0.7.1
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4.1
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2026-05-21 17:02:50 UTC
README
Die SessionAuthMiddleware (SAM) ist eine PSR-15-Middleware für Laminas/Mezzio-Anwendungen. Sie verbindet Session-basierte Authentifizierung, Login-Routen, Datenbank-gestützte Session-Fingerprints und ein gruppenbasiertes Berechtigungssystem.
SAM prüft nicht nur, ob ein Benutzer angemeldet ist. Sie stellt auch sicher, dass die Session noch gültig ist, dass kein neueres Login auf einem anderen Gerät existiert und dass der angemeldete Benutzer die Permission der aktuell angefragten Route besitzt.
Installation
composer require marcel-maqsood/session-auth
Das Paket registriert seinen ConfigProvider automatisch über Laminas/Mezzio. Die projektbezogenen Konfigurationen müssen trotzdem in die Anwendung übernommen und angepasst werden.
Was SAM macht
- schützt einzelne Routen oder optional die gesamte Pipeline
- nutzt Mezzio Sessions und
mezzio/mezzio-authentication-session - authentifiziert Benutzer über
MazeDEV\SessionAuth\Repository\PDORepository - schreibt pro Login einen Session-Hash und einen Session-Zeitstempel in die Login-Tabelle
- verhindert parallele Logins mit demselben Account, indem ein neuer Login ältere Sessions ungültig macht
- prüft die Session gegen Browser/IP/User-Agent/Salt-Fingerprint
- verlängert den Session-Zeitstempel während aktiver Nutzung
- leitet nicht angemeldete Benutzer auf die passende Fallback- oder Login-Route
- prüft Permissions über Gruppen und Gruppen-Permission-Zuordnungen
- unterstützt mehrere Auth-Bereiche, z. B.
userundadmin, über Table Overrides - kann Benutzernamen und Benutzer-/Permission-Daten an Requests weiterreichen
- stellt Handler für Login, Logout, Passwort-Reset und Account-Erstellung bereit
- legt Fehlertexte kurzzeitig im Cookie
errorab, damit Login-Templates sie anzeigen können
Grundkonfiguration
Die mitgelieferten Dateien in config/ sind als Vorlage für config/autoload/ einer Mezzio-Anwendung gedacht:
config/dependencies.global.phpconfig/authentication.global.phpconfig/messages.global.php
Die Datenbankstruktur liegt in db/base.sql. Zusätzlich gibt es ein MySQL-Workbench-Modell unter db/SQL-model.mwb.
Dependencies
SAM benötigt PersistentPDO, den eigenen PDORepository und die Mezzio SessionMiddleware.
use MazeDEV\DatabaseConnector\PersistentPDO; use MazeDEV\DatabaseConnector\PersistentPDOFactory; use MazeDEV\SessionAuth\Repository\PDORepository; use MazeDEV\SessionAuth\Repository\PDORepositoryFactory; use Mezzio\Authentication\AuthenticationInterface; use Mezzio\Authentication\Session\PhpSession; use Mezzio\Authentication\UserRepositoryInterface; use Mezzio\Session\SessionMiddleware; use Mezzio\Session\SessionMiddlewareFactory; return [ 'dependencies' => [ 'aliases' => [ AuthenticationInterface::class => PhpSession::class, UserRepositoryInterface::class => PDORepository::class, ], 'factories' => [ PersistentPDO::class => PersistentPDOFactory::class, PDORepository::class => PDORepositoryFactory::class, SessionMiddleware::class => SessionMiddlewareFactory::class, ], ], ];
Pipeline
Die Mezzio SessionMiddleware muss früh in der Pipeline laufen, bevor SAM oder Login-Handler auf die Session zugreifen.
$app->pipe(ErrorHandler::class); $app->pipe(ServerUrlMiddleware::class); $app->pipe(SessionMiddleware::class);
SAM kann pro Route verwendet werden. Das ist die empfohlene Variante, wenn nur bestimmte Bereiche geschützt werden sollen.
$app->route('/admin/dashboard[/]', [ MazeDEV\SessionAuth\SessionAuthMiddleware::class, App\Handler\AdminDashboardHandler::class, ], ['GET'], 'adminDashboard');
Alternativ kann SAM global in die Pipeline gesetzt werden. Dann müssen öffentliche Routen in no-auth-routes eingetragen werden.
Wichtig: Wenn SAM global gepiped wird, muss sie nach Mezzio\Helper\UrlHelperMiddleware laufen, weil Redirects über den UrlHelper erzeugt werden.
$app->pipe(UrlHelperMiddleware::class); $app->pipe(SessionAuthMiddleware::class);
Login-Routen
Login-Routen werden über loginHandling definiert. Der Schlüssel ist der Routename der Login-Route. destination ist die Route, auf die nach erfolgreichem Login weitergeleitet wird. resetDestination ist die Passwort-Reset-Route, die im Login-Template verfügbar gemacht wird.
'loginHandling' => [ 'adminLogin' => [ 'name' => 'Admin', 'destination' => 'adminDashboard', 'resetDestination' => 'adminPasswordReset', ], 'userLogin' => [ 'name' => 'Benutzer', 'destination' => 'userDashboard', 'resetDestination' => 'userPasswordReset', ], ],
Beispielrouten:
$app->route('/admin/login[/]', [ MazeDEV\SessionAuth\SessionAuthMiddleware::class, MazeDEV\SessionAuth\Handler\GlobalLoginHandler::class, ], ['GET', 'POST'], 'adminLogin'); $app->route('/admin/dashboard[/]', [ MazeDEV\SessionAuth\SessionAuthMiddleware::class, App\Handler\AdminDashboardHandler::class, ], ['GET'], 'adminDashboard');
Der GlobalLoginHandler rendert das Template app::Login. Die mitgelieferte Vorlage liegt unter src/Templating/Login.html.twig.
Bei einem Login:
- wird eine bestehende User-Session zuerst entfernt
- werden Benutzername und Passwort über
PhpSessionundPDORepositorygeprüft - wird das Passwort mit
password_verify($password . $salt, $hash)validiert - werden Session-Hash und Session-Start in der passenden Login-Tabelle gespeichert
- wird anschließend auf die konfigurierte
destinationweitergeleitet
Logout
Der LogoutHandler entfernt den User aus der Session und leitet auf die Route home weiter. Die Anwendung muss diese Route bereitstellen.
$app->route('/logout[/]', [ MazeDEV\SessionAuth\Handler\LogoutHandler::class, ], ['GET', 'POST'], 'logout');
Öffentliche Routen
Wenn SAM global gepiped wird, werden Routen aus no-auth-routes ohne Auth-Prüfung durchgelassen. Das ist besonders für Passwort-Reset-Routen wichtig.
'no-auth-routes' => [ 'adminPasswordReset' => 'adminLogin', 'userPasswordReset' => 'userLogin', ],
Der Wert wird vom Passwort-Reset-Handler auch als Login-Ziel genutzt, wenn ein Reset-Link ungültig oder abgelaufen ist.
Authentication Config
Die authentication-Config steuert Login-Felder, Repository-Zugriff, Session-Sicherheit, Passwort-Reset und optionale Weitergabe von Daten an Requests.
'authentication' => [ 'redirect' => '/', 'username-forwarding' => true, 'permission-forwarding' => false, 'passwordResetOffset' => 2592000, 'allowWildcard' => true, 'username' => 'username', 'password' => 'password', 'repository' => [ 'table' => 'user', 'fields' => [ 'identities' => [ 'username', 'email', ], 'password' => 'passwordhash', ], 'disable-check' => true, 'table_override' => [ 'user' => [ 'tableKey' => 'user', 'display' => 'Benutzer', 'loginAt' => 'userLogin', ], 'admin' => [ 'tableKey' => 'admin', 'display' => 'Admin', 'loginAt' => 'adminLogin', ], ], ], 'security' => [ 'algo' => 'sha256', 'salt' => 'change-this-salt', 'fields' => [ 'session' => 'sessionhash', 'stamp' => 'sessionstart', ], ], ],
Konfigurationsfelder:
usernameundpassword: POST-Feldnamen für Login-Formulare.repository.table: Standard-Tabellenprefix, wenn keine Route pertable_overrideerkannt wird.repository.fields.identities: Datenbankfelder, über die ein Login gesucht werden darf, z. B. Benutzername oder E-Mail.repository.fields.password: Feld mit dem Passwort-Hash.repository.disable-check: aktiviert die Prüfung gegen das konfigurierte Disabled-Feld der Login-Tabelle.repository.table_override: ordnet Routenpräfixe einem Tabellenprefix zu. Beginnt eine Route mitadmin, nutzt SAM z. B.admin,admin_groups,admin_permissionsusw.security.algo: Hash-Algorithmus für Session- und Reset-Hashes.security.salt: Salt für Passwortprüfung und Session-Fingerprint.security.fields.session: Datenbankfeld für den aktuellen Session-Hash.security.fields.stamp: Datenbankfeld für den Session-Zeitstempel.username-forwarding: setzt den aktuellen Benutzernamen als Request-AttributadminName.permission-forwarding: lädt Benutzerdaten und Permissions früh im Request über denPermissionManager.allowWildcard: erlaubt die Permission*als globale Berechtigung, wenn die angefragte Permission in der Datenbank existiert.passwordResetOffset: Gültigkeit neuer Passwort-Reset-Hashes in Sekunden.
Hinweis: Die aktuelle SessionAuthMiddleware liest table_override unter authentication.repository.table_override.
Session Config
'session' => [ 'config' => [ 'cookie_lifetime' => 60 * 60, 'gc_lifetime' => 60 * 60 * 24, ], ],
gc_lifetime ist für SAM besonders wichtig: Der Wert definiert, wie lange der in der Datenbank gespeicherte Session-Zeitstempel gültig bleibt. Läuft diese Zeit ab, wird die Session verworfen und der Benutzer muss sich neu anmelden.
Während aktiver Nutzung aktualisiert SAM den Datenbank-Zeitstempel höchstens einmal pro Minute.
PermissionManager
Der PermissionManager lädt Berechtigungsdaten für den aktuellen Tabellenprefix und prüft anschließend die Route als Permission.
Das Prinzip ist bewusst einfach: Der Routename ist die Permission.
Beispiel: Eine Route mit dem Namen adminDashboard benötigt eine Permission mit dem Wert adminDashboard. Benutzer erhalten Permissions nicht direkt, sondern über Gruppen.
Der PermissionManager kann:
- alle Permissions eines Bereichs laden
- den aktuellen Tabellenprefix setzen, z. B.
useroderadmin - Benutzerdaten inklusive Settings, Gruppen und Permissions laden
- prüfen, ob der Benutzer in einer Gruppe ist
- prüfen, ob der Benutzer eine Permission besitzt
- Bypass-Permissions berücksichtigen
- Wildcard-Permissions berücksichtigen
- eine Fallback-Route für fehlende Permissions ermitteln
- User-Daten und User-Settings aktualisieren
Permission-Fallbacks
Permissions können eine Fallback-Permission referenzieren. Hat ein Benutzer keinen Zugriff auf die angefragte Route und gibt es keinen internen Referer, leitet SAM auf die Fallback-Route um.
Wenn keine Permission zur Route existiert, verwendet der PermissionManager home als Fallback.
Bypass und Wildcard
Eine Permission mit allowBypass = 1 gilt immer als erlaubt. Das ist nützlich für Fallback- oder Basisrouten, die technisch geschützt sind, aber allen angemeldeten Benutzern offenstehen sollen.
Wenn authentication.allowWildcard auf true steht, kann eine Gruppe mit der Permission * Zugriff auf alle in der Datenbank definierten Permissions erhalten.
Tabellenkonfiguration
SAM verwendet Tabellenprefixe. Für den Prefix user werden z. B. diese Config-Keys erwartet:
useruser_settingsuser_group_relationuser_groupsuser_permissionsuser_group_permission_relation
Für admin entsprechend:
adminadmin_settingsadmin_group_relationadmin_groupsadmin_permissionsadmin_group_permission_relation
Beispiel für user:
'tables' => [ 'user' => [ 'tableName' => 'users', 'identifier' => 'loginId', 'loginName' => 'username', 'loginMail' => 'email', 'disabled' => 'disabled', 'hidden' => 'hidden', 'resetHash' => 'forgothash', 'resetValid' => 'forgotvalid', ], 'user_settings' => [ 'tableName' => 'user_settings', 'identifier' => 'settingId', 'user_identifier' => 'loginId', 'icon_path' => 'icon_path', 'language' => 'language', ], 'user_group_relation' => [ 'tableName' => 'user_has_groups', 'identifier' => 'lhgId', 'group_identifier' => 'groupId', 'login_identifier' => 'loginId', ], 'user_groups' => [ 'tableName' => 'user_groups', 'identifier' => 'groupId', 'name' => 'name', 'hidden' => 'hidden', ], 'user_permissions' => [ 'tableName' => 'user_permissions', 'identifier' => 'permissionId', 'name' => 'name', 'value' => 'value', 'noPermFallback' => 'noPermFallback', 'allowBypass' => 'allowBypass', 'hidden' => 'hidden', ], 'user_group_permission_relation' => [ 'tableName' => 'user_group_has_permissions', 'identifier' => 'ghpId', 'permission_identifier' => 'permissionId', 'group_identifier' => 'groupId', ], ],
Die Spaltennamen sind frei konfigurierbar, solange die Config die tatsächlichen Datenbankfelder korrekt abbildet.
Message Config
SAM und die Handler lesen Fehlermeldungen aus messages.error. Wenn SAM während einer Auth-Prüfung einen Fehler erkennt, wird der Text für 60 Sekunden als Cookie error gesetzt.
'messages' => [ 'error' => [ 'session-detail-error' => 'Ihre Sitzung scheint fehlerhaft zu sein, bitte melden Sie sich erneut an.', 'session-set-error' => 'Ihre Sitzung konnte nicht eingetragen werden, bitte probieren Sie es erneut.', 'session-expired-error' => 'Ihre Sitzung ist ausgelaufen, bitte melden Sie sich erneut an.', 'another-device-logon-error' => 'Ein anderes Gerät hat sich angemeldet.', 'logon-required-error' => 'Für diesen Inhalt müssen Sie angemeldet sein.', 'user-create-error' => 'Der Zugang konnte nicht angelegt werden.', 'user-repo-error' => 'Sie müssen sich mit einem Zugang für diesen Bereich anmelden.', 'credential-error' => 'Fehlerhafte Zugangsdaten', 'session-path-swap-error' => 'Sie wurden abgemeldet, da Ihre Sitzung für diesen Bereich ungültig ist.', ], ],
Verwendete Keys:
session-detail-error: Sessiondaten fehlen oder sind nicht lesbar.session-set-error: Session-Zeitstempel konnte nicht sauber gesetzt oder gelesen werden.session-expired-error: Session ist nachgc_lifetimeabgelaufen.another-device-logon-error: Der Datenbank-Sessionhash passt nicht mehr zur aktuellen Session.logon-required-error: Die Route benötigt ein Login.credential-error: Benutzername oder Passwort sind falsch.user-create-error: Account-Erstellung konnte nicht gespeichert werden.user-repo-error: Login passt nicht zum aktuellen Auth-Bereich.session-path-swap-error: Eine Session aus einem Auth-Bereich wird für einen anderen Bereich benutzt, z. B. Admin-Session auf User-Route.
Im Login-Handler wird das Cookie gelesen, gelöscht und als Template-Variable error an app::Login übergeben.
Passwort-Reset
Der ForgotPasswordHandler unterstützt zwei POST-Aktionen:
request: Reset-Link anfordernsubmit: neues Passwort speichern
Für den Reset werden diese Felder in der Login-Tabelle benötigt:
resetHashresetValidloginMail
Die Gültigkeit wird über authentication.passwordResetOffset gesteuert. Standard im Code ist 2592000 Sekunden, also 30 Tage.
Für den Mailversand erwartet der Handler:
requestPasswordAdaptersubmitPasswordAdapter- das optionale Paket
MazeDEV\FormularHandlerMiddleware\Adapter\SmtpMail
Die mitgelieferten Templates liegen unter:
src/Templating/SetPasswordForm.html.twigsrc/Templating/emailing/ForgotPassword.html.twigsrc/Templating/emailing/PasswordSet.html.twigsrc/Templating/emailing/DefaultMail.html.twig
Account-Erstellung
Der CreateAccountHandler verarbeitet POST-Requests und nutzt die Tabellenkonfiguration des aktuell erkannten Auth-Bereichs. Die eigentliche Feldabbildung wird über den AbstractRequestHandler erzeugt.
Fehlschläge werden mit messages.error.user-create-error gemeldet.
Session- und Sicherheitslogik
SAM erzeugt den Session-Fingerprint aus:
- PHP-Zeitzone
- Remote-IP plus Proxy-Header, wenn vorhanden
- User-Agent
authentication.security.salt
Der Hash wird mit authentication.security.algo erzeugt und beim Login in der Datenbank gespeichert. Bei jedem geschützten Request wird der aktuelle Hash mit dem Datenbankwert verglichen.
Wenn ein anderer Login denselben Account verwendet, überschreibt dieser Login den Datenbankhash. Die ältere Session erkennt das beim nächsten Request und wird abgemeldet.
Zugriff auf User und Permissions im Code
Die Middleware hält den PermissionManager statisch vor:
$user = MazeDEV\SessionAuth\SessionAuthMiddleware::$permissionManager::getUser(); $groups = MazeDEV\SessionAuth\SessionAuthMiddleware::$permissionManager->getGroups(); $hasPermission = MazeDEV\SessionAuth\SessionAuthMiddleware::$permissionManager->userHasPermission('adminDashboard');
Zusätzlich kann username-forwarding den Benutzernamen als Request-Attribut adminName setzen. Bei Sessions mit gespeichertem Auth-Bereich wird außerdem userPath gesetzt.
Credits
Entwickelt von MazeDEV / Marcel Maqsood.
License
MIT. Siehe LICENSE.md.