Anmeldung in eine Laravel-Anwendung einbauen
Dieser Schnellstart fügt der Laravel-13-Anwendung die Anmeldung mit KeenAuth hinzu, und zwar mit dem Standard-OpenID-Connect-Client facile-it/php-openid-client. KeenAuth hat noch kein PHP-SDK, und es wird auch keines benötigt: KeenAuth ist ein Standard-OpenID-Connect-Provider, daher funktioniert eine Standard-OpenID-Connect-Client-Bibliothek. Ihre Anwendung ist der OAuth-Client und bewahrt alle Token in der serverseitigen Sitzung auf; der Browser erhält nur ein verschlüsseltes Sitzungs-ID-Cookie. Sie fügen die Anmelde-Routen hinzu, schützen eine Seite, rufen eine geschützte API mit dem Zugriffstoken des Benutzers auf, melden sich ab und testen die Anwendung offline.
Die Anwendung hat diese Routen:
| Route | Was sie macht |
|---|---|
GET / |
Öffentliche Startseite: eine Schaltfläche zum Anmelden oder der Name des Benutzers und eine Schaltfläche zum Abmelden |
GET /dashboard |
Benötigt eine Sitzung. Zeigt den Benutzer und die Nachrichten, die Ihre API zurückgibt |
POST /login |
Startet die Anmeldung. Laravel prüft das CSRF-Token |
GET /auth/callback |
Prüft die Antwort, tauscht den Code aus und startet die Sitzung |
POST /logout |
Beendet die Sitzung hier und bei KeenAuth |
Bevor Sie beginnen
- Eine KeenAuth-Umgebung. Ihr Issuer sieht aus wie
https://your-environment.eu.keenauth.app. - Eine Webanwendung in der Umgebung (Anwendung anlegen) mit einem Client-Secret (
client_secret_basic). Registrieren Sie in einerdevelopment-Umgebung die Redirect-URIhttp://localhost/auth/callbackund die Post-Logout-Redirect-URIhttp://localhost/; eine Loopback-URI wird ohne Port registriert und passt auf jeden Port, etwa8000. - Eine API, die in der Umgebung registriert ist (API anlegen), mit dem Scope
messages:read. Ihreidentifier, zum Beispielhttps://api.example.com, ist die Audience ihrer Zugriffstoken. Der Express-Schnellstart erstellt eine API, die diese Token akzeptiert. - PHP 8.3 oder neuer und Composer 2.
1. Installieren
composer require facile-it/php-openid-client:1.0.0 guzzlehttp/guzzle:8.3.1
Die Bibliothek benötigt einen PSR-18-HTTP-Client; Guzzle ist einer davon. Das Beispiel fixiert jedes Paket genau in composer.json, und composer.lock fixiert jedes Paket auf einen Commit. Die Installation erfolgt mit composer install --no-plugins --no-scripts, daher läuft während der Installation kein Paketcode.
2. Konfigurieren
Die Anwendung liest ihre Konfiguration ausschließlich aus Umgebungsvariablen, und zwar über config/keenauth.php. Legen Sie diese in .env ab, das Laravel lädt, und committen Sie diese Datei niemals.
| Variable | Erforderlich | Bedeutung |
|---|---|---|
KEENAUTH_ISSUER |
ja | Der Issuer Ihrer Umgebung, https |
KEENAUTH_CLIENT_ID |
ja | Die client_id der Webanwendung |
KEENAUTH_CLIENT_SECRET |
ja | Ihr Client-Secret |
APP_BASE_URL |
ja | Die öffentliche URL der Anwendung. Die Redirect-URI wird daraus gebildet. http nur für localhost, 127.0.0.1 oder [::1] |
API_URL |
ja | Die API, die die Anwendung aufruft, zum Beispiel http://localhost:8080 |
KEENAUTH_API_AUDIENCE |
ja | Der Bezeichner dieser API in KeenAuth |
KEENAUTH_ID_TOKEN_ALG |
nein | Der Algorithmus, mit dem Ihre Umgebung ID-Token signiert: RS256 (Standard), PS256, ES256 oder EdDSA |
APP_KEY |
ja | Der eigene Schlüssel von Laravel; er verschlüsselt das Sitzungs-Cookie. Erzeugen Sie ihn mit php artisan key:generate |
Config::fromArray prüft diese Werte. Ein fehlender oder unbrauchbarer Wert stoppt die erste Anfrage, die ihn benötigt, und keine Fehlermeldung enthält einen Wert. Ein HMAC-Algorithmus wird abgelehnt, damit das Client-Secret niemals ein ID-Token signieren kann:
$alg = $values['id_token_alg'] ?? 'RS256';
if (!in_array($alg, ['RS256', 'PS256', 'ES256', 'EdDSA'], true)) {
// Never an HMAC algorithm: the client secret must not be able to sign an ID token.
throw new InvalidArgumentException('KEENAUTH_ID_TOKEN_ALG must be RS256, PS256, ES256 or EdDSA');
}
3. OpenID-Connect-Client erstellen
App\KeenAuth\KeenAuth baut den Client aus der Discovery (<issuer>/.well-known/openid-configuration) und speichert die Metadaten und den Schlüsselsatz eine Stunde lang zwischen:
$issuer = (new IssuerBuilder())
->setMetadataProviderBuilder((new MetadataProviderBuilder())->setHttpClient($this->http)
->setCache($this->cache)->setCacheTtl(3600))
->setJwksProviderBuilder((new JwksProviderBuilder())->withHttpClient($this->http)
->withCache($this->cache)->withCacheTtl(3600))
->build($this->config->issuer);
->setClientMetadata(ClientMetadata::fromArray([
'client_id' => $this->config->clientId,
'client_secret' => $this->config->clientSecret,
'token_endpoint_auth_method' => 'client_secret_basic',
'redirect_uris' => [$this->config->redirectUri()],
// The verifier accepts only this algorithm, never an HMAC one.
'id_token_signed_response_alg' => $this->config->idTokenAlg,
]))
Der Service-Provider registriert ihn als Singleton, den HTTP-Client als eigene Bindung, damit die Tests ihn ersetzen können.
4. Anmeldung starten
Jede Anmeldung erzeugt einen neuen state, nonce und PKCE-Verifier und hält sie in der Sitzung vor:
public static function newTransaction(): array
{
return [
'state' => self::base64url(random_bytes(32)),
'nonce' => self::base64url(random_bytes(32)),
'code_verifier' => self::base64url(random_bytes(32)),
];
}
Die Autorisierungsanfrage sendet die S256-Challenge des Verifiers und den Bezeichner Ihrer API als resource (RFC 8707). Ohne resource ist das Zugriffstoken nicht für Ihre API bestimmt, und die API weist es ab.
return $this->service()->getAuthorizationUri($this->client(), [
// messages:read is a scope of your API; the example API requires it on /api/messages.
'scope' => 'openid profile email messages:read',
'state' => $transaction['state'],
'nonce' => $transaction['nonce'],
// PKCE (RFC 7636): S256 of the verifier that only this session knows.
'code_challenge' => self::base64url(hash('sha256', $transaction['code_verifier'], true)),
'code_challenge_method' => 'S256',
// RFC 8707: an access token for your API.
'resource' => $this->config->apiAudience,
]);
Die Anmeldung ist ein Formular, das mit dem CSRF-Token an /login sendet, damit keine andere Website eine Anmeldung für den Benutzer starten kann:
<form action="/login" method="post">
@csrf
<button type="submit">Sign in</button>
</form>
5. Callback verarbeiten
Die Bibliothek überlässt die state-Prüfung Ihrer Anwendung. Der Callback entnimmt die Transaktion der Sitzung, sodass ein state nur einmal funktioniert, und vergleicht ihn in konstanter Zeit. Ihre Umgebung nennt sich in jeder Autorisierungsantwort selbst (RFC 9207). Daher lehnt der Callback auch eine Antwort ab, deren iss fehlt oder einen anderen Issuer nennt:
$transaction = $request->session()->pull('keenauth.transaction');
$state = $request->query('state');
if (!is_array($transaction) || !is_string($state) || !hash_equals($transaction['state'], $state)) {
return $this->failure('invalid_state');
}
// RFC 9207: the response names its issuer, so a response from another provider is refused.
$iss = $request->query('iss');
if ($keenauth->issuerInResponse() && $iss !== $keenauth->config->issuer) {
return $this->failure('invalid_issuer');
}
Aus dem Callback wird nur der Code übernommen. Die Bibliothek tauscht ihn mit dem PKCE-Verifier ein und prüft das ID-Token: seine Signatur mit den Schlüsseln Ihrer Umgebung, iss, aud, exp, iat, den Algorithmus und das nonce dieser Anmeldung.
return $this->service()->callback($this->client(), ['code' => $code], $this->config->redirectUri(),
AuthSession::fromArray($transaction));
Danach vergibt die Anwendung eine neue Sitzungs-ID und hält den Benutzer und die Token in der serverseitigen Sitzung:
// A new session id after sign-in prevents session fixation.
$request->session()->regenerate(destroy: true);
Die Sitzungseinstellungen stehen in config/session.php: der file-Treiber und ein verschlüsseltes, HttpOnly-Cookie, das SameSite=Lax ist, es sei denn, Sie setzen Secure für SESSION_SECURE_COOKIE=false bzw. http://localhost. Bei mehreren Servern verwenden Sie den Treiber redis oder database.
6. Seiten schützen und Ihre API aufrufen
Routen, die einen Benutzer benötigen, verwenden die Middleware keenauth. Das Beispiel hat kein Refresh-Token: Wenn das Zugriffstoken abläuft, meldet sich der Benutzer erneut an.
Route::get('/dashboard', [DashboardController::class, 'show'])->middleware('keenauth');
Das Dashboard ruft Ihre API mit dem Zugriffstoken als Bearer-Token auf. Das Token bleibt auf dem Server; der Browser erhält nur das HTML.
$response = $http->sendRequest(new HttpRequest('GET', $keenauth->config->apiUrl . '/api/messages', [
'Authorization' => 'Bearer ' . $request->session()->get('keenauth.access_token'),
'Accept' => 'application/json',
]));
7. Abmelden
Die Abmeldung ist ein POST mit dem CSRF-Token. Die Anwendung beendet die lokale Sitzung und schickt den Browser an den Logout-Endpunkt Ihrer Umgebung (OpenID Connect RP-Initiated Logout) mit id_token_hint, client_id und post_logout_redirect_uri, wodurch er zu Ihrer Anwendung zurückkehrt:
$idToken = $request->session()->get('keenauth.id_token');
$request->session()->invalidate();
$request->session()->regenerateToken();
8. Anwendung starten
Starten Sie die API aus dem Express-Schnellstart (oder eine andere API, die diese Token akzeptiert) und starten Sie die Anwendung:
php artisan serve
Öffnen Sie http://localhost:8000, wählen Sie Anmelden, melden Sie sich bei KeenAuth an und öffnen Sie das Dashboard.
9. Ohne Netzwerk testen
Die Tests benötigen kein Netzwerk und kein KeenAuth-Konto. Ein gefälschter PSR-18-Client ersetzt den HTTP-Client und übernimmt die Rolle Ihrer Umgebung (Discovery, ein für den Testlauf erzeugter Schlüsselsatz, der Token-Endpunkt) sowie der API. Die Tests laufen mit APP_ENV=ci, weil Laravel die CSRF-Prüfung unter testing überspringt und diese Tests sie prüfen.
public function test_callback_with_an_unknown_state_is_refused_before_any_token_request(): void
{
$this->startSignIn();
$this->returnFromKeenAuth(['state' => 'not-the-state'])->assertStatus(401)->assertSee('invalid_state');
vendor/bin/phpunit
php ../scripts/php_deps.php # exact pins, commits and licences
Die Tests prüfen, dass die Konfiguration validiert wird (und dass keine Fehlermeldung ein Secret enthält); dass die Anmeldung das CSRF-Token benötigt und PKCE S256 sendet, state, nonce und resource; dass der Code mit dem passenden Verifier und mit Client-Authentifizierung eingetauscht wird; dass das Zugriffstoken im Header Authorization an die API geht und niemals in ein Cookie gelangt; dass ein unbekannter oder wiederverwendeter state, ein fehlender oder falscher iss, ein ID-Token für einen anderen Client oder mit einem anderen Nonce sowie ein Autorisierungsfehler ohne Sitzung abgelehnt werden; dass eine Seite ohne Sitzung auf die Startseite weiterleitet; und dass die Abmeldung das CSRF-Token benötigt und mit id_token_hint an den Logout-Endpunkt geht.
Die vollständige Anmeldung mit einem echten Browser benötigt eine KeenAuth-Umgebung. Sie ist nicht Teil der Offline-Tests.
Hinter einem Proxy oder Load Balancer
Setzen Sie APP_BASE_URL auf die öffentliche URL der Anwendung; die Redirect-URI wird daraus gebildet, niemals aus dem Host-Header. Beendet ein Proxy TLS, konfigurieren Sie die vertrauenswürdigen Proxys von Laravel nur für diesen Proxy.
Aktuelle Einschränkungen
- Kein Refresh-Token und keine stille Anmeldung: Der Benutzer meldet sich erneut an, wenn das Zugriffstoken abläuft.
- Der Client authentifiziert sich mit einem Client-Secret. Die Bibliothek unterstützt außerdem
private_key_jwt; das Beispiel zeigt dies nicht.
Nächste Schritte
- Eine Express-API schützen: die API, die diese Anwendung aufruft.
- Token prüfen: der Verifier unter der Middleware Ihrer API.