Bagaimana Saya Memastikan Kredensial OAuth2 Sudah Siap Sebelum Menulis Kode Aplikasi
On this page
- Pendahuluan
- A. Apa sebenarnya OAuth2 dan OpenID Connect itu
- B. Apa saja yang sebenarnya wajib cocok di kedua sisi
- C. Kenapa kekeliruan di sini muncul jauh dari sebabnya
- D. Kenapa tool generik tak menjawab kebutuhan ini
- E. Enam hambatan yang menghalangi
- Metode
- A. Satu config file per target, provider didaftar di dalamnya
- B. Menahan verifier PKCE melintasi redirect
- C. Mendeteksi token yang tak bisa diverifikasi lewat signature
- D. Memilih key ketika tokennya tak menyebut satu pun
- E. Mengambil himpunan key dengan cara yang tak ditolak
- F. Melaporkan bagaimana audience-nya cocok, bukan sekadar bahwa ia cocok
- G. Menggladi logout, bukan cuma login
- Hasil dan Pembahasan
- A. Apa yang sebenarnya ditegakkan gladi resik ini
- B. Dua provider yang sama-sama patuh, dua perilaku yang berbeda
- C. Di mana ini berhenti jadi tool yang tepat
- Simpulan
- Istilah
Login yang gagal di aplikasi yang belum jadi nyaris tak memberi tahu apa pun. Browser mendarat di halaman error, log memuat token yang ditolak, dan ada dua kemungkinan penjelasan yang dari tempat kita berdiri terlihat sama persis: identity provider-nya salah konfigurasi, atau service yang sedang kita tulis yang bermasalah. Keduanya melahirkan layar yang serupa.
Kesamaran itu mahal justru pada saat kita paling tak sanggup menanggungnya — di awal, ketika aplikasinya masih setengah jadi dan tak ada hal lain yang sudah terbukti. Maka saya berhenti memulai dari sana. Sebelum baris pertama kode autentikasi lahir di service baru, kredensialnya saya lewatkan dulu ke tool buatan sendiri, lalu saya saksikan satu login utuh berlangsung dari ujung ke ujung. Kalau itu berhasil, provider-nya bukan lagi tersangka, dan tiap kegagalan sesudahnya menjadi tanggungan saya.
Pendahuluan
Tulisan ini tentang tool tersebut dan keputusan-keputusan di dalamnya, dan bukan panduan memakainya — cuplikan kode di sini ada untuk menjelaskan kenapa sebuah pilihan diambil, bukan untuk disalin. Yang ingin saya sampaikan adalah bentuk persoalannya, sebab bentuk itu ternyata jauh lebih tak seragam ketimbang yang disiratkan spesifikasinya.
A. Apa sebenarnya OAuth2 dan OpenID Connect itu
Kedua nama ini nyaris dipertukarkan begitu saja padahal keduanya berbeda. Masing-masing layak satu alinea, sebab tiap hambatan di bawah duduk di perbatasan keduanya.
OAuth2 protokol untuk otorisasi yang didelegasikan. Pertanyaannya: bolehkah aplikasi ini bertindak atas nama seseorang? Yang ia serahkan adalah access token — kredensial yang ditunjukkan sebuah client kepada API untuk membuktikan ia diizinkan mengerjakan sesuatu. OAuth2 sendiri tak mengatakan apa pun tentang siapa orangnya. Ia memang tak pernah dirancang untuk itu.
OpenID Connect lapisan tipis di atas OAuth2 yang menambahkan pertanyaan yang hilang tadi: ini siapa? Ia memperkenalkan token kedua, ID token, yang selalu berupa JSON Web Token dan selalu menggambarkan orang yang sudah terautentikasi — identifier-nya, kapan ia login, dan untuk client mana token itu diterbitkan. Provider OIDC adalah provider OAuth2 yang bersedia menjawab kedua pertanyaan itu sekaligus.
JWT terdiri dari tiga segmen base64url yang dipisahkan titik: header yang menyebut algoritma penanda tangan, payload berisi klaim, dan signature atas keduanya. Siapa pun bisa membaca payload-nya tanpa key — ia ter-encode, bukan terenkripsi. Cuma signature yang membuktikan ia tak diutak-atik, dan memverifikasi signature itu menuntut public key milik provider.
Key tersebut diterbitkan di endpoint JWKS, satu dokumen JSON berisi daftar public key milik provider. Tiap key lazimnya membawa kid, sebuah key identifier, dan header sebuah token menyebut kid mana yang menandatanganinya. Cocokkan keduanya dan kita tahu key mana yang dipakai memverifikasi. Kalimat itu menggambarkan susunan yang baku — dan tiga dari enam hambatan di bawah lahir justru karena susunan itu tak selalu berlaku.
B. Apa saja yang sebenarnya wajib cocok di kedua sisi
Sebuah client OAuth2 bukan satu rahasia tunggal. Ia sehimpunan nilai yang wajib cocok di kedua ujung sebuah sambungan antara dua sistem yang kerap diurus orang berbeda. Ketika orang bilang kredensialnya sudah siap, daftar inilah yang secara diam-diam sedang ia klaim sudah benar.
Issuer adalah identitas provider, sebuah URL yang muncul di dalam tiap token yang ia tanda tangani. Endpoint menentukan ke mana user dikirim, ke mana code ditukar, dari mana key diambil, dan ke mana sesi diakhiri. Client identifier menamai aplikasinya; redirect URI menyatakan ke mana provider boleh mengembalikan user, dan ia wajib sama persis huruf per huruf dengan yang terdaftar. Scope menyatakan apa yang diminta. Dan PKCE — mekanisme pembuktian yang dijelaskan di bawah — mengatur bagaimana authorization code ditebus.
Satu saja dari semuanya keliru, kegagalan lahir. Sayangnya mereka tak melahirkan kegagalan yang bisa dibedakan satu sama lain, dan di situlah seluruh kesulitannya.
C. Kenapa kekeliruan di sini muncul jauh dari sebabnya
Error autentikasi luar biasa buruk dalam menunjuk dirinya sendiri. Redirect URI yang cuma berbeda satu garis miring di ujung gagal di sisi provider, sebelum kode kita sempat berjalan, dengan pesan yang dilihat user dan tak dilihat kita. Scope yang tak pernah diberikan melahirkan token yang bekerja sempurna sampai ada request belakangan yang membutuhkan klaim yang tak ada di dalamnya. Audience yang tak memuat client identifier kita melahirkan token yang lolos secara struktur lalu ditolak middleware kita sendiri — perilaku yang benar, tapi tampak seperti bug.
Di tiap kasus, saat kekeliruannya terjadi dan saat gejalanya muncul terpisah — oleh sebuah redirect, oleh sebuah deploy, atau oleh berapa lama pun waktu yang dibutuhkan sampai jalur kode itu benar-benar dilewati. Dan pada service yang sedang aktif dikembangkan, anggapan yang paling wajar ketika sesuatu gagal adalah kode barunya yang salah, sebab kode baru itulah yang berubah. Anggapan itu cukup sering benar sampai menjadi berbahaya.
D. Kenapa tool generik tak menjawab kebutuhan ini
Tool yang sanggup menjalankan alur OAuth2 ada banyak. Saya memakainya, dan semuanya menyelesaikan persoalan yang berbeda dari persoalan saya.
Client generik dibangun untuk memperagakan protokolnya sekali. Yang saya butuhkan adalah menjalankan gladi resik yang sama berulang-ulang, melintasi beberapa provider, terhadap beberapa konfigurasi, berpindah-pindah di antaranya dalam satu sore — sebab sistem yang memfederasi identitas tak punya satu provider, ia punya daftar. Saya juga butuh gladi resiknya jujur soal apa yang sebenarnya ia verifikasi: bukan "tokennya kembali", melainkan "signature ini diperiksa dengan key ini, audience-nya cocok lewat jalur ini, dan klaim ini ada".
Merakit itu sendiri dengan HTTP client umum memang bisa, dan bentuk usahanya justru keliru. Bagian yang sulit dari OAuth2 — menurunkan challenge PKCE, menahan verifier melintasi redirect browser, memilih key yang tepat dari dokumen JWKS — justru bagian yang tak bisa dibantu perakit request, sekaligus bagian yang paling mungkin keliru secara halus kalau dikerjakan tangan dengan terburu-buru.
E. Enam hambatan yang menghalangi
Dinyatakan sebagai tujuan: membuktikan sehimpunan kredensial OAuth2 benar-benar siap sebelum ada aplikasi yang bergantung padanya, supaya tiap kegagalan sesudahnya bisa ditimpakan dengan yakin kepada yang benar. Dengan dua batasan. Ia wajib bekerja untuk beberapa provider sekaligus, sepanjang mereka mematuhi spesifikasinya — bukan satu provider yang saya istimewakan. Dan ia wajib cukup cepat sehingga saya sungguh-sungguh mengerjakannya, sebab langkah verifikasi yang memakan setengah jam adalah langkah verifikasi yang akhirnya dilewati.
Gampang dirumuskan, canggung dibangun. Ada enam hambatan yang menghalangi, dan benang yang mengalir lewat empat hambatan di tengah sama: provider yang sungguh-sungguh mematuhi spesifikasi yang sama ternyata tak berperilaku sama.
Hambatan pertama: tak ada tool serba-guna yang tahu bentuk konfigurasi saya. Tiap target yang saya pedulikan adalah kombinasi berbeda dari issuer, endpoint, redirect URI, client identifier, dan scope — dan saya perlu berpindah di antaranya tanpa menyunting apa pun. Alternatif yang ditolak justru yang paling kentara: merakit request dengan tangan di HTTP client umum, yang bekerja sekali lalu berubah jadi latihan menyalin pada kali ketiga. Penggantinya: satu config file per target, dipilih lewat command line, dengan provider-nya didaftar di dalamnya, bukan ditanam di kode.
Hambatan kedua: PKCE menuntut satu nilai selamat melewati perjalanan pulang-pergi lewat browser. Sebuah verifier acak dibangkitkan sebelum user dikirim ke provider; hash-nya ikut di authorization URL; dan yang aslinya wajib ditunjukkan, tanpa berubah, saat code ditukar dengan token — bisa jadi beberapa menit kemudian, sesudah satu form login dan satu redirect. Membangkitkannya ulang saat penukaran adalah kekeliruan yang paling terpikirkan, dan ia gagal sebab hash yang dicatat provider diturunkan dari verifier yang pertama. Maka verifier-nya dicetak dan ditahan, sengaja terlihat, sampai penukaran memakainya.
Hambatan ketiga: tak semua access token itu JWT. Anggapan yang wajar — bahwa access token bisa dibelah, di-decode, lalu diverifikasi — berlaku untuk sebagian provider dan tidak untuk sebagian lain. Google menerbitkan access token yang opaque: untaian tanpa struktur untuk diperiksa dan tanpa signature untuk dicocokkan. Memperlakukannya sebagai JWT melahirkan error decoding yang tampak seperti kerusakan, padahal ia perbedaan rancangan. Alternatif yang ditolak adalah menganggap token selalu terstruktur; penggantinya mendeteksi bentuknya lebih dulu, lalu ketika ia opaque, memverifikasinya lewat satu-satunya jalan yang tersedia — bertanya kepada provider dan benar-benar memakainya.
Hambatan keempat: dokumen JWKS tak selalu menyatakan key mana yang menandatangani token. Susunan bakunya memasangkan kid di header token dengan kid di himpunan key. Ketika header-nya tak menyertakannya, implementasi yang ketat tak punya apa pun untuk dijadikan dasar pemilihan lalu menyerah. Alternatif yang ditolak adalah memercayai kid selalu ada — dan itulah yang diandaikan hampir semua default library; penggantinya mendahulukan kecocokan kid, dan bila gagal, mencoba tiap key non-enkripsi di himpunan itu sampai ada yang berhasil.
Hambatan kelima: helper pengambil key bawaan library ditolak web application firewall. Client praktis yang ikut dalam library JWT mengambil himpunan key lewat urllib, yang user agent bawaannya diblokir oleh sangat banyak proxy pelindung — request-nya kembali sebagai 403, dan tak ada apa pun pada response itu yang menyiratkan persoalannya ada di user agent, bukan di kredensialnya. Memakai helper bawaan adalah alternatif yang ditolak; penggantinya mengambil dokumen key-nya langsung dengan HTTP client biasa dan user agent yang tak tampak seperti mesin.
Hambatan keenam: pemeriksaan audience yang benar justru menolak token yang sepenuhnya sah. Spesifikasinya menyatakan client identifier semestinya berada di klaim aud, dan validator yang menegakkannya sudah berperilaku benar. Keycloak — identity provider open-source yang banyak dipakai — dengan konfigurasi bawaannya sama sekali tak menaruh client identifier di aud milik access token — ia muncul di azp, si authorized party. Maka kedua alternatif yang ditolak sama-sama menggoda dan sama-sama keliru: memercayai aud saja akan menolak kredensial yang bekerja, sementara mematikan validasi audience supaya error-nya hilang justru membuang pemeriksaan yang paling berarti. Penggantinya memperlakukan aud sebagai yang utama dan azp sebagai cadangan — dan, inilah yang menentukan untuk sebuah tool verifikasi, melaporkan yang mana di antara keduanya yang cocok, bukan diam-diam menerima keduanya.
Urutannya soal ketergantungan. Yang pertama menentukan bentuk tool-nya, dan semua sisanya tinggal di dalam bentuk itu. Yang kedua milik alurnya sendiri dan wajib benar sebelum ada token apa pun untuk diperiksa. Yang ketiga, keempat, dan kelima sebenarnya satu temuan yang sama tiba dalam tiga samaran berbeda — bahwa mematuhi spesifikasi tak melahirkan perilaku yang seragam — dan ketiganya diurutkan menurut seberapa awal masing-masing menghentikan verifikasinya. Yang keenam ditaruh terakhir sebab ia bukan soal apakah sebuah token bisa diverifikasi, melainkan soal apa artinya memverifikasinya — dan pertanyaan itu baru bisa diajukan setelah lima yang sebelumnya terjawab.
Metode
A. Satu config file per target, provider didaftar di dalamnya
Inilah jawaban atas hambatan pertama, dan ia menentukan bentuk semua yang datang sesudahnya.
Sebuah konfigurasi adalah daftar issuer. Tiap issuer membawa endpoint-nya, redirect URI yang terdaftar untuknya, client yang menjadi miliknya, dan scope yang layak diminta. Tak ada apa pun tentang satu provider tertentu yang ikut ter-build ke dalam tool-nya; provider adalah data.
Dua akibat mengikutinya, dan keduanya memang yang dituju. Beberapa issuer bisa duduk dalam satu file, sehingga sistem yang memfederasi identitas dari provider korporat dan provider publik sekaligus bisa digladi resik dalam sekali duduk. Dan beberapa file bisa berdampingan, satu per target deployment, dipilih saat tool-nya dijalankan.
# Target jadi argumen, bukan suntingan. Berpindah berarti mengetik kata
# yang berbeda, bukan mengubah keadaan sebuah file.
Baris kedua itu lebih berarti dari kelihatannya. File yang memuat tiga issuer adalah cara saya memastikan service yang bermaksud menerima login dari beberapa provider benar-benar akan menerima semuanya — satu per satu, dalam satu sesi, tanpa menyentuh apa pun di antara tiap jalannya.
B. Menahan verifier PKCE melintasi redirect
Inilah jawaban atas hambatan kedua. PKCE — Proof Key for Code Exchange — ada supaya authorization code yang tercegat tak berguna bagi yang mencegatnya. Client mengarang sebuah rahasia sebelum ia mengirim user pergi, lalu membuktikan ia memegang rahasia itu saat menebus code-nya.
Penurunannya sengaja searah: challenge yang berjalan terbuka di URL adalah hash dari verifier yang tidak.
# Challenge itu yang disimpan provider; verifier itu yang membuktikan kita
# belakangan. Satu diturunkan dari yang lain, tak pernah sebaliknya.
=
= Dua nilai lain dibangkitkan bersamanya dan keduanya menjawab pertanyaan berbeda. state dipantulkan kembali provider dan membuktikan callback-nya milik request yang kita buat; nonce ditanam di dalam ID token dan membuktikan token itu milik login ini, bukan login lama yang diputar ulang. Gladi resik yang melewatkan salah satunya sedang menggladi yang berbeda dari apa yang nanti dikerjakan aplikasinya.
Karena tool-nya interaktif dan browser program terpisah, verifier-nya tak bisa tinggal di sebuah variabel yang membentang melintasi jurang itu. Ia dicetak, terang-terangan, disertai pesan untuk menyimpannya — dan alurnya berhenti di situ sampai callback URL-nya kembali. Itu bukan keterbatasan yang layak direkayasa habis-habisan: tool verifikasi justru diuntungkan bila mekanismenya dibuat terlihat, bukan disembunyikan.
C. Mendeteksi token yang tak bisa diverifikasi lewat signature
Inilah jawaban atas hambatan ketiga, dan di sinilah anggapan tentang keseragaman pertama kali patah.
Pemeriksaannya sendiri nyaris sepele, dan justru itulah intinya. JWT terdiri dari tiga segmen berpemisah titik. Selain itu opaque, dan sebanyak apa pun usaha decoding tak akan mengubahnya.
# Struktur, bukan isi. Token opaque bukan token yang cacat —
# ia rancangan yang berbeda, dan butuh pemeriksaan yang berbeda.
return == 3Ketika tokennya opaque, masih ada pertanyaan yang layak dijawab: apakah ia sah, dan apakah ia memang milik client ini? Keduanya bisa ditegakkan tanpa signature — dengan bertanya langsung kepada provider, lalu dengan memakai tokennya persis seperti yang nanti dilakukan aplikasi. Yang pertama memastikan masa berlaku, audience, dan scope yang diberikan; yang kedua memastikan tokennya sungguh membuka sebuah resource.
Langkah kedua itu yang takkan saya hilangkan. Token yang lolos introspeksi dengan bersih lalu gagal mengambil profil sudah menyingkapkan apa yang takkan pernah disingkapkan pemeriksaan struktur.
D. Memilih key ketika tokennya tak menyebut satu pun
Inilah jawaban atas hambatan keempat. Jalur biasanya: baca kid dari header token lalu temukan key dengan identifier yang sama. Jalur canggungnya: segala yang selain itu.
# Dahulukan kecocokan kid. Kalau gagal, coba tiap signing key, sebab
# sebagian provider tak menyertakan kid sama sekali dan pencarian yang
# ketat akan langsung menyerah.
=
return
# tanpa kid, atau tak ada yang cocok
return
continueDua detail di dalamnya disengaja. Key enkripsi dibuang lebih dulu sebelum apa pun, sebab sebuah himpunan key sah-sah saja memuat key yang memang tak pernah dimaksudkan untuk memverifikasi signature. Dan cadangannya benar-benar cadangan, bukan jalur utama — ketika kid ada dan cocok, kecocokan itulah yang dipakai, sebab mencoba key satu per satu padahal provider sudah menyebutkan key mana yang benar itu menebak-nebak yang berdandan seperti ketangguhan.
E. Mengambil himpunan key dengan cara yang tak ditolak
Inilah jawaban atas hambatan kelima, sekaligus yang paling banyak membuang waktu demi alasan yang paling tak menarik.
Library JWT-nya menyertakan client praktis yang mengambil lalu menyimpan himpunan key untuk kita. Ia memakai urllib, dan sangat banyak proxy pelindung menolak mentah-mentah user agent bawaan urllib. Hasilnya HTTP 403 di tengah-tengah validasi token, yang terbaca sebagai persoalan izin pada kredensialnya — kesimpulan yang persis keliru, pada saat yang persis keliru, di sebuah tool yang seluruh tujuannya justru memberi tahu apakah kredensialnya yang bermasalah.
# Diambil langsung, bukan lewat helper library: helper-nya memakai urllib,
# dan proxy pelindung menolak user agent-nya dengan 403.
=
= Pelajaran umumnya layak dipisahkan dari perbaikan khususnya. Tool diagnostik punya tuntutan yang lebih ketat ketimbang aplikasi: ia tak boleh melahirkan kegagalan buatannya sendiri yang menyerupai kegagalan yang justru ingin ia deteksi. Apa pun di dalamnya yang bisa gagal secara samar layak diganti dengan yang gagal secara terang, sekalipun penggantinya kurang praktis.
F. Melaporkan bagaimana audience-nya cocok, bukan sekadar bahwa ia cocok
Inilah jawaban atas hambatan keenam, dan satu-satunya tempat spesifikasi dan sistem yang bekerja saling berselisih.
OpenID Connect mewajibkan client identifier muncul di klaim aud milik ID token, dan validator yang menegakkannya berperilaku benar. Keycloak, pada konfigurasi bawaannya, tak menaruh client identifier di aud milik access token — client-nya muncul di azp, si authorized party, yang menamai siapa yang meminta token, bukan untuk siapa token itu.
Maka pemeriksaan yang ketat melaporkan kegagalan atas kredensial yang sebenarnya bekerja. Godaannya: mematikan validasi audience sampai error-nya berhenti — dan itu menukar satu alarm palsu dengan satu pemeriksaan yang hilang.
# Klaim mana yang cocok itu sendiri sebuah temuan, jadi kembalikan
# nilainya, jangan lumatkan kedua kasus jadi True.
= in
return # baku dan lebih diutamakan
return # diterima, dan layak disebut lantang
return None # sama sekali bukan untuk client iniMengembalikan klaim mana yang cocok, ketimbang sebuah boolean, itulah yang membuatnya berguna di sebuah tool verifikasi. Kecocokan lewat aud dilaporkan sebagai benar. Kecocokan lewat azp dilaporkan sebagai diterima disertai catatan — artinya provider-nya belum dikonfigurasi dengan audience mapper, dan itu temuan nyata tentang konfigurasinya meski tak ada yang rusak. Tak cocok sama sekali berarti tokennya diterbitkan untuk orang lain, dan itu bukan peringatan melainkan penghentian.
G. Menggladi logout, bukan cuma login
Login separuh dari sebuah sesi. Mengakhirinya separuh yang biasanya diuji belakangan, lazimnya di production, lazimnya oleh user yang ternyata masih login sesudah menekan tombolnya.
Alur logout-nya sebentuk dengan alur login dan digladi dengan cara yang sama: URL dibangun, browser dikirim ke sana, dan callback-nya divalidasi saat ia kembali. Yang ia bawa berbeda — id_token_hint supaya provider tahu sesi mana yang harus diakhiri, sebuah redirect pasca-logout yang juga wajib terdaftar, dan state untuk mengikat callback-nya ke request-nya.
Validasinya adalah tempat perbedaan sungguhan antar provider menyembul. state wajib kembali dan wajib cocok. iss, sebaliknya, opsional pada callback logout, dan Keycloak tak mengirimkannya.
# state wajib: tak cocok berarti callback ini bukan milik kita.
# iss opsional di sini. Tak ada itu wajar; keliru itu error.
!= :
Pemeriksa yang mewajibkan iss akan melaporkan kegagalan atas logout yang berjalan sempurna. Membedakan tak ada dari keliru itulah beda antara tool yang memberi tahu sesuatu dan tool yang lama-lama kita abaikan.
Hasil dan Pembahasan
A. Apa yang sebenarnya ditegakkan gladi resik ini
Di akhir satu jalan yang berhasil, tool-nya sudah mengerjakan, berurutan: membangun authorization URL dengan challenge sungguhan, memvalidasi callback-nya terhadap state yang ia bangkitkan, menukar code-nya memakai verifier yang asli, menerima token, mengambil key milik provider, memverifikasi signature kedua token terhadap key tersebut, lalu mencetak tiap klaim di dalamnya.
Rangkaian itulah seluruh nilainya, sebab tiap langkah cuma mungkin terjadi bila langkah sebelumnya benar. Token yang terverifikasi adalah bukti bahwa client identifier, secret-nya bila ada, redirect URI, scope, penurunan PKCE, dan penemuan key-nya semuanya benar — bukan diklaim satu per satu, melainkan diperagakan sekaligus.
Artinya klaim yang sedari awal saya inginkan kini tersedia: ketika aplikasinya gagal sesudah ini, provider-nya bukan tersangka. Itu tak mempersempit persoalannya sampai satu baris, tapi ia membuang separuh ruang pencarian justru pada saat ruang pencariannya paling luas.
B. Dua provider yang sama-sama patuh, dua perilaku yang berbeda
Hal paling berguna yang dihasilkan latihan ini bukan tool-nya. Melainkan bukti bahwa "mendukung OpenID Connect" menggambarkan wilayah kesepakatan yang jauh lebih sempit ketimbang kedengarannya.
| Perilaku | Keycloak | |
|---|---|---|
| Bentuk access token | JWT, bisa diverifikasi lewat signature | Opaque, diverifikasi lewat introspeksi |
kid di header token | Selalu ada | Tak selalu |
Client di aud access token | Tak ada secara bawaan; muncul di azp | Ada |
iss di callback logout | Tak dikirim | Alurnya berbeda sama sekali |
Tiap baris adalah tempat sebuah implementasi yang wajar — ditulis mengikuti spesifikasinya, diuji terhadap satu provider — patah begitu bertemu provider yang lain. Tak satu pun baris itu pelanggaran. Inilah akibat biasa dari spesifikasi yang membolehkan pilihan, bertemu dua tim yang memilih berbeda.
Simpulan praktis yang saya tarik: verifikasi apa pun yang layak dikerjakan wajib dilakukan terhadap provider yang benar-benar akan dipakai, bukan terhadap provider yang dianggap mewakili. Kedengarannya jelas begitu dituliskan, dan ia rutin dilanggar oleh test suite yang memalsukan identity provider-nya sama sekali.
C. Di mana ini berhenti jadi tool yang tepat
Ia interaktif secara rancangan, yang membuatnya tak cocok untuk continuous integration — ia menuntut manusia dengan browser, dan itu pertukaran yang benar untuk gladi resik yang dikerjakan beberapa kali per service, bukan pada tiap commit.
Ia memverifikasi konfigurasi, bukan perilaku di bawah beban, bukan umur sesi sepanjang berhari-hari, bukan refresh token di perbatasan masa berlaku. Dan ia membuktikan kredensialnya pada satu titik waktu; provider yang konfigurasinya berubah minggu depan menuntut gladi resiknya dijalankan lagi — yang justru murah, sebab config file-nya sudah terlanjur ditulis.
Simpulan
Yang layak dibawa dari sini bukan tool-nya, melainkan urutannya. Memverifikasi sebuah integrasi sebelum bergantung padanya mengubah satu golongan kegagalan yang samar jadi golongan yang tegas, dan perubahan itu lebih berharga ketimbang waktu yang diongkoskannya — terutama di awal umur satu service, ketika segalanya belum terbukti dan godaan menyalahkan kode yang paling baru sedang paling kuat.
Hal kedua lebih sempit dan terus saya temui di tempat lain: tool diagnostik wajib gagal lebih terang ketimbang apa yang ia diagnosis. User agent yang menarik 403 bukan jebakan yang menarik, tapi jebakan yang mengajari — sebab ia akan membuat tool-nya berdusta persis ke arah yang seharusnya ia cegah.
Istilah
- OAuth2
- Protokol untuk otorisasi yang didelegasikan. Menjawab apakah sebuah aplikasi boleh bertindak atas nama seseorang, dan tak mengatakan apa pun tentang siapa orang itu.
- OpenID Connect
- Lapisan identitas di atas OAuth2, menambahkan ID token yang menggambarkan orang yang sudah terautentikasi.
- Access token
- Kredensial yang ditunjukkan kepada API. Bisa berupa JWT, bisa untaian opaque, tergantung provider-nya.
- ID token
- Selalu JWT, selalu tentang orang yang terautentikasi, dan token yang audience-nya wajib menyebut client-nya.
- JWT
- Header, payload, dan signature yang di-encode sebagai tiga segmen berpemisah titik. Terbaca tanpa key; baru layak dipercaya sesudah signature-nya diverifikasi.
- JWKS
- Himpunan public key milik provider yang diterbitkan, dipakai memverifikasi signature token.
- kid
- Key identifier yang muncul di header token maupun di himpunan key, dipakai memilih key pemverifikasi. Tak selalu ada.
- PKCE
- Mekanisme yang mengikat authorization code ke client yang memintanya, lewat sebuah verifier rahasia dan hash-nya yang diterbitkan.
- state
- Nilai yang dipantulkan kembali provider, membuktikan callback-nya milik request yang dibuat.
- nonce
- Nilai yang ditanam di ID token, membuktikan ia diterbitkan untuk login ini, bukan diputar ulang dari login sebelumnya.
- aud
- Klaim audience: token ini untuk siapa. Pemeriksaan utama bahwa sebuah token memang ditujukan untuk client ini.
- azp
- Authorized party: siapa yang meminta token. Cadangan ketika sebuah provider tak memasukkan client ke audience-nya.
Technologies covered
- Python
- Keycloak
- JWT
- OAuth 2.0
- OpenID Connect
- PKCE