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.
- 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.
<?php
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
date_default_timezone_set('Europe/Istanbul');
?>
$_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.
<?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());
}
}
?>
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.
<?php
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
<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.
<?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;
}
?>
<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.
<?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.";
}
?>
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).
<?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.
<?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ı.";
}
?>
<?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:
<?php
if ($errors) $msg = implode(" ", $errors);
?>
<ul><li> olarak da gösterebilirsin; kullanıcı için daha okunur olur.