Mengamankan aplikasi PHP: hashing password dengan bcrypt dan argon2id melalui password_hash, session-based auth untuk aplikasi web, JWT untuk API, lalu authorization berbasis role dan permission dengan Spatie Permission atau Laravel policy, plus middleware sebagai penjaga gerbang.

Di episode 13 kita membangun API yang bisa menerima data. Sekarang pertanyaannya: siapa pengguna ini, dan apa saja yang boleh dia lakukan? Inilah dua pertanyaan yang dijawab oleh authentication (membuktikan identitas) dan authorization (mengizinkan aksi). Tanpa keduanya, siapa pun bisa mengakses data siapa pun — dan itu akhir dari kepercayaan.
Mengapa penting? Dua kegagalan keamanan paling sering di dunia nyata adalah password yang disimpan tidak aman dan kontrol akses yang lemah. Episode ini mengajarkan praktik modern: password_hash yang benar, session yang di-hardening untuk web, JWT untuk API, dan lapisan authorization berbasis role/permission. Kita bangun semua di atas kode PHP murni dulu, lalu di-velapkan dengan pola framework.
Aturan nomor satu: jangan pernah menyimpan password plaintext atau hash MD5/SHA1 (mudah di-brute-force). Gunakan password_hash yang menerapkan algoritma modern dan salt acak otomatis:
<?php
declare(strict_types=1);
$password = "super-rahasia";
// Default PHP 8.4+: bcrypt; opsi lain PASSWORD_ARGON2ID
$hash = password_hash($password, PASSWORD_DEFAULT);
var_dump($hash); // $2y$10$...salt...hash...
// Verifikasi
if (password_verify($password, $hash)) {
echo "Login berhasil\n";
}Bandingkan algoritma:
| Algoritma | Kekuatan | Kapan |
|---|---|---|
PASSWORD_DEFAULT | bcrypt ($2y$), cost 10-12 | Pilihan default — sudah cukup |
PASSWORD_ARGON2ID | Argon2id — memori-hard | Jika tersedia (libsodium), prefer untuk data sangat sensitif |
| MD5/SHA1/plaintext | ⛔ tidak aman | Jangan pernah |
Perhatikan: password_hash menghasilkan hash yang berbeda setiap kali (karena salt acak) — jadi perbandingan harus lewat password_verify, bukan ==. Dan jangan menambah salt sendiri — fungsi ini sudah menangani salt dengan benar.
Alur klasik aplikasi web: login → simpan identitas di session → halaman berikutnya diperiksa:
session_start();
// saat login sukses
$_SESSION["user_id"] = $user["id"];
$_SESSION["role"] = $user["role"];
session_regenerate_id(true); // cegah session fixation
// di halaman terproteksi
if (empty($_SESSION["user_id"])) {
header("Location: /login.php", true, 302);
exit;
}session_regenerate_id() mengganti ID session setelah login — mencegah penyerang "meminjam" session yang belum di-autentikasi. Di Laravel, semua ini di-encapsulate oleh Auth::login() dan middleware auth.
Untuk API stateless, JWT (JSON Web Token) membawa identitas dalam token yang ditandatangani:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secret = getenv("APP_KEY"); // kunci rahasia dari env
// membuat token saat login
$payload = [
"sub" => $user["id"],
"role" => $user["role"],
"iat" => time(),
"exp" => time() + 3600, // 1 jam
];
$token = JWT::encode($payload, $secret, "HS256");
// memverifikasi token di request masuk
try {
$decoded = JWT::decode($token, new Key($secret, "HS256"));
$userId = (int) $decoded->sub;
} catch (Throwable $e) {
http_response_code(401);
exit("Token tidak valid atau kedaluwarsa.");
}Praktik JWT yang benar:
decode di atas).Autentikasi menjawab "siapa kalian"; authorization menjawab "apa yang boleh kalian lakukan". Dua pendekatan utama:
Spatie Permission adalah package Laravel paling populer untuk RBAC:
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrateuse App\Models\User;
$admin = User::find(1);
$admin->assignRole("admin");
$editor = User::find(2);
$editor->givePermissionTo("edit-posts");
// cek di mana pun
$admin->hasRole("admin"); // true
$admin->hasPermissionTo("delete-anything"); // via role
$editor->can("edit-posts"); // trueKetika hak akses bergantung pada data spesifik (misal hanya pemilik post yang boleh mengeditnya), gunakan Policy:
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
class PostPolicy
{
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->hasRole("admin");
}
}public function update(Request $request, Post $post)
{
$this->authorize("update", $post); // 403 otomatis jika tidak boleh
// ... logika update
}Rule of thumb: RBAC untuk mengatur "peran apa yang bisa aksi apa"; Policy untuk aturan detail per-object. Kombinasi keduanya menghasilkan kontrol akses yang ekspresif.
Middleware berjalan sebelum controller — tempat sempurna untuk meletakkan autentikasi/otorisasi:
$app->add(function (Request $req, RequestHandler $handler): Response {
$auth = $req->getHeaderLine("Authorization");
if (!str_starts_with($auth, "Bearer ")) {
$res = new Response(401);
$res->getBody()->write(json_encode(["error" => "Belum login"]));
return $res;
}
$token = substr($auth, 7);
try {
$req = $req->withAttribute("user", JWT::decode($token, new Key($secret, "HS256")));
} catch (Throwable $e) {
$res = new Response(401);
$res->getBody()->write(json_encode(["error" => "Token tidak valid"]));
return $res;
}
return $handler->handle($req);
});Di Laravel, hal yang sama menjadi Route::middleware("auth") atau auth:sanctum (untuk API) — satu baris yang melindungi seluruh grup route.
Warning
Otorisasi harus selalu di sisi server, tidak hanya menyembunyikan tombol di UI. Cek hak akses di controller/policy setiap kali — bukan hanya di tampilan. Menyembunyikan tombol tanpa cek di server sama dengan pintu yang terlihat terkunci tapi sebenarnya terbuka.
password_hash + password_verify.session_regenerate_id saat login — rentan session fixation.exp singkat.Inti yang harus dibawa pulang:
password_hash (bcrypt/argon2id) + password_verify — jangan pernah plaintext atau MD5/SHA1.session_regenerate_id setelah login.Di episode 15 selanjutnya kita kembali ke tampilan: Templating & Views — Blade (Laravel) dan Twig (Symfony), mulai dari layout, component, partial, hingga escaping otomatis yang menjadi pertahanan pertama terhadap XSS. Kalian akan melihat bagaimana PHP modern merender UI tanpa mencampur logika dan HTML.