1 poin oleh GN⁺ 4 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • 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, dan interval, lalu melakukan polling berkala ke /token sambil menangani status standar seperti authorization_pending, slow_down, access_denied, dan expired_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 302 ke 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”
  • gcloud auth login, wrangler login, vercel login versi lama, dan berbagai CLI vendor memakai pendekatan ini
    • Wrangler menggunakan port 8976
    • gcloud menggunakan 8085
    • Claude Code mengambil port sementara setiap kali dijalankan
  • 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-open bisa 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-open atau open
    • 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
  • 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 curl langsung ke URL localhost dari terminal kedua
  • claude dari 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_endpoint milik penyedia autentikasi
    • Contoh request mengirim client_id=my-cli&scope=openid+offline_access
  • Penyedia autentikasi mengembalikan JSON berisi nilai berikut
    • device_code
    • user_code
    • verification_uri
    • verification_uri_complete
    • expires_in
    • interval
  • 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 interval detik
  • 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 pengguna
    • slow_down: penyedia autentikasi meminta interval polling diperlambat, dan spesifikasi menyatakan interval harus ditambah minimal 5 detik
    • access_denied: pengguna menolak
    • expired_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 /token lebih sederhana dan lebih murah
  • Jika penyedia autentikasi mendukung OpenID Connect Discovery, CLI bisa mengambil device_authorization_endpoint dan token_endpoint dari .well-known/openid-configuration sehingga tidak perlu hardcode URL

Risiko phishing pada device flow

  • Dalam device flow ada serangan di mana penyerang memanggil device_authorization_endpoint milik penyedia autentikasi yang sah untuk mendapatkan user_code dan device_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 /token menggunakan device_code yang 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_code yang singkat
    • tampilan mencolok untuk nama client dan lokasi permintaan di halaman verifikasi
    • rate limiting untuk percobaan memasukkan code
    • tidak mengekspos verification_uri_complete agar 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
  • 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.PostForm ke DeviceAuthorizationEndpoint dengan client_id dan scope
    • Mendekode JSON respons untuk mengambil DeviceCode, UserCode, VerificationURIComplete, dan Interval
    • Mencetak VerificationURIComplete dan UserCode untuk pengguna
    • Mengirim POST berulang ke TokenEndpoint dengan device_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_token dan refresh_token
    • Error lainnya diperlakukan sebagai kegagalan
  • 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
  • interval dan slow_down wajib 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 login memakai device flow sejak awal dan dinilai sebagai reference implementation paling rapi di open source
    • aws sso login menjalankan device flow end-to-end terhadap IAM Identity Center
    • vercel login berpindah ke RFC 8628 pada September 2025, menggantikan login berbasis email dan flag --oob sebelumnya
    • 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
  • 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

 
GN⁺ 4 jam lalu
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

    • Pendekatan ini tidak terlalu banyak membantu sebesar yang diklaim tulisan itu. Cukup mudah membuat halaman arahan phishing yang memulai alur saat pengguna masuk lalu langsung mengalihkan ke penyedia yang sah
      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-open dan melakukan port forwarding otomatis untuk menutupi pengalaman pengguna yang buruk: https://github.com/phinze/bankshot

  • Menarik. 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

    Alternatives

    If you are writing an app for a platform such as Android, iOS, macOS, Linux, or Windows (including the Universal Windows Platform), that has access to the browser and full input capabilities, use the OAuth 2.0 flow for mobile and desktop applications. (You should use that flow even if your app is a command-line tool without a graphical interface.)
    Jadi saya membacanya apa adanya dan mengimplementasikan hanya alur RFC 8252. Alat saya memang CLI, tetapi karena use case-nya hanya lokal, saya tidak mempertimbangkan lingkungan SSH atau container
    Selain itu, dalam alur RFC 8268 Google hanya mengizinkan cakupan OAuth 2.0 terbatas, jadi untuk beberapa aplikasi ini bisa menjadi batasan yang menentukan

    • Koreksi kecil: setelah saya cek lagi nomor dokumen aslinya, ternyata RFC 8628
      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-open dari 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 balik hello, lalu jika klien tidak menerima hello, CLI menampilkan URL-nya
    Pada saat yang sama, jika server tidak menerima respons atas hello yang 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 Google

    cli -> server/auth?r=localhost&fallback_choices=10,20,30  
    server -> localhost/hello
    
    Case 1: hello request received, go to redirect URI on localhost  
    Case 2: server has not received a hello reply, client has not received a hello request
    - CLI displays a/the webpage url and prompts for selecting a fallback_choice
    - Webpage displays a number say `20` from choices
      - Warn in the webpage not to share this code
    - User enters/selects it on the CLI
      - solves the token copy/paste problem if choices  
    

    Keuntungannya 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

    • Alur ini juga berjalan berbasis browser saat semuanya berfungsi baik. Hanya saja, saat gagal ada jalur alternatif yang lebih baik