Sosyal Giriş Sağlayıcısı Yazma

1.7k görüntülenme Markdown

Giriş, kayıt ve admin ekranlarına "şununla giriş yap" butonu ekleyin. Tek bir sınıf sağlayıcının uçlarını bildirir ve taleplerini bir hesaba eşler.

Genel Bakış

SocialAuth gerçek bir taban sınıfa sahiptir. SocialAuthProvider yetkilendirme adresini, CSRF durumunu, kod takasını ve token doğrulamasını üstlenir. Modülünüz beş uç, bir şema ve bir eşleme metodu bildirir.

Üç sağlayıcı gelir. Keşif kancaya değil kayıt defterine dayanır: Auth::activeProviders() etkin modülleri tutar, yeni bir klasör her ekranda belirir.

Akış: buton, popup, onay, tek geri çağrı, kod takası, doğrulama, hesap çözümü, giriş.

Ön Koşullar

  • OAuth 2.0 kod akışı ve OpenID Connect konuşan, RS256 imzalı id_token veren bir sağlayıcı.
  • Bir client id ve secret, kayıtlı bir yönlendirme adresi.
  • Token ve JWKS uçlarına HTTPS erişimi.
  • Modül Anatomisi, Modül Yapılandırması.

Yapı

dosya düzeni
coremio/modules/SocialAuth/Acme/
├── Acme.php          class Acme extends \SocialAuthProvider
├── config.php        künye + durum + boş ayar anahtarları
├── acme.svg          marka logosu, isteğe bağlı (bi-* simgesine geri düşülür)
└── lang/
    ├── en.php
    └── tr.php
SocialAuthProvider Ortak taban.
Auth::activeProviders() Oturum açma ekranlarının okuduğu kayıt defteri.
Auth::handleProviderCallback() Geri çağrı giriş noktası; durumu tüketir, feedback() çağırır.
Auth::connectProvider() Hesabı çözer ve oturumu açar.
coremio/modules/SocialAuth Klasörünüz buraya girer.

Adım Adım

1. Modül İskeletini Kurun

  1. coremio/modules/SocialAuth/Acme/ dizinini açın.
  2. config.php yazın: meta, status => false ve boş bir settings haritası.
  3. lang/en.php ve lang/tr.php yazın: button-label, field-* çiftleri, error-*, kurulum adımları.
  4. Renkli logoyu acme.svg olarak bırakın ya da bi-* ikon adı döndürün.

2. Uçlar ve Kimlik Bilgileri

  1. Tabanı genişletin ve beş uç metodunu uygulayın.
  2. configFields() uygulayın; 'type' => 'password' alanı şifrelenir ve maskelenir.
  3. testConnection() metodunu sağlayıcıya gerçek bir soru soracak şekilde yazın.
  4. Kurulum adımlarını setup-step-1, setup-step-2 diye kesintisiz yazın.

3. Kimliği Eşleyin

  1. feedback() uygulayın: önce exchange_code(), sonra verify_id_token().
  2. Doğrulanmamış e-postayı hata fırlatarak reddedin; çözücü e-postayla eşler.
  3. Kimliği hesap başına seçin: Google ve Apple sub, Microsoft oid kullanır.
  4. Aşağıdaki iki parçalı diziyi döndürün.

4. Etkinleştirin

  1. Ayarlar'daki Social Auth listesinde sağlayıcınızı bulun.
  2. Panelin gösterdiği Yönlendirme Adresini sağlayıcı konsoluna kaydedin.
  3. Kimlik bilgilerini yapıştırın, Bağlantıyı Test Et'e tıklayın, anahtarı açıp kaydedin.
  4. Giriş sayfasını yenileyin.

Referans

Soyut Sözleşme

Dokuz soyut metot.

imzalar
// Sunum. ['label' => string, 'icon' => 'bi-*', 'logo' => mutlak url (isteğe bağlı)]
abstract public function provider_meta(): array;

// Ayarlar akordiyonu için kimlik bilgisi şeması. Aşağıdaki anahtar tablosuna bakın.
abstract public function configFields(): array;

// [Bağlantıyı Test Et] butonu. true döndürün, ya da operatörün işine yarayacak bir mesajla fırlatın.
abstract public function testConnection(): bool;

abstract protected function auth_endpoint(): string;    // popup'ın kullanıcıyı gönderdiği yer
abstract protected function token_endpoint(): string;   // kodun takas edildiği yer
abstract protected function scopes(): string;           // örn. 'openid email profile'
abstract protected function jwks_url(): string;         // imza doğrulama anahtarları
abstract protected function issuers(): array;           // kabul edilen `iss` değerleri

// $ctx = ['code' => string, 'redirect_uri' => string, 'nonce' => string]
abstract public function feedback(array $ctx): array;

feedback()

Bağlam dizisi üç anahtar taşır.

code Yetkilendirme kodu; sorgu dizesinde ya da gövdede gelir.
redirect_uri Yetkilendirme adımının kullandığı adres.
nonce Yetkilendirme adresine konan değer; verify_id_token() metoduna geçirin.
dönüş biçimi
return [
    // Hesap bağı. `name` bir kullanıcı bilgi anahtarı olur, `value` şifreli kalıcı kimliktir.
    'field_info' => [
        'name'  => "acme_uid",
        'value' => Crypt::encode($sub, Config::get("crypt/user")),
    ],
    // Hesabın neyden kurulduğu / neyle eşleştirildiği.
    'data' => [
        'name'         => "Ada",              // ad
        'surname'      => "Lovelace",         // soyad
        'email'        => "[email protected]",  // sağlayıcı tarafından doğrulanmış OLMALI
        'picture'      => "",                 // avatar url'si, ya da boş dize
        'provider_uid' => $sub,               // ham kimlik, şifresiz
    ],
];

Alan Tanımı

Dönen harita alan adı => tanım biçimindedir; ad hem POST adı hem ayar anahtarıdır.

type Yoksa metin girdisi; password maskeli ve şifreli, textarea çok satırlı.
label Görünen etiket; dil dosyanızdan okuyun.
required HTML doğrulamasını değil etkinlik durumunu besler.
secret password olmayan bir alanı şifreleyip maskeler.
placeholder İpucu.
description Yardım metni.
rows textarea yüksekliği.

Geçersiz Kılma Noktaları

Varsayılanlar düz bir OpenID Connect sağlayıcısına uyar.

MetotVarsayılanNe zaman geçersiz kılınır
client_id()saklı client_idbaşka bir anahtardaysa; beklenen izleyici de budur.
client_secret()çözülmüş client_secretsabit sır yoksa ve her istekte üretiliyorsa.
extra_auth_params()['prompt' => 'select_account']ek parametre gerekiyorsa. Değiştirmeyin, birleştirin.
accept_issuer($iss, $issuers, $payload)issuers() ile eşleşmeihraççı sabit değilse.
Erişimciyi çağırın

Yetkilendirme adresi, kod takası ve izleyici denetimi client_id() üzerinden geçer. Ayarı doğrudan okumak geçersiz kılmayı etkisiz bırakır.

Taban Yardımcıları

exchange_code() Kodu client secret ile gönderir; id_token yoksa fırlatır.
verify_id_token() İmzayı, ihraççıyı, izleyiciyi, süreyi ve nonce'ı lokal doğrular; talepleri döndürür.
setting() Tek bir saklı ayar, ham hâliyle; sır şifreli döner.
callback_url() Tek yönlendirme adresi; başlatan taraf durumda taşınır.
enabled() Anahtar açıkken ve zorunlu her kimlik bilgisi doluyken true.
authorize_url($context, $mode) Yetkilendirme adresini kurar ve durumu kaydeder. $context admin ya da client, $mode login ya da register.
setup_guide() setup-step-N anahtarlarını ilk boşluğa kadar toplar.
save_settings($fields) Akordiyonu kaydeder; maske gönderilirse saklı sırrı korur.

Çekirdek Çağrı Noktaları

Çağrı noktasıÇağırdığıNeden
Auth::activeProviders()provider_meta(), enabled(), connection_button()Kayıt defteri.
Auth::handleProviderCallback()enabled(), consume_state(), feedback()Geri çağrı; mesajınız popup'a döner.
controllers/admin/settings.phpconfigFields(), callback_url(), setup_guide()Ayarlar akordiyonu.
save_social_provider()save_settings()Kayıt; her alan tipine göre filtrelenir.
test_social_provider()testConnection()Test; yazılan değerler önce bellekte uygulanır.

Örnek

Eksiksiz bir sağlayıcı, sonra çekirdek kodu.

Acme.php
<?php
namespace WISECP\Modules\SocialAuth;

use Config;
use Crypt;
use Exception;
use Filter;
use Utility;

class Acme extends \SocialAuthProvider
{
    public function provider_meta(): array
    {
        return [
            'label' => $this->lang["button-label"] ?? "Continue with Acme",
            'icon'  => 'bi-box-arrow-in-right',
            'logo'  => $this->url . 'acme.svg',
        ];
    }

    public function configFields(): array
    {
        return [
            'client_id' => [
                'type'        => 'text',
                'label'       => $this->lang["field-client-id"] ?? "Client ID",
                'required'    => true,
                'placeholder' => "acme-0000-0000",
                'description' => $this->lang["field-client-id-desc"] ?? '',
            ],
            'client_secret' => [
                'type'        => 'password',
                'label'       => $this->lang["field-client-secret"] ?? "Client Secret",
                'required'    => true,
                'description' => $this->lang["field-client-secret-desc"] ?? '',
            ],
        ];
    }

    protected function auth_endpoint(): string  { return "https://id.acme.example/oauth2/authorize"; }
    protected function token_endpoint(): string { return "https://id.acme.example/oauth2/token"; }
    protected function scopes(): string         { return "openid email profile"; }
    protected function jwks_url(): string       { return "https://id.acme.example/.well-known/jwks.json"; }
    protected function issuers(): array         { return ["https://id.acme.example"]; }

    public function testConnection(): bool
    {
        $clientId = $this->client_id();
        if ($clientId === '')
            throw new Exception($this->lang["error-invalid-client-id"] ?? "Please enter a Client ID.");

        // Sağlayıcıya bu kimliğin var olup olmadığını sorun: bilinmeyen biri invalid_client,
        // gerçek olan redirect_uri_mismatch döndürür. Biçim denetimi ikisini de geçirirdi.
        $probe = $this->auth_endpoint() . '?' . http_build_query([
            'client_id'     => $clientId,
            'response_type' => 'code',
            'scope'         => $this->scopes(),
            'redirect_uri'  => $this->callback_url(),
            'state'         => 'wisecp-connectivity-check',
        ]);

        $resp = (string) Utility::HttpRequest($probe, ['timeout' => 8]);
        if ($resp === '')
            throw new Exception($this->lang["error-unreachable"] ?? "Could not reach Acme.");

        if (str_contains($resp, 'invalid_client'))
            throw new Exception($this->lang["error-client-not-found"] ?? "Acme does not recognize this Client ID.");

        return true;
    }

    public function feedback(array $ctx): array
    {
        $code = (string) ($ctx['code'] ?? '');
        if ($code === '') throw new Exception("Missing authorization code.");

        $token   = $this->exchange_code($code, (string) ($ctx['redirect_uri'] ?? ''));
        $payload = $this->verify_id_token((string) $token['id_token'], (string) ($ctx['nonce'] ?? ''));

        $email = (string) ($payload["email"] ?? '');
        if (!$email)
            throw new Exception($this->lang["error-no-email"] ?? "Could not read your email address.");

        // Şüphede reddet: çözücü mevcut bir hesabı e-postayla eşler, yani doğrulanmamış
        // bir adres herkesin başkasının hesabını sahiplenmesine izin verirdi.
        if (($payload["email_verified"] ?? false) !== true)
            throw new Exception($this->lang["error-email-unverified"] ?? "Acme has not verified this email address.");

        $fullName = trim((string) ($payload["given_name"] ?? '') . ' ' . (string) ($payload["family_name"] ?? ''));
        $smash    = Filter::name_smash(Utility::ucfirst_space(Utility::substr($fullName, 0, 255)));

        $sub = (string) ($payload["sub"] ?? '');
        if ($sub === '') throw new Exception($this->lang["error-no-account"] ?? "Could not identify your Acme account.");

        return [
            'field_info' => [
                'name'  => "acme_uid",
                'value' => Crypt::encode($sub, Config::get("crypt/user")),
            ],
            'data' => [
                'name'         => $smash["first"] ?? '',
                'surname'      => $smash["last"] ?? '',
                'email'        => $email,
                'picture'      => (string) ($payload["picture"] ?? ''),
                'provider_uid' => $sub,
            ],
        ];
    }
}
çekirdek tarafı, kopyalamayın
// classes/Auth.php - handleProviderCallback(), sınıfınızın dokunduğu kısma indirgenmiş.
$st = $provider->consume_state($state);            // mode + context + nonce, tek seferlik
if (!$st) return ['status' => 'error', 'message' => "Sign-in could not be completed."];

$result = $provider->feedback([
    'code'         => $code,
    'redirect_uri' => $provider->callback_url(),
    'nonce'        => (string) ($st['nonce'] ?? ''),
]);

// connectProvider() sonra şunları bu sırayla yapar:
//   1. hesabı field_info ile arar (e-posta değişikliğinden sağ çıkar),
//   2. data.email değerine düşer,
//   3. yalnız mode=register ve üye bağlamında hesabı oluşturur,
//   4. field_info değerini saklar ki sonraki giriş 1. adımla çözülsün.
return Auth::connectProvider('member', 'Acme', $result, (string) ($st['mode'] ?? 'login'));

Oturum açma ekranı kayıt defterine sorar:

butonun sayfaya gelişi
// controllers/website/sign.php - giriş sayfası, kayıtta ise aynı çağrı "register" ile.
$this->addData("social_providers", \Auth::activeProviders("login", "client"));

// Tema sonra diziyi gezer; her kayıt ['meta' => [...], 'button' => '<html>'] biçimindedir.

Tuzaklar

Doğrulanmamış e-posta hesap ele geçirmesidir

Çözücü hesabı e-postayla eşler, yani doğrulanmamış bir adres saldırganın başkası olarak girmesine izin verir. Pozitif doğrulama sinyali arayın.

Yanlış kalıcı kimlik ikinci girişi bozar

Hesap için değişmez ve uygulamalar arasında aynı olan bir talep seçin; uygulama başına kimlik, hesabı bir daha üretilmeyecek değere bağlar.

Hatayı fırlatarak bildirin

$this->error = '...'; return false; eski kuşaktan kalmadır. Geri çağrı işleyicisi ve Ayarlar operation'ları hatayı yakalayıp mesajını gösterir; dönen false bozuk sonuç sayılır.

config.php dosyasına elle sır koymayın

Sırlar şifreli yazılır ve eşleşen çözücüyle okunur; elle yazılan değer boş dizeye döner.

Örneği fabrikayla kurun

Her çağrı noktası Modules::getInstance("SocialAuth", $name) kullanır; bu config ve dili doldurur. Doğrudan kurmak atlar.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.