Belajar PHP - Authentication & Authorization
Series/Belajar PHP/Episode 14
Episode 14 of 23

Belajar PHP - Authentication & Authorization

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.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

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.

Autentikasi: Membuktikan Identitas

Hashing Password yang Benar

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:

Hashing password (PHP murni)
<?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:

AlgoritmaKekuatanKapan
PASSWORD_DEFAULTbcrypt ($2y$), cost 10-12Pilihan default — sudah cukup
PASSWORD_ARGON2IDArgon2id — memori-hardJika tersedia (libsodium), prefer untuk data sangat sensitif
MD5/SHA1/plaintext⛔ tidak amanJangan 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.

Session-Based Auth untuk Web

Alur klasik aplikasi web: login → simpan identitas di session → halaman berikutnya diperiksa:

Login & proteksi halaman
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.

JWT untuk API

Untuk API stateless, JWT (JSON Web Token) membawa identitas dalam token yang ditandatangani:

Membuat & memverifikasi JWT
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:

  • Token berisi klaim minimum (sub, role, exp) — jangan data sensitif.
  • Expiry pendek (menit-jam); untuk sesi panjang pakai refresh token.
  • Kunci rahasia tidak pernah di kode — dari environment variable.
  • Selalu verifikasi signature (yang dilakukan decode di atas).

Authorization: Mengontrol Aksi

Autentikasi menjawab "siapa kalian"; authorization menjawab "apa yang boleh kalian lakukan". Dua pendekatan utama:

Role-Based Access Control (RBAC) dengan Spatie Permission

Spatie Permission adalah package Laravel paling populer untuk RBAC:

Install Spatie Permission
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
RBAC dengan Spatie
use 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");                    // true

Policy untuk Otorisasi Berbasis Object

Ketika hak akses bergantung pada data spesifik (misal hanya pemilik post yang boleh mengeditnya), gunakan Policy:

app/Policies/PostPolicy.php (Laravel)
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");
    }
}
Menerapkan policy di controller
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: Penjaga Gerbang

Middleware berjalan sebelum controller — tempat sempurna untuk meletakkan autentikasi/otorisasi:

Middleware sederhana (Slim)
$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.

Common Pitfalls

  • Password plaintext / hash lemah — MD5/SHA1 mudah di-brute-force; selalu password_hash + password_verify.
  • Lupa session_regenerate_id saat login — rentan session fixation.
  • JWT tanpa expiry — token abadi yang bocor tidak bisa dicabut; selalu set exp singkat.
  • Middleware hanya di sebagian route — pastikan semua endpoint sensitif terproteksi.
  • Cek role di view saja — authorization harus dijalankan di controller/server.

Penutup

Inti yang harus dibawa pulang:

  • Password: password_hash (bcrypt/argon2id) + password_verify — jangan pernah plaintext atau MD5/SHA1.
  • Web: session-based auth dengan session_regenerate_id setelah login.
  • API: JWT dengan klaim minimal, expiry pendek, dan secret dari environment.
  • Otorisasi: RBAC (Spatie Permission) untuk peran-aksi, Policy untuk aturan per-object, semua melalui middleware.

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.

Belajar PHP - Authentication & Authorization | Belajar PHP