PHP Login Kodu Açıklaması (PDO + CSRF + Session)

Bu sayfada aşağıdaki login kodunu “mantık akışı” ile inceleyeceğiz: session başlatma, PDO bağlantı, CSRF token, logout, POST kontrolü, kullanıcı doğrulama ve redirect.

Hızlı Özet
  • Girişte: Session başlar → CSRF token üretir → Form POST geldiyse doğrular → DB’den kullanıcıyı çeker → şifreyi doğrular → session’a user yazar → menu.php’ye yönlendirir.
  • Çıkışta: Session temizlenir → cookie iptal edilir → session destroy → aynı sayfaya geri döner.

1) Session Başlatma ve Saat Dilimi

Oturum (session) kullanmak için sayfanın başında session_start() gerekir. session_status() ile “zaten aktif mi?” kontrol edip tekrar başlatmayı engelleriz. Saat dilimi de tarih/saat fonksiyonlarında tutarlılık sağlar.

Kod
<?php
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
date_default_timezone_set('Europe/Istanbul');
?>
Örnek
Kullanıcı giriş zamanı kaydedilecekse ($_SESSION['login_at'] = time();) sunucunun farklı timezone’da olması karmaşa yaratır. Bu yüzden timezone belirlemek iyi alışkanlıktır.

2) PDO ile Veritabanı Bağlantısı

mysql_connect gibi eski fonksiyonlar yerine PDO kullanıyoruz. Bu bölüm “$pdo zaten var mı?” diye kontrol eder; yoksa bağlantıyı oluşturur. ERRMODE_EXCEPTION sayesinde hata olunca try/catch’e düşer.

Kod
<?php
if (!isset($pdo) || !($pdo instanceof PDO)) {
  $host = "localhost";
  $db   = "veritabani_adi";
  $user = "root";
  $pass = "sifre";
  $charset = "utf8mb4";
  $dsn = "mysql:host=$host;dbname=$db;charset=$charset";

  $options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
  ];

  try {
    $pdo = new PDO($dsn, $user, $pass, $options);
  } catch (PDOException $e) {
    die("Veritabanı bağlantı hatası: " . $e->getMessage());
  }
}
?>
Püf Nokta
utf8mb4 kullanmak emoji ve tüm Unicode karakterler için daha güvenlidir. ATTR_EMULATE_PREPARES=false da gerçek prepared statement kullanımı açısından önemlidir.

3) CSRF Token Üretimi

CSRF, kullanıcının tarayıcısından “habersiz form gönderme” saldırısıdır. Çözüm: form içine bir token koyup POST’ta doğrulamak. Token’ı session’da saklarız.

Kod
<?php
if (empty($_SESSION['csrf_token'])) {
  $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
Form tarafı örneği
Token’ı formun içine hidden input olarak koyarsın:
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">

4) Logout (Çıkış)

?logout=1 gelirse session içini temizler, session cookie’yi iptal eder, session’ı kapatır ve sayfayı query’siz hale getirerek yeniden açar.

Kod
<?php
if (isset($_GET['logout']) && $_GET['logout'] == '1') {
  $_SESSION = [];
  if (ini_get("session.use_cookies")) {
    $p = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000, $p["path"], $p["domain"], $p["secure"], $p["httponly"]);
  }
  session_destroy();
  header("Location: " . strtok($_SERVER["REQUEST_URI"], '?'));
  exit;
}
?>
Örnek Kullanım
Menüde “Çıkış” butonu: <a href="/login.php?logout=1">Çıkış</a>

5) Form POST Geldiyse: CSRF + Alan Kontrolü

Login formu gönderildiğinde (POST) önce CSRF doğrulanır, sonra kullanıcı adı ve şifre boş mu kontrol edilir.

Kod
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {

  $token = $_POST['csrf_token'] ?? '';
  if (!hash_equals($_SESSION['csrf_token'], $token)) {
    $errors[] = "Güvenlik doğrulaması başarısız (CSRF).";
  }

  $MyKullanici = trim((string)($_POST["username"] ?? ""));
  $MySifre     = (string)($_POST["password"] ?? "");

  if ($MyKullanici === "") $errors[] = "Kullanıcı adı boş olamaz.";
  if ($MySifre === "")     $errors[] = "Şifre boş olamaz.";
}
?>
Püf Nokta
hash_equals timing attack riskini azaltır; token karşılaştırmada doğru yöntemdir.

6) Kullanıcıyı DB’den Çekme (Prepared Statement)

Kullanıcı adına göre veritabanından tek kayıt çekiyoruz. Burada önemli olan: SQL birleştirmek yerine :u parametresiyle çalışmak (SQL Injection önler).

Kod
<?php
$sql = "SELECT id, username, sifre_hash FROM kullanicilar WHERE username = :u LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute(['u' => $MyKullanici]);
$user = $stmt->fetch();
?>

7) Şifre Doğrulama, Oturum Açma ve Yönlendirme

Şifreler DB’de düz metin tutulmaz. Kayıt sırasında password_hash() ile hash’lenir. Login’de password_verify() ile doğrulanır.

Kod
<?php
if ($user && password_verify($MySifre, $user['sifre_hash'])) {

  session_regenerate_id(true);

  $_SESSION["user_id"]   = (int)$user["id"];
  $_SESSION["user_name"] = (string)$user["username"];
  $_SESSION["login_at"]  = time();

  header("Location: menu.php");
  exit;

} else {
  $errors[] = "Kullanıcı adı veya şifre hatalı.";
}
?>
Örnek: Kayıt olurken şifre nasıl hash’lenir?
<?php
$hash = password_hash($MySifre, PASSWORD_DEFAULT);
// DB'ye $hash kaydedilir (düz şifre değil!)
?>

8) Hata Mesajlarını Tek Satırda Toplamak

Hataları diziye toplayıp en sonda tek bir mesaj haline getiriyorsun:

Kod
<?php
if ($errors) $msg = implode(" ", $errors);
?>
Öneri
İstersen hataları <ul><li> olarak da gösterebilirsin; kullanıcı için daha okunur olur.