Setiap aplikasi yang mengenal pengguna harus menjawab dua pertanyaan yang berbeda: “siapa Anda?” dan “apa yang boleh Anda lakukan?”. Kedua pertanyaan itu sering dianggap satu hal yang sama padahal jawabannya independen — pengguna yang sah tetap bisa melakukan hal yang dilarang, dan penyerang yang identitasnya palsu harus ditolak bahkan sebelum pertanyaan kedua diajukan. Authentication dan authorization adalah dua proses tersebut, dan kekeliruan membedakannya menghasilkan API yang mengembalikan status code salah, log keamanan yang menyesatkan, dan celah akses yang tidak disadari. Pada chapter pembuka section ini kita akan memisahkan keduanya dengan tegas, lalu menerjemahkannya menjadi pemeriksaan akses yang dapat diuji.
Authentication: Membuktikan Identitas #
Authentication adalah proses memverifikasi klaim identitas: pengguna mengaku bernama Budi dengan email [email protected], dan tugas kita adalah membuktikan klaim itu. Pembuktiannya biasanya berupa salah satu dari tiga jenis faktor: sesuatu yang diketahui (kata sandi), sesuatu yang dimiliki (telepon untuk OTP, kunci API), atau sesuatu yang melekat (sidik jari, wajah). Aplikasi modern sering menggabungkan lebih dari satu faktor — itulah multi-factor authentication yang akan kita bahas di akhir section ini.
Hasil authentication yang berhasil bukan sekadar boolean, melainkan sebuah identitas terverifikasi: ID pengguna, perannya, dan atribut lainnya. Identitas inilah yang menjadi dasar seluruh keputusan berikutnya. Karena kata sandi masih menjadi faktor dominan di web, cara menyimpan dan memverifikasinya dengan aman adalah topik pertama yang kita bedah tuntas pada chapter Password Hashing .
Authorization: Menentukan Hak Akses #
Authorization menjawab pertanyaan kedua: setelah tahu siapa penggunanya, operasi apa yang boleh dijalankannya? Keputusan ini selalu membandingkan tiga hal: siapa pelakunya (subjek), apa yang ingin dilakukannya (aksi), dan atas apa (resource). Budi boleh membaca produk, Budi tidak boleh menghapus produk, Siti yang berperan admin boleh menghapus produk — tiga kalimat yang semuanya adalah aturan otorisasi.
Aturan seperti ini paling sering dimodelkan lewat role (RBAC) yang memetakan peran ke daftar permission, meskipun model lain seperti berbasis atribut juga lazim. Pendalaman modelnya ada pada chapter Role-Based Access Control dan Permission Modeling ; fokus kita sekarang adalah memisahkan keputusan otorisasi dari autentikasi secara struktural.
Tampak Sama, Gagal Berbeda: 401 vs 403 #
Kedua proses ini gagal dengan cara yang berbeda, dan HTTP memberi mereka status code yang berbeda. Kegagalan authentication menghasilkan 401 Unauthorized — sebutan resmi HTTP ini memang menyesatkan, karena maknanya sebenarnya “unauthenticated”: klien belum membuktikan identitasnya, atau buktinya tidak dikenal. Kegagalan authorization menghasilkan 403 Forbidden: identitas diketahui dan valid, tetapi aksesnya ditolak:
| Aspek | Authentication | Authorization |
|---|---|---|
| Pertanyaan | Siapa Anda? | Apa yang boleh Anda lakukan? |
| Urutan | Selalu lebih dulu | Hanya setelah identitas diketahui |
| Kegagalan | 401 Unauthorized | 403 Forbidden |
| Respons perbaikan | Ulangi login / kirim kredensial benar | Tidak membantu; akses memang ditolak |
| Contoh | Token kedaluwarsa, kata sandi salah | Editor mencoba hapus produk |
Pembedaan ini penting bagi klien API. Klien yang menerima 401 tahu satu-satunya jalan adalah memperbaiki kredensialnya (misalnya memperbarui token); klien yang menerima 403 justru tidak boleh mencoba ulang dengan kredensial sama, karena masalahnya bukan pada identitas. Log keamanan pun berbeda: rentetan 401 dari satu alamat IP adalah sinyal serangan brute force, sedangkan 403 dari pengguna yang sah bisa jadi sinyal bug di frontend yang menampilkan tombol yang tidak seharusnya.
Alur Permintaan yang Terlindungi #
Dalam satu permintaan API, kedua pemeriksaan berjalan berurutan sebagai gerbang: authentication membangun identitas, authorization memutuskan boleh-tidaknya aksi, baru handler bisnis berjalan. Diagram berikut memetakan alurnya:
Urutan pada diagram tersebut bukan gaya-gayaan: memeriksa otorisasi sebelum autentikasi tidak mungkin dilakukan karena keputusan otorisasi butuh tahu siapa pelakunya, dan menjalankan handler bisnis sebelum keduanya lolos berarti mengeksekusi efek samping untuk permintaan yang akan ditolak. Inilah alasan kedua pemeriksaan hidup sebagai middleware terpisah di hampir semua framework — masing-masing gagal cepat dengan status code yang benar sebelum permintaan menyentuh logika bisnis.
Menerapkan Pemeriksaan Akses #
Mari kita terjemahkan konsepnya menjadi kode yang dapat diuji. Nilai null pada parameter $principal merepresentasikan permintaan tanpa identitas (tamu), sehingga fungsi checkAccess() mengembalikan null untuk “belum terautentikasi”, true untuk diizinkan, dan false untuk ditolak — tiga keadaan yang tidak boleh diperas menjadi boolean dua nilai:
<?php
const ROLE_PERMISSIONS = [
'viewer' => ['product.read'],
'editor' => ['product.read', 'product.update'],
'admin' => ['*'],
];
function checkAccess(?array $principal, string $permission): ?bool
{
if ($principal === null) {
return null;
}
$permissions = ROLE_PERMISSIONS[$principal['role']] ?? [];
return in_array('*', $permissions, true)
|| in_array($permission, $permissions, true);
}
function guard(?array $principal, string $permission): array
{
$akses = checkAccess($principal, $permission);
if ($akses === null) {
return ['status' => 401, 'error' => 'authentication_required'];
}
if ($akses === false) {
return ['status' => 403, 'error' => 'permission_denied'];
}
return ['status' => 200, 'error' => 'ok'];
}
$budi = ['user_id' => 17, 'nama' => 'Budi', 'role' => 'editor'];
$siti = ['user_id' => 2, 'nama' => 'Siti', 'role' => 'admin'];
$kasus = [
[null, 'product.read'],
[$budi, 'product.read'],
[$budi, 'product.delete'],
[$siti, 'product.delete'],
];
foreach ($kasus as [$principal, $permission]) {
$hasil = guard($principal, $permission);
$siapa = $principal === null ? 'tamu' : $principal['nama'];
printf("%-5s %-15s -> HTTP %d %s\n", $siapa, $permission, $hasil['status'], $hasil['error']);
}Output yang Dihasilkan:
tamu product.read -> HTTP 401 authentication_required
Budi product.read -> HTTP 200 ok
Budi product.delete -> HTTP 403 permission_denied
Siti product.delete -> HTTP 200 okMari kita bedah logikanya:
- Konstanta
ROLE_PERMISSIONS: peta role ke daftar permission; tanda*pada admin berarti wildcard yang diizinkan melakukan apa pun. checkAccess(?array $principal, ...): tipe parameter nullable memaksa pemanggil menyatakan secara eksplisit kemungkinan “tidak ada identitas” — keadaan yang di PHP lama sering hilang di dalamifyang tidak sengaja lolos.- Nilai kembalian
?bool:null(belum terautentikasi),false(ditolak), dantrue(diizinkan) adalah tiga keadaan berbeda yang olehguard()diterjemahkan menjadi401,403, dan kelanjutan permintaan. - Fungsi
guard(): menerima principal dan permission, mengembalikan keputusan akhir berupa status code — bentuk yang identik dengan handler murni pada section REST API sehingga mudah diuji.
Output memperlihatkan keempat kombinasi penting: tamu ditolak 401, editor diizinkan membaca, editor ditolak menghapus (403), dan admin lolos lewat wildcard. Perhatikan bahwa kasus pertama dan ketiga menghasilkan status berbeda meskipun keduanya “gagal” — itulah pembedaan yang kita kejar.
Session Auth vs Token Auth #
Ada dua keluarga besar mekanisme authentication di web, dan keduanya menghasilkan principal dengan cara berbeda. Session auth menyimpan keadaan login di server (di session file atau database) sementara klien hanya memegang ID session dalam cookie; token auth menyimpan klaim identitas di dalam token yang ditandatangani (misalnya JWT) sehingga server tidak perlu menyimpan apa pun:
| Aspek | Session Auth | Token Auth |
|---|---|---|
| Bukti identitas | Cookie berisi ID session | Header Authorization: Bearer <token> |
| Keadaan di server | Ada (stateful) | Tidak ada (stateless) |
| Cocok untuk | Aplikasi web klasik (browser) | API untuk mobile, SPA, layanan lain |
| Pencabutan akses | Mudah: hapus session di server | Sulit: butuh denylist atau token berumur pendek |
| Kekhawatiran khas | CSRF, session fixation | Token dicuri, klaim basi |
Pilihan di antara keduanya akan kita bangun lengkap pada chapter Session-Based Authentication dan API Token Authentication ; yang penting sekarang adalah menyadari bahwa keduanya hanya cara berbeda untuk tiba di titik yang sama — sebuah principal terverifikasi yang siap diperiksa otorisasinya.
Principal dan Identity #
Dalam literatur keamanan, entitas yang identitasnya telah diverifikasi disebut principal: pengguna Budi, akun service backend, bahkan proses cron yang membawa kredensialnya sendiri. Identity adalah kumpulan klaim tentang principal tersebut — ID numerik, alamat email, role, tanggal verifikasi email. Istilah ini penting karena keputusan authorization tidak harus selalu bertumpu pada “siapa namanya”: kadang klaim tambahan seperti “email sudah diverifikasi” atau “token diterbitkan kurang dari 15 menit lalu” yang menentukan boleh-tidaknya sebuah aksi.
Merangkum identitas dalam satu nilai principal yang diteruskan dari middleware ke handler juga menyehatkan arsitektur. Handler tidak perlu tahu identitas itu berasal dari session, token, atau CLI; handler hanya menerima principal dan membuat keputusan berdasarkan klaimnya. Inilah yang membuat fungsi guard() di atas bisa diuji tanpa session maupun token sungguhan — kita cukup menulis array principal tiruan.
Defense in Depth #
Prinsip terakhir yang membingkai seluruh section ini adalah defense in depth: jangan mempertaruhkan keamanan pada satu tembok tunggal. Authentication yang kuat tidak menggantikan authorization yang ketat; otorisasi di middleware tidak menggantikan pemeriksaan di query (“apakah pesanan ini memang milik pengguna ini?”); dan semuanya tidak menggantikan praktik keamanan dasar seperti HTTPS, rate limiting pada endpoint login, dan logging yang mencatat keputusan akses. Setiap lapisan mengasumsikan lapisan sebelumnya bisa gagal.
Lapisan-lapisan itu juga saling melengkapi secara temporal: rate limiting menahan kecepatan serangan, hashing kata sandi menahan kebocoran database, otorisasi membatasi ledakan akses bila satu akun jatuh, dan logging memberi kita kesadaran untuk merespons. Karena itulah section Web Security membahas topik-topik tersebut sebagai disiplin tersendiri setelah fondasi autentikasi kita berdiri.
Referensi #
- HTTP authentication with PHP : mekanisme autentikasi HTTP bawaan PHP.
- Security guide — dokumentasi resmi PHP : peta topik keamanan dari maintainer PHP.
Ringkasan #
Authentication membuktikan siapa penggunanya dan menghasilkan principal dengan klaim identitas; authorization memutuskan apa yang boleh dilakukan principal tersebut atas sebuah resource. Keduanya gagal dengan status berbeda — 401 untuk identitas yang tidak dikenal, 403 untuk akses yang ditolak — dan berjalan berurutan sebagai gerbang sebelum logika bisnis. Mekanisme pembuktian identitas bermacam-macam, mulai dari session di server hingga token stateless, tetapi semuanya bermuara pada pertanyaan pertama yang sama. Kita mulai pembangunannya dari bahan paling sensitif: cara menyimpan kata sandi dengan aman melalui password hashing pada chapter berikutnya.