SSO aman tanpa dark pattern berarti memberi pengguna pilihan login yang wajar tanpa mengorbankan keamanan atau memaksa mereka terkunci pada satu vendor identitas. Untuk aplikasi web, desain yang baik biasanya menggabungkan login lokal dan login federasi, memakai session yang dapat dirotasi, alur linking akun yang eksplisit, serta kontrol backend yang ketat untuk redirect, CSRF, state, dan nonce.
Masalahnya bukan hanya “bagaimana menambahkan tombol Login with X”, tetapi bagaimana menghindari anti-pattern yang merugikan pengguna dan developer: akun ganda yang sulit dipulihkan, logout yang tidak jelas, fallback yang sengaja dipersulit, dan alur autentikasi yang bergantung pada asumsi vendor tertentu. Artikel ini fokus pada keputusan implementasi yang praktis untuk web app dan backend.
Tujuan desain: aman, portabel, dan tidak mendorong lock-in
Desain login yang sehat sebaiknya memenuhi beberapa prinsip berikut:
- Pengguna tetap punya pilihan: sediakan login lokal atau setidaknya mekanisme pemulihan yang tidak bergantung pada satu IdP.
- Akun dapat dipindahkan: identitas pengguna di sistem Anda tidak boleh semata-mata ditentukan oleh email dari satu provider.
- Backend menjadi sumber kebenaran: frontend hanya memulai alur; verifikasi token, pembuatan session, dan pengambilan keputusan keamanan harus terjadi di server.
- Session dapat dicabut: pengguna dan admin harus bisa logout dari perangkat lain dan memutus session aktif.
- Fallback aman tersedia: passkey, password, email magic link, atau metode lain harus dirancang sebagai opsi, bukan jebakan UX.
Prinsip penting: gunakan federasi untuk mempermudah autentikasi, bukan untuk menyerahkan kontrol penuh atas lifecycle akun dan akses pengguna.
Memilih model autentikasi: login lokal, federasi, atau hybrid
Login lokal
Login lokal berarti aplikasi Anda menyimpan kredensial sendiri, biasanya berupa password hash atau kredensial passkey/WebAuthn yang terdaftar ke akun internal.
Kelebihan:
- Tidak bergantung pada ketersediaan atau kebijakan vendor identitas.
- Lebih mudah menyediakan recovery dan account portability.
- Cocok untuk aplikasi yang perlu kontrol penuh atas proses autentikasi.
Kekurangan:
- Anda memikul tanggung jawab keamanan password reset, rate limit, credential stuffing defense, dan monitoring.
- UX bisa lebih berat jika tidak dilengkapi passkey atau magic link.
Login federasi
Login federasi biasanya memakai OpenID Connect (OIDC) di atas OAuth 2.0. Aplikasi mengarahkan pengguna ke Identity Provider (IdP), lalu menerima hasil login melalui authorization code flow. Untuk web app tradisional atau backend-for-frontend, ini umumnya pendekatan yang paling aman dibanding mengandalkan token langsung di browser.
Kelebihan:
- Mengurangi kebutuhan pengguna membuat password baru.
- Dapat memanfaatkan MFA, deteksi risiko, dan kebijakan keamanan yang sudah dimiliki IdP.
- Mempermudah onboarding jika pengguna sudah punya akun pada penyedia tertentu.
Kekurangan:
- Risiko lock-in jika itu satu-satunya cara masuk.
- Masalah account recovery jika email, domain, atau akun IdP pengguna berubah.
- Potensi kebingungan jika ada beberapa IdP dan linking akun tidak jelas.
Pendekatan hybrid yang biasanya paling sehat
Untuk banyak aplikasi web, pendekatan paling seimbang adalah:
- punya user record internal yang stabil di database;
- mendukung beberapa login method per akun, misalnya password, passkey, dan satu atau lebih identitas federasi;
- menganggap provider eksternal sebagai metode autentikasi, bukan identitas tunggal pengguna di sistem Anda.
Ini mengurangi lock-in sekaligus menjaga UX tetap sederhana.
Model data yang aman untuk akun dan identity linking
Kesalahan umum adalah menyimpan satu kolom seperti google_id di tabel user lalu menganggap masalah selesai. Model yang lebih aman adalah memisahkan entitas user, auth identity, dan session.
users
- id
- primary_email
- email_verified_at
- status
- created_at
user_auth_identities
- id
- user_id
- provider -- local, oidc_google, oidc_enterprise, passkey
- provider_subject -- subject/identifier unik dari provider
- email_at_login -- email yang diklaim saat autentikasi
- linked_at
- last_used_at
- metadata_json
user_sessions
- id
- user_id
- session_hash
- created_at
- last_seen_at
- expires_at
- revoked_at
- ip_address
- user_agent
- auth_strength -- misalnya password, passkey, federated+mfa
Manfaat model ini:
- Satu pengguna bisa punya banyak metode login.
- Perubahan email di provider tidak otomatis mengubah identitas akun.
- Logout lintas perangkat menjadi lebih mudah karena session terlacak per perangkat.
- Anda bisa menerapkan kebijakan berbeda berdasarkan auth strength.
Jangan link akun hanya berdasarkan email
Ini anti-pattern yang sangat umum. Walau email sering dipakai sebagai petunjuk, jangan otomatis menautkan akun federasi ke akun lokal hanya karena alamat email cocok, kecuali Anda benar-benar memahami tingkat jaminan dari provider dan konteks bisnis Anda mengizinkannya.
Risikonya:
- Email dapat berubah di sisi provider.
- Beberapa provider tidak memberi jaminan yang sama tentang verifikasi email.
- Dalam organisasi, domain email bisa berpindah kepemilikan atau akses karyawan dicabut.
- Anda membuka peluang account takeover jika linking dilakukan tanpa re-authentication pada akun lama.
Alur account linking yang aman
- Pengguna login ke akun yang sudah ada dengan metode yang sudah terdaftar.
- Pengguna memilih “Tambahkan metode login”.
- Server memulai alur federasi baru dengan
statekhusus linking. - Setelah callback, server memverifikasi token dan memastikan identitas eksternal belum terhubung ke akun lain.
- Jika aksi sensitif, minta step-up authentication atau re-auth sebelum menyimpan link.
- Simpan
provider+provider_subjectsebagai pasangan unik. - Kirim notifikasi keamanan bahwa metode login baru telah ditambahkan.
Jika seorang pengguna mencoba login dengan IdP yang email-nya cocok dengan akun lama tetapi belum terhubung, lebih aman menampilkan opsi seperti:
- login ke akun lama terlebih dahulu untuk menautkan;
- atau buat akun baru secara eksplisit.
Jangan menggabungkan keduanya secara diam-diam.
Alur OIDC web yang aman: redirect URI, state, nonce, dan token handling
Gunakan authorization code flow di backend
Untuk aplikasi web, pola yang umum dan aman adalah browser diarahkan ke IdP, lalu callback kembali ke backend Anda. Backend menukar code dengan token, memverifikasi respons, lalu membuat session aplikasi sendiri. Ini lebih aman daripada menjadikan token IdP sebagai session utama browser.
Validasi redirect URI secara ketat
Redirect URI harus diperlakukan sebagai nilai yang sudah didaftarkan, bukan parameter bebas yang bisa diisi pengguna. Kesalahan umum adalah menerima return_to atau redirect dari query parameter lalu meneruskannya mentah-mentah setelah login.
Praktik yang lebih aman:
- Daftarkan callback URI statis pada IdP.
- Gunakan allowlist untuk tujuan redirect internal setelah login.
- Simpan tujuan akhir di server, terkait ke
state, bukan percaya langsung pada input browser.
// Pseudocode
allowedPaths = ["/dashboard", "/settings/profile", "/billing"]
requestedPath = request.query.return_to
finalPath = allowedPaths.contains(requestedPath) ? requestedPath : "/dashboard"
state = randomSecureString()
storeState(state, {
purpose: "login",
final_path: finalPath,
expires_at: now + 10 minutes
})
redirectToIdP(state)State dan nonce bukan formalitas
state dipakai untuk mengikat callback ke permintaan login yang benar dan membantu mencegah CSRF pada alur OAuth/OIDC. nonce dipakai untuk mengikat ID token ke alur autentikasi tertentu dan membantu mencegah replay atau token substitution pada skenario tertentu.
Minimal yang perlu dilakukan:
- hasilkan
statedannoncedengan generator acak kriptografis; - simpan di server atau session sementara yang aman;
- beri masa berlaku pendek;
- verifikasi keduanya pada callback;
- hapus setelah dipakai satu kali.
Jangan percaya token tanpa verifikasi
Jika Anda menerima ID token atau access token dari provider, backend perlu memverifikasi setidaknya:
- issuer cocok dengan IdP yang diharapkan;
- audience ditujukan ke aplikasi Anda;
- masa berlaku valid;
- tanda tangan token valid menurut kunci publik penyedia;
noncecocok jika dipakai;subdipakai sebagai identifier stabil provider, bukan email.
Hindari memakai data identitas dari browser sebagai sumber kebenaran jika backend belum memverifikasinya sendiri.
Session management: rotation, penyimpanan, dan logout lintas perangkat
Buat session aplikasi sendiri
Setelah login berhasil, idealnya aplikasi membuat session internal sendiri, biasanya berbentuk cookie HttpOnly, Secure, dan SameSite yang sesuai. Jangan menjadikan access token dari IdP sebagai satu-satunya artefak login browser untuk web app biasa jika Anda tidak benar-benar membangun arsitektur SPA/API yang memerlukannya.
Keuntungan session internal:
- lebih mudah dicabut kapan saja;
- tidak terlalu bergantung pada format atau lifecycle token vendor;
- bisa diperkaya dengan metadata perangkat, level autentikasi, dan kebijakan internal.
Rotasi session setelah login dan aksi sensitif
Session rotation penting untuk mengurangi risiko session fixation. Saat pengguna berhasil login, tautkan identitas ke session baru, bukan mempertahankan session anonim lama tanpa regenerasi.
Rotasi juga layak dilakukan setelah:
- reset password;
- menambahkan metode login baru;
- menaikkan privilege;
- step-up authentication.
// Pseudocode
oldSessionId = getSessionIdFromCookie(request)
if (oldSessionId) {
revokeSession(oldSessionId)
}
newSessionId = randomSecureString()
storeSession(hash(newSessionId), {
user_id: user.id,
created_at: now,
expires_at: now + sessionTTL,
ip_address: request.ip,
user_agent: request.userAgent
})
setSessionCookie(newSessionId)Simpan identifier session dengan aman
Jika session disimpan di database, pertimbangkan untuk menyimpan hash session token, bukan token mentah. Jika database bocor, attacker tidak langsung bisa memakai session aktif. Pendekatan ini mirip dengan penyimpanan password hash, walau biasanya dengan skema yang lebih sederhana karena token acak berentropi tinggi.
Tambahan yang berguna:
- TTL yang wajar dan perpanjangan saat aktif bila memang dibutuhkan;
- kolom
revoked_atuntuk invalidasi; - fingerprint ringan seperti user agent dan IP untuk audit, bukan satu-satunya kontrol keamanan.
Logout lintas perangkat
Fitur “logout dari semua perangkat” adalah bagian penting dari desain yang minim lock-in dan lebih ramah pemulihan akun. Implementasinya relatif sederhana jika Anda menyimpan session secara server-side atau punya daftar session aktif.
Alur dasarnya:
- Pengguna membuka halaman manajemen session/perangkat.
- Server menampilkan daftar session aktif berdasarkan tabel session.
- Pengguna dapat mencabut satu session atau semua session lain.
- Request berikutnya dari session yang dicabut harus gagal dan meminta login ulang.
Jika Anda juga mengelola refresh token untuk API atau integrasi lain, pencabutan perlu mencakup token tersebut, bukan hanya cookie web.
CSRF, cookie, dan proteksi request sensitif
Untuk web app berbasis cookie, CSRF tetap relevan. Banyak tim salah mengira bahwa login federasi menghilangkan kebutuhan CSRF pada aplikasi mereka sendiri.
Gunakan pertahanan berlapis
- Cookie flags: gunakan
HttpOnlydanSecure; pertimbangkanSameSite=Laxuntuk banyak aplikasi web biasa. - CSRF token: wajib untuk form atau endpoint state-changing yang memakai cookie session.
- Origin/Referer check: berguna sebagai lapisan tambahan, terutama untuk endpoint sensitif.
SameSite membantu, tetapi bukan pengganti token CSRF untuk semua kasus. Perubahan flow login, subdomain, atau integrasi tertentu dapat membuat asumsi terlalu sempit menjadi rapuh.
Passkey sebagai opsi kuat, bukan paksaan tanpa fallback
Passkey/WebAuthn dapat mengurangi phishing dan friksi password, tetapi implementasinya bisa berubah menjadi dark pattern jika dijadikan satu-satunya opsi atau didesain agar pengguna sulit pindah perangkat atau browser.
Prinsip desain passkey yang sehat
- Anggap passkey sebagai metode login tambahan atau utama yang tetap punya fallback.
- Sediakan cara pemulihan yang tidak mengunci pengguna pada satu device ecosystem.
- Jelaskan dengan jujur kapan pengguna perlu mendaftarkan lebih dari satu kredensial.
- Jangan sembunyikan opsi login alternatif hanya untuk mendorong adopsi passkey.
Fallback yang masuk akal bisa berupa:
- password + MFA;
- email magic link dengan kontrol rate limit yang ketat;
- passkey kedua di perangkat lain;
- recovery code sekali pakai.
Trade-off-nya jelas: semakin banyak fallback, semakin besar permukaan serangan. Namun fallback nol sering kali berarti support burden tinggi dan risiko lockout yang juga merugikan keamanan operasional.
Rate limit login dan pencegahan abuse
Endpoint login, password reset, magic link, dan callback federasi adalah target abuse. Rate limit harus dirancang agar menghambat serangan tanpa terlalu mudah dipakai untuk denial-of-service terhadap pengguna sah.
Apa yang perlu dibatasi
- percobaan login per akun;
- percobaan login per IP atau subnet, dengan hati-hati;
- permintaan password reset atau magic link;
- percobaan linking identitas baru;
- percobaan kode OTP atau challenge MFA.
Praktik yang lebih baik daripada hard lock sederhana
Hard lock berkepanjangan pada kegagalan login sering menjadi alat abuse: attacker cukup menebak email korban untuk mengunci akunnya. Lebih baik gunakan kombinasi:
- progressive delay setelah beberapa kegagalan;
- challenge tambahan setelah pola mencurigakan;
- notifikasi keamanan untuk percobaan gagal berulang;
- deteksi credential stuffing berbasis pola, bukan hanya satu metrik.
// Pseudocode sederhana
keyAccount = "login:account:" + normalizeIdentifier(input)
keyIp = "login:ip:" + request.ip
if tooManyRecentFailures(keyAccount, keyIp) {
requireAdditionalChallenge()
}
if loginFailed {
incrementFailure(keyAccount, ttl=15m)
incrementFailure(keyIp, ttl=15m)
sleepWithBoundedBackoff()
}Jangan lupa menjaga pesan error agar tidak mempermudah enumerasi akun. Misalnya, gunakan respons generik untuk login gagal atau password reset, sambil tetap mencatat alasan internal untuk debugging.
Anti-pattern yang merugikan pengguna dan developer
1. Hanya menyediakan satu IdP tanpa recovery independen
Jika akun IdP hilang, dibekukan, atau pengguna berganti konteks kerja, akses ke aplikasi ikut hilang. Ini buruk untuk UX, support, dan kontinuitas bisnis.
2. Auto-link akun berdasarkan email tanpa verifikasi tambahan
Ini mempermudah account takeover dan menyebabkan state akun yang sulit dipahami saat provider berubah.
3. Logout hanya menghapus cookie lokal
Jika server tidak mencabut session atau refresh token terkait, pengguna mendapat rasa aman palsu. Logout harus benar-benar memutus artefak autentikasi yang relevan.
4. Redirect bebas ke URL arbitrer
Open redirect pada flow login sering menjadi pintu phishing atau token leakage.
5. Mengandalkan localStorage untuk session web biasa
Untuk session cookie-based, menyimpan token sensitif di JavaScript-accessible storage menambah risiko XSS. Ini bukan berarti localStorage selalu salah di semua arsitektur, tetapi untuk banyak web app tradisional, cookie HttpOnly lebih aman dan lebih sederhana.
6. Menyembunyikan fallback agar pengguna terdorong ke metode tertentu
Dari sisi keamanan produk, ini sering terlihat “rapi”, tetapi secara praktik meningkatkan lockout, beban support, dan ketergantungan yang tidak perlu.
Threat model singkat
Berikut ancaman yang paling relevan untuk desain login semacam ini:
- Phishing: pengguna diarahkan ke halaman login palsu atau callback redirect berbahaya.
- CSRF: request state-changing dilakukan dengan cookie korban.
- Session fixation/hijacking: attacker memanfaatkan session lama atau mencuri token session.
- Account takeover via linking: identitas eksternal tertaut ke akun yang salah.
- Credential stuffing: percobaan massal memakai pasangan email/password bocor.
- Open redirect: alur login disalahgunakan untuk mengarahkan korban ke domain jahat.
- Token replay/substitution: token dipakai di konteks yang tidak semestinya.
- User enumeration: endpoint login/reset mengungkap apakah akun ada.
Tujuan Anda bukan menghilangkan semua risiko, melainkan membuat setiap jalur serangan membutuhkan lebih banyak prasyarat, lebih mudah dideteksi, dan lebih mudah dipulihkan.
Contoh alur implementasi yang seimbang
Alur registrasi dan login awal
- Pengguna membuat akun lokal dengan email dan password, atau langsung memakai passkey, atau login federasi.
- Backend membuat
userinternal dan mencatat metode autentikasi pertama diuser_auth_identities. - Setelah login sukses, backend merotasi session dan menyimpan session baru.
- UI menyarankan pengguna menambahkan metode login cadangan, misalnya passkey kedua atau login lokal.
Alur login federasi untuk akun yang sudah ada
- Pengguna memilih login federasi.
- Server membuat
state,nonce, dan tujuan redirect internal yang sudah di-allowlist. - Setelah callback, server memverifikasi respons provider.
- Server mencari pasangan
provider + subject. - Jika ada, login berhasil dan session baru dibuat.
- Jika belum ada tetapi email cocok dengan akun lama, jangan auto-link; minta pengguna login ke akun lama untuk mengonfirmasi linking.
Alur logout dari semua perangkat
- Pengguna menekan “logout dari semua perangkat”.
- Server menandai semua session aktif akun sebagai dicabut, kecuali session saat ini jika diinginkan.
- Jika ada refresh token internal, token tersebut juga dicabut.
- Permintaan selanjutnya dari session yang dicabut harus ditolak dan meminta login ulang.
Checklist implementasi
- Gunakan model akun internal yang terpisah dari identitas provider.
- Dukung lebih dari satu metode login per akun jika memungkinkan.
- Jangan auto-link berdasarkan email tanpa re-auth atau verifikasi eksplisit.
- Gunakan authorization code flow dengan backend sebagai penukar token.
- Validasi issuer, audience, expiry, signature,
state, dannonce. - Daftarkan callback URI statis dan pakai allowlist untuk redirect internal.
- Gunakan cookie session
HttpOnly,Secure, dan pengaturanSameSiteyang sesuai. - Rotasi session setelah login, reset password, dan aksi sensitif.
- Simpan hash session token jika session disimpan di database.
- Sediakan halaman manajemen perangkat/session dan logout lintas perangkat.
- Terapkan CSRF token untuk endpoint state-changing berbasis cookie.
- Rate limit login, reset, magic link, MFA challenge, dan linking akun.
- Hindari pesan error yang memudahkan enumerasi akun.
- Simpan secret klien OIDC, kunci signing, dan token internal di secret manager atau environment yang terlindungi; jangan hardcode di repo.
- Log kejadian keamanan penting: login sukses, gagal berulang, linking, unlinking, reset, revocation.
- Uji flow edge case: email provider berubah, akun provider dicabut, callback diulang, state kedaluwarsa, session dicabut saat request berjalan.
Penyimpanan secret dan operasional
Topik ini sering diremehkan. Secret seperti client secret OIDC, kunci enkripsi cookie, signing key internal, dan kredensial ke secret store harus diperlakukan sebagai material sensitif.
Praktik minimum:
- jangan commit secret ke repository;
- pisahkan secret per environment;
- batasi akses operator berdasarkan kebutuhan;
- siapkan rotasi secret dan prosedur rollback;
- audit penggunaan secret pada pipeline dan runtime.
Jika Anda mengenkripsi data tertentu di database, pastikan memahami perbedaan antara hashing untuk verifikasi tak-balik dan encryption untuk data yang perlu dipulihkan.
Trade-off UX vs security
Tidak ada desain login yang sempurna untuk semua produk. Beberapa trade-off yang realistis:
- Lebih banyak opsi login meningkatkan portabilitas, tetapi menambah kompleksitas account recovery dan support.
- Passkey-first meningkatkan ketahanan phishing, tetapi butuh fallback yang baik agar tidak jadi lockout machine.
- Linking eksplisit lebih aman daripada auto-link, tetapi menambah satu langkah UX.
- Session server-side lebih mudah dicabut, tetapi menambah kebutuhan storage dan sinkronisasi.
- Rate limit agresif menghambat brute force, tetapi bisa mengganggu pengguna sah jika tuning buruk.
Pendekatan yang masuk akal biasanya bukan memaksimalkan satu sisi, melainkan membuat alur default aman sambil tetap menyediakan jalur pemulihan yang jujur dan dapat dipahami pengguna.
Penutup
SSO aman tanpa dark pattern bukan berarti menolak federasi, melainkan menempatkannya dalam arsitektur yang memberi pengguna pilihan dan memberi backend kontrol yang benar. Gunakan akun internal sebagai jangkar, verifikasi alur OIDC dengan disiplin, hindari auto-link berdasarkan email, rotasi session, sediakan logout lintas perangkat, dan jangan menjadikan fallback sebagai alat lock-in.
Jika Anda harus memilih prioritas implementasi, mulai dari empat hal ini: linking akun yang eksplisit, validasi redirect/state/nonce yang ketat, session rotation + revocation, dan rate limit yang waras. Empat hal tersebut biasanya memberi dampak terbesar terhadap keamanan nyata tanpa merusak UX secara berlebihan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!