Laravel uygulamasına oturum açma ekleyin

Bu hızlı başlangıç, standart OpenID Connect istemcisini facile-it/php-openid-client kullanarak bir Laravel 13 uygulamasına KeenAuth ile oturum açmayı ekler. KeenAuth'un henüz bir PHP SDK'sı yok ve buna ihtiyaç da yok: KeenAuth standart bir OpenID Connect sağlayıcısıdır, dolayısıyla standart bir OpenID Connect istemci kütüphanesi çalışır. Uygulamanız OAuth istemcisidir ve tüm belirteçleri sunucu tarafındaki oturumda tutar; tarayıcıya yalnızca şifrelenmiş bir oturum kimliği çerezi gider. Oturum açma rotalarını ekler, bir sayfayı korur, korumalı bir API'yi kullanıcının erişim belirteciyle çağırır, oturumu kapatır ve uygulamayı çevrimdışı test edersiniz.

Uygulamanın şu rotaları vardır:

Rota Ne yapar
GET / Herkese açık ana sayfa: bir oturum açma düğmesi ya da kullanıcının adı ve bir oturum kapatma düğmesi
GET /dashboard Oturum gerektirir. Kullanıcıyı ve API'nizin döndürdüğü iletileri gösterir
POST /login Oturum açmayı başlatır. Laravel CSRF belirtecini denetler
GET /auth/callback Yanıtı denetler, kodu değiştirir ve oturumu başlatır
POST /logout Oturumu burada ve KeenAuth'ta sonlandırır

Başlamadan önce

1. Yükleyin

composer require facile-it/php-openid-client:1.0.0 guzzlehttp/guzzle:8.3.1

Kütüphane bir PSR-18 HTTP istemcisine ihtiyaç duyar; Guzzle bunlardan biridir. Örnek, composer.json dosyasında her paketi tam olarak sabitler ve composer.lock, her paketi bir commit'e sabitler. composer install --no-plugins --no-scripts ile yüklenir, bu nedenle yükleme sırasında hiçbir paket kodu çalışmaz.

2. Yapılandırın

Uygulama yapılandırmasını yalnızca ortam değişkenlerinden, config/keenauth.php aracılığıyla okur. Bunları Laravel'in yüklediği .env dosyasına koyun ve bu dosyayı asla commit etmeyin.

Değişken Zorunlu Anlamı
KEENAUTH_ISSUER evet Ortamınızın yayımcısı, https
KEENAUTH_CLIENT_ID evet Web uygulamasının client_id
KEENAUTH_CLIENT_SECRET evet Uygulamanın istemci gizli anahtarı
APP_BASE_URL evet Uygulamanın herkese açık URL'si. Yönlendirme URI'si bundan oluşturulur. http yalnızca localhost, 127.0.0.1 veya [::1] için kullanılır
API_URL evet Uygulamanın çağırdığı API, örneğin http://localhost:8080
KEENAUTH_API_AUDIENCE evet Bu API'nin KeenAuth'taki tanımlayıcısı
KEENAUTH_ID_TOKEN_ALG hayır Ortamınızın ID belirteçlerini imzaladığı algoritma: RS256 (varsayılan), PS256, ES256 veya EdDSA
APP_KEY evet Laravel'in kendi anahtarı; oturum çerezini şifreler. php artisan key:generate ile bir anahtar üretin

Config::fromArray bu değerleri denetler. Eksik ya da kullanılamayan bir değer, ihtiyaç duyan ilk isteği durdurur ve hiçbir hata iletisi bir değer içermez. HMAC algoritmaları reddedilir; böylece istemci gizli anahtarı hiçbir zaman bir ID belirtecini imzalayamaz:

        $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 istemcisini oluşturun

App\KeenAuth\KeenAuth, istemciyi keşif (<issuer>/.well-known/openid-configuration) üzerinden oluşturur ve üst veriyi ile anahtar kümesini bir saat önbellekte tutar:

        $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,
            ]))

Servis sağlayıcı, istemciyi tekil (singleton) olarak kaydeder; HTTP istemcisini ise testlerin değiştirebilmesi için ayrı bir bağlama olarak kaydeder.

4. Oturum açmayı başlatın

Her oturum açma işlemi yeni bir state, nonce ve PKCE doğrulayıcısı üretir ve bunları oturumda saklar:

    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)),
        ];
    }

Yetkilendirme isteği, doğrulayıcının S256 özetini ve API'nizin tanımlayıcısını resource olarak gönderir (RFC 8707). resource olmadan erişim belirteci API'niz için düzenlenmez ve API onu reddeder.

        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,
        ]);

Oturum açma, CSRF belirteciyle /login adresine gönderim yapan bir formdur; böylece başka bir site kullanıcı adına oturum açmayı başlatamaz:

    <form action="/login" method="post">
      @csrf
      <button type="submit">Sign in</button>
    </form>

5. Geri çağrıyı işleyin

Kütüphane state denetimini uygulamanıza bırakır. Geri çağrı, işlemi oturumdan alır; böylece bir state yalnızca bir kez çalışır ve sabit zamanda karşılaştırılır. Ortamınız her yetkilendirme yanıtında kendini adlandırır (RFC 9207); bu nedenle geri çağrı, iss değeri eksik olan ya da başka bir yayımcıyı gösteren yanıtı da reddeder:

        $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');
        }

Geri çağrıdan yalnızca kod alınır. Kütüphane kodu PKCE doğrulayıcısıyla değiştirir ve ID belirtecini denetler: ortamınızın anahtarlarıyla imzasını, iss, aud, exp, iat, algoritmayı ve bu oturum açmaya ait nonce değerini.

        return $this->service()->callback($this->client(), ['code' => $code], $this->config->redirectUri(),
            AuthSession::fromArray($transaction));

Uygulama bundan sonra yeni bir oturum kimliği başlatır ve kullanıcıyı ile belirteçleri sunucu tarafındaki oturumda tutar:

        // A new session id after sign-in prevents session fixation.
        $request->session()->regenerate(destroy: true);

Oturum ayarları config/session.php dosyasındadır: file sürücüsü ve şifrelenmiş, HttpOnly, SameSite=Lax bir çerez; bu çerez Secure olur, ancak SESSION_SECURE_COOKIE=false değerini http://localhost için ayarlarsanız farklı olur. Birden çok sunucu kullanıyorsanız redis ya da database sürücüsünü kullanın.

6. Sayfaları koruyun ve API'nizi çağırın

Kullanıcı gerektiren rotalar keenauth ara katmanını kullanır. Örnekte yenileme belirteci yoktur: erişim belirteci sona erdiğinde kullanıcı yeniden oturum açar.

Route::get('/dashboard', [DashboardController::class, 'show'])->middleware('keenauth');

Kontrol paneli, API'nizi erişim belirteciyle taşıyıcı belirteç olarak çağırır. Belirteç sunucuda kalır; tarayıcı yalnızca HTML alır.

        $response = $http->sendRequest(new HttpRequest('GET', $keenauth->config->apiUrl . '/api/messages', [
            'Authorization' => 'Bearer ' . $request->session()->get('keenauth.access_token'),
            'Accept' => 'application/json',
        ]));

7. Oturumu kapatın

Oturum kapatma, CSRF belirteciyle gönderilen bir POST'dır. Uygulama yerel oturumu sonlandırır ve tarayıcıyı ortamınızın çıkış uç noktasına (OpenID Connect RP-Initiated Logout) yönlendirir; bunu id_token_hint, client_id ve post_logout_redirect_uri ile yapar ve tarayıcı uygulamanıza geri döner:

        $idToken = $request->session()->get('keenauth.id_token');
        $request->session()->invalidate();
        $request->session()->regenerateToken();

8. Uygulamayı çalıştırın

Express hızlı başlangıcındaki API'yi (ya da bu belirteçleri kabul eden herhangi bir API'yi) başlatın ve uygulamayı çalıştırın:

php artisan serve

http://localhost:8000 adresini açın, Giriş yap seçeneğini seçin, KeenAuth'ta oturum açın ve kontrol panelini açın.

9. Ağ olmadan test edin

Testler ağ bağlantısı ya da bir KeenAuth hesabı gerektirmez. Sahte bir PSR-18 istemcisi HTTP istemcisinin yerini alır ve ortamınızı (keşif, test çalıştırması için üretilmiş bir anahtar kümesi, belirteç uç noktası) ve API'yi taklit eder. Testler APP_ENV=ci ile çalışır, çünkü Laravel CSRF denetimini testing altında atlar ve bu testler onu denetler.

    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

Testler şunları denetler: yapılandırmanın doğrulanması (ve hiçbir hata iletisinde gizli bilginin bulunmaması); oturum açmanın CSRF belirteci gerektirmesi ve PKCE S256 göndermesi, state, nonce ve resource; kodun eşleşen doğrulayıcıyla ve istemci kimlik doğrulamasıyla değiştirilmesi; erişim belirtecinin Authorization başlığıyla API'ye gönderilmesi ve hiçbir zaman bir çereze yazılmaması; bilinmeyen ya da yeniden kullanılmış bir state, eksik ya da yanlış bir iss, başka bir istemci için ya da farklı bir nonce ile üretilmiş ID belirteci ve bir yetkilendirme hatasının hiçbir oturum oluşturmadan reddedilmesi; oturumu olmayan bir sayfanın ana sayfaya yönlendirilmesi; ve oturum kapatmanın CSRF belirteci gerektirmesi ve id_token_hint ile çıkış uç noktasına gitmesi.

Gerçek bir tarayıcıyla tam oturum açma için bir KeenAuth ortamı gerekir. Bu, çevrimdışı testlerin parçası değildir.

Bir proxy ya da yük dengeleyici arkasında

APP_BASE_URL değerini uygulamanın herkese açık URL'sine ayarlayın; yönlendirme URI'si bundan oluşturulur, hiçbir zaman Host başlığından oluşturulmaz. Bir proxy TLS'yi sonlandırıyorsa, Laravel'in güvenilen proxy ayarını yalnızca o proxy için yapılandırın.

Bugünkü sınırlar

Sonraki adımlar