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:
- Cookie: Sepotong data kecil (maksimal ~4 KB) yang dikirim oleh server melalui header HTTP
Set-Cookieuntuk disimpan di peramban pengguna, lalu otomatis dikirimkan kembali oleh peramban melalui headerCookiepada setiap permintaan berikutnya ke domain tersebut. - Session (
$_SESSION): Data pengguna (sepertiuser_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 (misalnyaPHPSESSID=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 Cookie | Apa yang Dilindungi? | Rekomendasi Produksi |
|---|---|---|
httponly => true | Melarang 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 => true | Memastikan 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:
- Penyerang mengunjungi situs Anda dan mendapatkan ID sesi kosong
PHPSESSID=12345. - Penyerang menjebak korban agar menggunakan ID sesi
12345tersebut, lalu korban melakukan login dengan nama pengguna dan kata sandinya. - Jika saat korban berhasil login aplikasi Anda tidak mengganti ID sesinya, maka ID sesi
12345yang 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:
- Buat
final class SesiManajerdengan metodemulai(),login(int $userId),logout(), danapakahLogin(): bool. - Tambahkan fitur Flash Message (
setFlash(string $kunci, string $pesan)danambilFlash(string $kunci): ?string) yang menyimpan pesan notifikasi sekali pakai di$_SESSION['_flash'][$kunci]lalu langsung menghapusnya denganunset()begitu pesan tersebut selesai dibaca satu kali!