Autentikasi CLI, dengan cara yang benar
(abgeo.dev)- Banyak CLI memakai redirect localhost OAuth sebagai default karena cepat selesai di browser lokal laptop, tetapi asumsi yang sama runtuh di lingkungan pengembangan seperti SSH, container, dan WSL sehingga alur login macet
- Pendekatan saat ini adalah CLI membuka server HTTP sementara di
127.0.0.1, mengirim browser ke URL autentikasi, lalu penyedia autentikasi mengembalikan authorization code ke callback lokal - RFC 8628 Device Authorization Grant yang distandardisasi pada 2019 memisahkan CLI yang meminta token dan perangkat browser tempat pengguna melakukan autentikasi, sehingga tidak bergantung pada port binding atau browser lokal
- Device flow menerima
device_code,user_code,verification_uri, daninterval, lalu melakukan polling berkala ke/tokensambil menangani status standar sepertiauthorization_pending,slow_down,access_denied, danexpired_token - Untuk CLI baru, jadikan device flow sebagai default, temukan endpoint melalui
.well-known/openid-configuration, dan simpan refresh token di OS keychain, bukan di file JSON dalam~/.config
Apa yang diasumsikan oleh redirect localhost
- Login CLI yang umum bekerja di atas asumsi bahwa server HTTP lokal dan browser sistem berada di mesin yang sama
- CLI melakukan bind server HTTP ke port tertentu di
127.0.0.1 - Membuka browser sistem ke OAuth authorization endpoint dan menyertakan
redirect_uri=http://127.0.0.1:<port>/callback - Setelah pengguna login, penyedia autentikasi melakukan redirect
302ke URL loopback dengan authorization code - Server HTTP kecil milik CLI membaca code tersebut lalu menukarnya menjadi token di token endpoint
- Dalam kebanyakan kasus ini dipasangkan dengan PKCE, lalu muncul halaman “Anda boleh menutup tab ini”
- CLI melakukan bind server HTTP ke port tertentu di
gcloud auth login,wrangler login,vercel loginversi lama, dan berbagai CLI vendor memakai pendekatan ini- Wrangler menggunakan port
8976 - gcloud menggunakan
8085 - Claude Code mengambil port sementara setiap kali dijalankan
- Wrangler menggunakan port
- RFC 8252 merekomendasikan pola ini untuk native app ketika browser tersedia, tetapi tidak membahas cara menanganinya saat host tidak memiliki browser
Mengapa pengguna nyaris tidak melihat tahap localhost
- Callback localhost berlangsung sangat singkat sehingga kebanyakan pengguna tidak melihatnya
- URL yang dicetak CLI panjang, dan redirect URI ada di dalam query string
- Pengguna login dan memberi persetujuan di domain asli milik penyedia autentikasi
- Penyedia autentikasi mengirim browser ke callback localhost agar CLI dapat membaca code, lalu memindahkannya lagi ke halaman “signed in” yang lebih rapi
- Dari luar terlihat seperti “login di website lalu CLI terautentikasi”, padahal sebenarnya alur ini ditopang oleh koeksistensi server HTTP lokal dan browser
Titik kegagalan di SSH, container, dan WSL
- Seluruh alur bergantung pada asumsi bahwa mesin tempat CLI berjalan sama dengan mesin tempat browser berjalan
- Dalam sesi SSH, host remote tidak memiliki browser, dan
xdg-openbisa gagal atau membuka browser remote yang tidak terlihat di lingkungan X forwarding- Memang dimungkinkan menyalurkan port callback ke laptop lewat tunnel, tetapi redirect URI yang terdaftar pada penyedia autentikasi harus mengizinkan port yang melewati tunnel itu
- Container tidak memiliki browser, dan banyak image bahkan tidak menyertakan
xdg-openatauopen- Port callback bisa diekspos dengan
-p, tetapi Anda harus tahu port mana yang akan dipilih CLI - Di Cloudflare CLI, ada rangkaian issue dari pengguna yang terblokir oleh masalah ini
- Port callback bisa diekspos dengan
- Di WSL, browser terbuka di Windows sementara loopback server berjalan di Linux
- Port forwarding di WSL2 umumnya berfungsi, tetapi tidak selalu
- Di shared box, proses lain pada mesin yang sama bisa menemukan listening port melalui
/proc/net/tcp, atau berlomba melakukan bind lebih dulu ke port yang sudah dikenal- PKCE melindungi code exchange, tetapi tidak melindungi sesi terautentikasi dari redirect itu sendiri
Fallback justru mengungkap masalah desain
- CLI yang menjadikan alur loopback sebagai default biasanya juga menyediakan fallback untuk saat alur itu rusak
- gcloud memiliki
--no-launch-browser - Wrangler macet, dan workaround yang diterima adalah menjalankan
curllangsung ke URL localhost dari terminal kedua claudedari Anthropic mencetak “Paste code here if prompted” lalu menunggu- Fallback semacam ini pada dasarnya adalah device flow manual, dan ia ada karena alur default memang tidak berjalan di lingkungan tempat CLI benar-benar digunakan
RFC 8628 Device Authorization Grant
- RFC 8628 adalah OAuth 2.0 Device Authorization Grant yang dirilis pada 2019 untuk “input-constrained devices”
- TV, konsol, dan CLI termasuk dalam sasarannya
- Intinya adalah memisahkan perangkat yang meminta token dan perangkat tempat pengguna melakukan autentikasi
- CLI melakukan POST ke
device_authorization_endpointmilik penyedia autentikasi- Contoh request mengirim
client_id=my-cli&scope=openid+offline_access
- Contoh request mengirim
- Penyedia autentikasi mengembalikan JSON berisi nilai berikut
device_codeuser_codeverification_uriverification_uri_completeexpires_ininterval
- CLI mencetak URL dan kode pendek, dan bila memungkinkan juga menampilkan QR untuk
verification_uri_complete - Pengguna membuka URL itu di perangkat mana pun, login, lalu melihat scope yang diminta dan nama client, memastikan cocok dengan kode pendek yang ditampilkan CLI, kemudian menyetujuinya
Polling dan penanganan status standar
- CLI melakukan polling ke token endpoint setiap
intervaldetik - Grant type yang dipakai adalah
urn:ietf:params:oauth:grant-type:device_code - RFC 8628 section 3.5 mendefinisikan status berikut
authorization_pending: sedang menunggu persetujuan penggunaslow_down: penyedia autentikasi meminta interval polling diperlambat, dan spesifikasi menyatakan interval harus ditambah minimal 5 detikaccess_denied: pengguna menolakexpired_token: token kedaluwarsa karena menunggu terlalu lama
- Dalam device flow, CLI tidak melakukan bind port dan tidak mengasumsikan host eksekusi memiliki browser
- Metode login yang sama bekerja di laptop, container, dan job CI yang menunggu persetujuan manusia
Biaya polling dan discovery endpoint
- Interval polling default adalah 5 detik
- Sebagian besar autentikasi selesai dalam waktu kurang dari 1 menit, jadi login normal berhenti setelah sekitar 10 kali polling ke
/token - Server bisa menaikkan interval lewat
slow_down, dan client yang ditulis dengan baik harus mematuhinya - Dibanding menjaga koneksi WebSocket atau SSE ke endpoint stateful untuk setiap login yang tertunda, polling stateless ke
/tokenlebih sederhana dan lebih murah - Jika penyedia autentikasi mendukung OpenID Connect Discovery, CLI bisa mengambil
device_authorization_endpointdantoken_endpointdari.well-known/openid-configurationsehingga tidak perlu hardcode URL
Risiko phishing pada device flow
- Dalam device flow ada serangan di mana penyerang memanggil
device_authorization_endpointmilik penyedia autentikasi yang sah untuk mendapatkanuser_codedandevice_code, lalu mengelabui korban agar memasukkannya - Korban bisa login di URL yang asli, dengan kode yang asli, dan menyetujui consent screen yang asli
- Penyerang lalu melakukan polling ke
/tokenmenggunakandevice_codeyang ia buat sendiri hingga memperoleh access token - Aktor ancaman dari Rusia menjalankan kampanye ini terhadap tenant M365 sejak Agustus 2024
- Microsoft Threat Intelligence melacaknya sebagai Storm-2372
- Volexity mengatribusikannya ke APT29/Midnight Blizzard
- Tenant pemerintah, pertahanan, dan NGO di berbagai benua terdampak
Pertahanan phishing adalah tanggung jawab penyedia autentikasi
- Pertahanan terhadap phishing harus dilakukan di sisi penyedia autentikasi, bukan di CLI
- Mitigasi yang diperlukan antara lain
- waktu kedaluwarsa
user_codeyang singkat - tampilan mencolok untuk nama client dan lokasi permintaan di halaman verifikasi
- rate limiting untuk percobaan memasukkan code
- tidak mengekspos
verification_uri_completeagar korban mengetik kode secara manual alih-alih mengklik tautan - conditional access policy yang memblokir device code flow untuk tenant bernilai tinggi jika bukan dari network atau perangkat yang dikenal
- waktu kedaluwarsa
- Tugas CLI adalah mengikuti spesifikasi dan tidak membuat shortcut
- Device flow mengubah local attack surface menjadi social attack surface, tetapi lebih tepat menyediakan alur yang bekerja di lebih banyak lingkungan dan memanfaatkan mitigasi dari penyedia autentikasi
Inti alur implementasi Go
- Implementasi penuh di Go muat dalam sekitar 30 baris hanya dengan
net/http - Alurnya adalah sebagai berikut
- Memanggil
http.PostFormkeDeviceAuthorizationEndpointdenganclient_iddanscope - Mendekode JSON respons untuk mengambil
DeviceCode,UserCode,VerificationURIComplete, danInterval - Mencetak
VerificationURICompletedanUserCodeuntuk pengguna - Mengirim POST berulang ke
TokenEndpointdengandevice_code,client_id, dan device grant type - Jika
authorization_pending, terus menunggu - Jika
slow_down, tambahkan interval 5 detik - Jika tidak ada error, kembalikan
access_tokendanrefresh_token - Error lainnya diperlakukan sebagai kegagalan
- Memanggil
- Jika Anda mengaktifkan capability “OAuth 2.0 Device Authorization Grant” di realm Keycloak, atau memakai provider bersertifikasi OpenID yang mendukung grant ini, login device-flow akan berjalan
Cara yang seharusnya jadi default untuk CLI baru
- Default harus device flow
- Endpoint harus ditemukan dari
.well-known/openid-configuration, bukan di-hardcode sebagai URL intervaldanslow_downwajib dipatuhi- Refresh token harus disimpan di OS keychain, bukan di file JSON di bawah
~/.config - Jika tetap ingin menyediakan jalur loopback untuk login cepat di laptop, letakkan di balik flag
--web, jangan dijadikan default
CLI yang sudah berpindah dan alat yang masih tertinggal
- Ada CLI yang sudah memakai device flow sebagai default
gh auth loginmemakai device flow sejak awal dan dinilai sebagai reference implementation paling rapi di open sourceaws sso loginmenjalankan device flow end-to-end terhadap IAM Identity Centervercel loginberpindah ke RFC 8628 pada September 2025, menggantikan login berbasis email dan flag--oobsebelumnya- Stripe CLI tidak memakai RFC 8628 secara langsung, tetapi menggunakan pairing-code flow dengan UX yang baik
- Masih ada juga alat yang tetap memakai loopback flow sebagai default dengan fallback paste-the-code
- Google
gcloud - Cloudflare
wrangler - Anthropic
claude
- Google
- Jika setiap kali CLI keluar dari laptop selalu membutuhkan fallback paste-the-code manual, maka fallback itulah yang semestinya disediakan sebagai alur default
1 komentar
Komentar Lobste.rs
Penyampaiannya agak kasar, tapi menarik. Jika kode/perangkat link diganti setiap 1 menit, penyalahgunaan untuk phishing juga sepertinya bisa berkurang
Setelah sekali dipakai, rotasinya bisa dihentikan lalu sesi tersebut diikat ke IP atau browser
Untuk penyedia seperti Microsoft yang membuat pengguna memasukkan kode secara manual, halaman arahan itu juga bisa menampilkan petunjuk dan menyalin kode ke clipboard agar pengguna lebih mudah terjebak phishing
Tulisan yang bagus, dan saya setuju semua orang perlu beralih ke RFC 8628
Karena terlalu sering menjalani alur CLI OAuth di mesin pengembangan jarak jauh, saya membuat alat pribadi yang mencegat
xdg-opendan melakukan port forwarding otomatis untuk menutupi pengalaman pengguna yang buruk: https://github.com/phinze/bankshotMenarik. Baru-baru ini saya kebetulan mengimplementasikan cara autentikasi “lama”, yaitu RFC 8252, dan saya belum tahu soal cara “baru”, RFC 8268
Sepertinya ada celah pengetahuan karena use case utama saya adalah autentikasi server Google. Di dokumen yang saya kira menjelaskan alur RFC 8268, tertulis seperti ini
Batasan scope Google adalah bagian tempat OIDC muncul dengan segala kerumitannya. Idealnya Google mengembalikan ID token alih-alih menumpuknya ke access token, tetapi itu masalah konfigurasi OAuth Google, bukan sifat dari 8628 itu sendiri
Kompleksitas OAuth yang tak ada habisnya berasal dari sini. Standarnya mendefinisikan dengan baik kerangka cara membangun dan mengirimkan skema otorisasi, tetapi sengaja tidak mengatakan apa isi skema itu seharusnya. Bahkan untuk mendapatkan kumpulan endpoint HTTP umum yang disepakati “kebanyakan” penyedia pun butuh penemuan OIDC dan waktu bertahun-tahun
Trik lain adalah meneruskan pemanggilan
xdg-opendari server ke laptop. Saya membuat alat kecil untuk infrastruktur pribadi yang melakukan itu: https://github.com/zimbatm/subportal/Kenapa tidak menggabungkan kedua pendekatan? Redirect ke URL
localhost, kirim balikhello, lalu jika klien tidak menerimahello, CLI menampilkan URL-nyaPada saat yang sama, jika server tidak menerima respons atas
helloyang dikirimnya, browser bisa menampilkan kode dan pesan seperti “pastikan Anda memang sedang mencoba login”. Bisa juga dibuat lebih mudah dengan menampilkan angka yang dipilih di ponsel, seperti yang dilakukan GoogleKeuntungannya adalah bahkan pada kasus 2, orang tetap mudah mengeklik tautan tetapi relatif lebih jarang membagikan OTP/kode, dan penyerang harus terus ikut campur lewat rekayasa sosial sepanjang proses pembajakan
Saat semuanya berjalan baik di mesin lokal, tidak perlu interaksi, jadi idealnya default-nya adalah alur berbasis browser