Kodeloka
  1. Kodeloka
  2. Kelas PHP
  3. Materi

3 · Web & Keamanan Dasar · Modul 14 dari 25

Manajemen State: Cookie yang Aman, Session, dan Pencegahan Session Fixation

Mengelola identitas dan state pengguna di atas protokol HTTP yang stateless menggunakan setcookie() dengan atribut keamanan (HttpOnly, Secure, SameSite, partitioned), mengonfigurasi $_SESSION, serta mencegah serangan Session Fixation dengan session_regenerate_id(true).

Mengapa HTTP Membutuhkan Cookie dan Session?

Protokol HTTP pada dasarnya bersifat stateless (tanpa ingatan): setelah server selesai mengirim balasan untuk halaman login Anda, server langsung melupakan siapa Anda. Ketika 2 detik kemudian Anda mengeklik tautan menuju halaman /dasbor, server membutuhkan cara untuk mengenali bahwa permintaan kedua ini masih berasal dari pengguna yang baru saja berhasil login.

Di sinilah Cookie dan Session bekerja sama:

  1. Cookie: Sepotong data kecil (maksimal ~4 KB) yang dikirim oleh server melalui header HTTP Set-Cookie untuk disimpan di peramban pengguna, lalu otomatis dikirimkan kembali oleh peramban melalui header Cookie pada setiap permintaan berikutnya ke domain tersebut.
  2. Session ($_SESSION): Data pengguna (seperti user_id, peran, atau pesan kilat/flash message) disimpan dengan aman di sisi server (di berkas server atau Redis), sementara peramban pengguna cukup menyimpan satu Cookie ID Sesi acak (misalnya PHPSESSID=a8f9...) sebagai kuncinya!

1. Mengirim Cookie yang Aman dengan setcookie()

Sejak PHP 7.3+, fungsi setcookie() mendukung argumen ke-3 berupa array asosiatif opsi yang jauh lebih mudah dibaca daripada menghafal 7 urutan parameter lama. Sejak PHP 8.5, array opsi ini juga mendukung kunci 'partitioned' untuk CHIPS (Cookies Having Independent Partitioned State)!

<?php

declare(strict_types=1);

// Mengatur cookie preferensi tampilan selama 30 hari dengan opsi keamanan lengkap:
setcookie(
    name: 'preferensi_bahasa',
    value: 'id',
    options: [
        'expires'  => time() + (30 * 86400), // Kedaluwarsa dalam 30 hari
        'path'     => '/',                   // Berlaku di seluruh jalur situs
        'domain'   => '',                    // Hanya untuk host domain saat ini (HostOnly)
        'secure'   => true,                  // WAJIB: Hanya dikirim lewat koneksi terenkripsi HTTPS
        'httponly' => true,                  // WAJIB: Tidak bisa dibaca oleh document.cookie di JavaScript (anti-pencurian XSS)
        'samesite' => 'Lax',                 // Melindungi dari pengiriman lintas situs (CSRF)
    ],
);

Memahami Tiga Pilar Pengaman Cookie: HttpOnly, Secure, dan SameSite

Atribut CookieApa yang Dilindungi?Rekomendasi Produksi
httponly => trueMelarang JavaScript (document.cookie) membaca cookie tersebut. Jika terjadi celah XSS, skrip penyerang tidak dapat menyalin nilai cookie sesi Anda.Wajib true untuk semua cookie sesi dan token autentikasi!
secure => trueMemastikan peramban hanya mengirimkan cookie melalui sambungan terenkripsi HTTPS, tidak pernah lewat HTTP polos yang bisa disadap di jaringan Wi-Fi umum.Wajib true di lingkungan produksi.
samesite => 'Lax' atau 'Strict'Mengatur apakah cookie ikut dikirim pada permintaan lintas situs (cross-site request). 'Lax' mengizinkan cookie saat pengguna mengeklik tautan biasa dari luar menuju situs Anda, tetapi menahan cookie pada pengiriman formulir POST dari situs luar!Gunakan 'Lax' (atau 'Strict' untuk aplikasi perbankan/admin sensitif).

2. Mengelola Session ($_SESSION) dengan Benar

Sebelum memanggil session_start(), selalu pastikan parameter cookie sesi dikonfigurasi secara ketat:

<?php

declare(strict_types=1);

function mulaiSesiAman(): void
{
    if (session_status() === PHP_SESSION_ACTIVE) {
        return;
    }

    // Aktifkan strict mode agar PHP menolak ID sesi yang tidak dibuat oleh server kita sendiri!
    ini_set('session.use_strict_mode', '1');
    ini_set('session.use_only_cookies', '1');

    session_set_cookie_params([
        'lifetime' => 0,       // Cookie sesi berakhir saat peramban ditutup
        'path'     => '/',
        'secure'   => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
        'httponly' => true,
        'samesite' => 'Lax',
    ]);

    session_start();
}

3. Mencegah Serangan Session Fixation dengan session_regenerate_id(true)

Bayangkan skenario serangan Session Fixation berikut:

  1. Penyerang mengunjungi situs Anda dan mendapatkan ID sesi kosong PHPSESSID=12345.
  2. Penyerang menjebak korban agar menggunakan ID sesi 12345 tersebut, lalu korban melakukan login dengan nama pengguna dan kata sandinya.
  3. Jika saat korban berhasil login aplikasi Anda tidak mengganti ID sesinya, maka ID sesi 12345 yang sudah diketahui penyerang tadi kini berubah menjadi sesi milik korban yang sudah terautentikasi!

Aturan Keamanan Wajib: Setiap kali terjadi perubahan tingkat hak akses—terutama tepat setelah pengguna berhasil login atau setelah pengguna mengubah kata sandinya—Anda wajib memanggil session_regenerate_id(true) untuk menerbitkan ID sesi baru dan menghapus berkas sesi lama!

<?php

declare(strict_types=1);

function prosesLoginBerhasil(int $idPengguna, string $peran): void
{
    mulaiSesiAman();

    // WAJIB: Buat ID sesi baru yang acak dan hapus sesi lama (argumen true)
    // untuk mencegah serangan Session Fixation!
    session_regenerate_id(delete_old_session: true);

    $_SESSION['user_id'] = $idPengguna;
    $_SESSION['peran'] = $peran;
    $_SESSION['waktu_login'] = time();
}

4. Cara Menghancurkan Sesi dengan Bersih Saat Logout

Untuk mengeluarkan pengguna (logout) secara tuntas, jangan hanya memanggil session_destroy()! Mengapa? Karena session_destroy() hanya menghapus data sesi di diska server, sementara variabel $_SESSION di memori skrip saat ini masih terisi dan cookie di peramban pengguna belum dihapus.

Ikuti urutan lengkap resmi dari PHP Manual:

<?php

declare(strict_types=1);

function prosesLogout(): void
{
    mulaiSesiAman();

    // 1. Kosongkan seluruh isi array $_SESSION di memori
    $_SESSION = [];

    // 2. Hapus cookie sesi di peramban pengguna dengan mengatur waktu kedaluwarsa di masa lalu
    if (ini_get('session.use_cookies')) {
        $params = session_get_cookie_params();
        setcookie(
            name: session_name(),
            value: '',
            options: [
                'expires'  => time() - 42000,
                'path'     => $params['path'],
                'domain'   => $params['domain'],
                'secure'   => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'] ?: 'Lax',
            ],
        );
    }

    // 3. Hancurkan penyimpanan sesi di sisi server
    session_destroy();
}

Latihan Praktik: Membuat Manajer Sesi dan Flash Message

Buatlah berkas SesiManajer.php:

  1. Buat final class SesiManajer dengan metode mulai(), login(int $userId), logout(), dan apakahLogin(): bool.
  2. Tambahkan fitur Flash Message (setFlash(string $kunci, string $pesan) dan ambilFlash(string $kunci): ?string) yang menyimpan pesan notifikasi sekali pakai di $_SESSION['_flash'][$kunci] lalu langsung menghapusnya dengan unset() begitu pesan tersebut selesai dibaca satu kali!