Tombol “Login dengan Google” atau “Masuk dengan GitHub” ada hampir di semua aplikasi modern. Di balik tombol itu, aplikasi kita tidak pernah melihat password pengguna — aplikasi justru meminjam kepercayaan dari penyedia akun tersebut melalui protokol bernama OAuth2, lalu (jika dipakai untuk login) lapisan identitas bernama OpenID Connect. Berbeda dengan chapter-chapter sebelumnya yang membangun kode aktif, chapter ini adalah chapter konsep: kita membedah peran-perannya, mengikuti alur authorization code dari klik pertama sampai session terbentuk, dan memahami pertahanan seperti parameter state dan PKCE. Kode yang menyertai adalah simulasi bagian-bagian yang bisa diuji tanpa server penyedia sungguhan.
Masalah yang Dipecahkan OAuth2 #
OAuth2 lahir dari masalah delegasi akses: bagaimana memberi aplikasi pihak ketiga akses terbatas ke data kita tanpa memberikan password? Dengan membagi password, aplikasi tersebut mendapat akses penuh — membaca email, mengubah password, menghapus akun — dan tidak ada cara mencabutnya kecuali mengganti password. OAuth2 mengganti pembagian password dengan pemberian token berizin sempit: aplikasi hanya menerima akses ke scope yang kita setujui, untuk waktu terbatas, dan bisa dicabut dari halaman pengaturan penyedia kapan saja.
Perhatikan bahwa OAuth2 aslinya menyelesaikan delegasi akses (“izinkan aplikasi ini membaca kontak saya”), bukan login. Kebutuhan “tombol login dengan Google” ditangani oleh OpenID Connect yang dibangun di atas OAuth2 — kita bahas pemisahan ini di bagian akhir chapter.
Empat Peran dalam OAuth2 #
Spesifikasi OAuth2 mendefinisikan empat peran. Mengenali keempatnya membuat seluruh alur berikutnya mudah diikuti:
| Peran | Deskripsi | Contoh |
|---|---|---|
| Resource owner | Pemilik data yang berhak memberi izin | Budi, pemilik akun Google |
| Client | Aplikasi yang meminta akses | Aplikasi PHP kita |
| Authorization server | Mengautentikasi pemilik data dan menerbitkan token | Layanan login Google |
| Resource server | API yang menyimpan data terlindungi | Gmail API |
Authorization server dan resource server sering dikelola satu perusahaan (dan kadang satu perangkat lunak), tetapi secara konsep keduanya terpisah: yang satu mengurus izin, yang lain mengurus data. Client kita terdaftar di authorization server dengan client_id publik dan client_secret rahasia, ditambah satu atau lebih redirect_uri yang sudah didaftarkan di muka.
Alur Authorization Code #
Alur yang dipakai hampir semua aplikasi web server-side adalah authorization code flow. Diagram berikut merangkum perjalanan lengkapnya:
Tahapannya dari sisi aplikasi PHP kita:
- Budi mengeklik tombol login; kita meredirect browsernya ke authorization server dengan parameter
client_id,redirect_uri,scope,state, danresponse_type=code. - Budi login di penyedia (jika belum) dan melihat halaman persetujuan yang menjelaskan scope yang diminta.
- Authorization server meredirect kembali ke
redirect_urikita dengan kode sekali pakai (code) dan parameterstateyang sama seperti yang kita kirim. - Handler callback memverifikasi
state, lalu menukarcodedengan token lewat permintaan POST dari server ke server — ini satu-satunya momenclient_secretdipakai. - Authorization server mengirim balasan berisi
access_token(dan biasanyarefresh_token). - Kita memakai access token pada header
Authorization: Beareruntuk memanggil resource server, lalu membuka session aplikasi kita sendiri untuk Budi.
Pemisahan langkah 3 dan 4 adalah kunci keamanannya: token tidak pernah melewati browser sebagai parameter URL yang bisa tersimpan di history, melainkan ditukar di backchannel antara dua server.
Redirect URI dan Parameter state #
redirect_uri adalah pertahanan pertama: authorization server hanya mau mengirim code ke alamat yang sudah didaftarkan, sehingga penyerang tidak bisa mengalihkan kode ke server miliknya. Pertahanan kedua adalah parameter state. Penyerang bisa menautkan Budi ke URL callback berisi code milik akun penyerang sendiri — tanpa state, login Budi akan membawanya ke akun penyerang (serangan login CSRF). Nilai state acak yang dibuat per permintaan dan dibandingkan saat callback menutup celah ini:
<?php
function createState(): string
{
return bin2hex(random_bytes(16));
}
function stateMatches(string $returnedState, ?string $storedState): bool
{
return $storedState !== null && hash_equals($storedState, $returnedState);
}
// Saat halaman login dirender: state dibuat dan disimpan di session,
// lalu disertakan sebagai parameter pada URL redirect.
$storedState = createState();
// Saat callback tiba: nilai state pada URL dibandingkan dengan session.
var_dump(stateMatches($storedState, $storedState));
var_dump(stateMatches("state-palsu-dari-penyerang", $storedState));
var_dump(stateMatches($storedState, null));Output yang Dihasilkan:
bool(true)
bool(false)
bool(false)Fungsi stateMatches() memeriksa dua hal: state tersimpan harus ada (callback tanpa state aktif ditolak), dan perbandingannya memakai hash_equals() agar waktu eksekusi tidak membocorkan apakah tebakannya mendekati benar. Jika state yang kembali tidak cocok dengan yang ada di session, handler callback wajib menghentikan proses — tidak ada pengecualian yang layak dibuat.
Scope dan Persetujuan Pengguna #
Parameter scope menentukan apa yang bisa dilakukan token hasilnya. Nilainya berupa daftar dipisah spasi, dan tiap penyedia mendefinisikan kumpulan scope sendiri. URL redirect ke authorization server kira-kira berbentuk:
https://accounts.provider.example/authorize?
client_id=CLIENT_ID_ANDA
&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
&response_type=code
&scope=openid%20email%20profile
&state=<nilai-acak-per-request>Halaman persetujuan menampilkan scope dalam bahasa manusia — “melihat alamat email Anda”, “melihat profil dasar Anda” — sehingga Budi tahu persis apa yang disetujuinya. Prinsip least privilege berlaku di sini: minta scope paling sedikit yang masih membuat fitur berfungsi, karena setiap scope tambahan memperluas kerusakan bila token bocor. Aplikasi yang meminta semuanya “untuk jaga-jaga” mengajari pengguna untuk mengklik setuju tanpa membaca.
Access Token dan Refresh Token #
Balasan dari token endpoint biasanya memuat dua jenis token dengan pembagian tugas yang tegas:
| Aspek | Access Token | Refresh Token |
|---|---|---|
| Umur | Pendek — menit hingga beberapa jam | Panjang — hari hingga minggu |
| Fungsi | Memanggil resource server | Menukar access token baru |
| Dikirim ke | Resource server lewat header Bearer | Hanya ke authorization server |
| Bila bocor | Jendela bahaya sempit karena cepat mati | Harus segera dicabut dari penyedia |
Access token sengaja dibuat berumur pendek: bila bocor, penyerang hanya punya jendela kecil sebelum token mati sendiri. Refresh token bertugas memperbarui access token tanpa memaksa Budi login lagi — tinggal ditukar di token endpoint dari server ke server. Simpan refresh token di penyimpanan aman di server (bukan localStorage yang rentan XSS), dan gunakan endpoint pencabutan penyedia saat user keluar dari aplikasi kita.
PKCE untuk Klien Publik #
Aplikasi web server-side bisa menyimpan client_secret aman di server. Aplikasi SPA atau mobile tidak bisa — semua kode mereka hidup di perangkat pengguna. Alur implicit yang dulu dipakai untuk kasus ini kini tidak dianjurkan karena token lewat langsung di URL fragment browser, lengkap dengan risiko history dan kebocoran referer. Penggantinya adalah PKCE (Proof Key for Code Exchange): klien membuat verifier acak, mengirim hash-nya sebagai challenge saat meminta kode, lalu membuktikan kepemilikan verifier saat menukar kode:
<?php
function createCodeChallenge(string $verifier): string
{
$digest = hash("sha256", $verifier, true); // 32 byte mentah, bukan heksadesimal
$base64url = rtrim(strtr(base64_encode($digest), "+/", "-_"), "=");
return $base64url;
}
// Produksi: verifier berupa string acak 43-128 karakter.
// Demo memakai verifier tetap agar output contoh ini deterministik.
var_dump(createCodeChallenge("verifier-demo-tetap-untuk-buku"));Output yang Dihasilkan:
string(43) "wZGTqrhia7OPzaXzsRkEbDuk0XxPdi9SLJ4nD8621q8"Fungsi createCodeChallenge() menghash verifier dengan SHA-256 dalam mode raw output (true pada argumen ketiga menghasilkan 32 byte biner, bukan 64 karakter heksadesimal), lalu mengencode-nya sebagai base64url — base64 standar dengan + diganti -, / diganti _, dan padding = dibuang agar aman diletakkan di URL. Authorization server menyimpan challenge; saat menukar code, klien mengirim verifier asli dan server memverifikasi hash-nya cocok. Penyerang yang mencegat code di redirect tidak bisa menukarnya karena tidak tahu verifier. Praktik terbaik saat ini: pakai PKCE untuk semua client, termasuk aplikasi server-side yang punya secret.
OpenID Connect sebagai Lapisan Identitas #
OAuth2 murni tidak mendefinisikan cara mengetahui “siapa yang baru saja login”. Untuk itu ada OpenID Connect (OIDC): sekumpulan konvensi di atas OAuth2 yang menambahkan scope openid dan balasan berupa ID token — token berformat JWT yang memuat klaim identitas seperti sub (identifier unik user di penyedia), email, dan name. Endpoint userinfo menyediakan cara lain mengambil profil yang sama dengan access token. Jadi ketika tombol “Login dengan Google” dipasang, yang terjadi adalah alur OIDC: authorization code flow plus scope openid, diakhiri dengan membaca ID token untuk mengenali user dan membuka session.
Integrasi di Aplikasi PHP Nyata #
Menulis alur OAuth2 dari nol tergoda secara edukatif, tetapi untuk produksi gunakan pustaka yang sudah teruji: paket league/oauth2-client mendukung penyedia generik maupun adapter khusus (Google, GitHub, dan lainnya) dan menangani detail seperti pertukaran kode dan validasi token. Di ekosistem Laravel, Socialite menyediakan abstraksi serupa dengan konfigurasi beberapa baris. Tugas yang tetap di tangan kita: mendaftarkan redirect_uri yang tepat, menjaga client_secret di luar repositori (misalnya lewat environment variable), memverifikasi state di callback, memakai PKCE, dan mengaitkan akun penyedia ke user lokal lewat kolom provider + provider_user_id yang unik.
Referensi #
Dokumentasi resmi untuk pendalaman lebih lanjut:
- OAuth 2.0 — oauth.net
- How OpenID Connect Works — openid.net
- OAuth 2.0 Cheat Sheet — OWASP
hash_equals— php.net
Ringkasan #
OAuth2 memindahkan pemberian izin dari tangan password ke tangan token: client meminta kode lewat browser, menukarnya lewat backchannel, dan memakai access token berumur pendek yang diperbarui refresh token. Parameter state menjaga callback dari CSRF, PKCE mengamankan klien yang tidak bisa menyimpan secret, dan OpenID Connect menambahkan ID token sehingga alur yang sama bisa dipakai untuk login. Dengan ini rantai autentikasi kita sudah menjangkau penyedia pihak ketiga. Lapisan berikutnya mengamankan pintu masuk itu sendiri: sesuatu yang Budi punya di luar password — dibahas pada chapter Multi-Factor Authentication
.