Alur login dan cookie httpOnly
Alur login dan cookie httpOnly
Browser tidak dapat menyimpan token JWT dengan aman di JavaScript. OmniStream memakai cookie httpOnly bernama access_token sebagai kanal sekunder yang mengangkut token sehingga frontend dapat melakukan request tanpa menyentuh token mentah.
Halaman ini merinci endpoint login, bentuk cookie yang dikembalikan, dan kapan memakai cookie vs header Authorization: Bearer.
Endpoint /api/auth/login
| Item | Nilai |
|---|---|
| Metode | POST |
| Path | /api/auth/login |
| Auth | Tidak (public) |
| Content-Type | application/json |
Request body:
Code
Response sukses (200 OK):
Body JSON:
Code
Header Set-Cookie:
Code
Nilai Max-Age adalah JWT_EXPIRATION_HOURS * 3600 — default 24 jam berarti 86400 detik.
Implementasi lengkap ada di crates/api-gateway/src/routes/auth.rs::login. Flow internalnya:
- Ambil baris
agentsberdasarkanemail. - Verifikasi
passwordterhadappassword_hash(Argon2id). - Susun
AgentClaimsdenganexp = now + JWT_EXPIRATION_HOURS * 3600. - Encode JWT HS256 memakai
JWT_SECRET. - Kirim token di dua tempat: JSON body dan header
Set-Cookie. - Rekam aktivitas
loginkeactivity_logssecara asinkron.
Atribut cookie
Frontend SvelteKit hanya perlu cookie — JavaScript tidak perlu menyentuh nilainya. Atribut:
| Atribut | Nilai | Alasan |
|---|---|---|
HttpOnly | ya | Mencegah XSS membaca token |
Secure | ya | Browser hanya mengirim cookie lewat HTTPS — di dev HTTP lokal, cookie tetap diset dan Chromium mengizinkannya lewat localhost |
SameSite=Strict | ya | Mencegah CSRF; cookie tidak dikirim pada cross-site navigation |
Path=/ | ya | Cookie berlaku di semua rute backend |
Max-Age | sesuai JWT_EXPIRATION_HOURS | Browser otomatis menghapus setelah kedaluwarsa |
Karena SameSite=Strict, frontend OmniStream harus dihosting pada origin yang sama atau subdomain yang sama dengan api-gateway supaya cookie ikut terkirim. Untuk deployment cross-origin, klien harus memakai header Authorization: Bearer dari body login.
Cara middleware membaca token
Middleware autentikasi di crates/api-gateway/src/middleware/auth.rs mencari token dengan urutan:
- Header
Authorization: Bearer <token>(prioritas pertama) - Cookie
access_token
Jika keduanya ada, header Bearer menang. Jika keduanya tidak ada, middleware mengembalikan 401 Unauthorized.
Efeknya: klien API non-browser (skrip Python, curl, Postman) cukup menyimpan token dari body response dan mengirim sebagai header — cookie tidak diperlukan.
Endpoint terkait
GET /api/auth/me
Mengembalikan AgentPublic untuk token yang aktif. Dipakai frontend saat reload halaman untuk memulihkan state user dari cookie.
Code
GET /api/auth/token
Mengeluarkan JWT baru untuk agent yang sedang login. Frontend memakai endpoint ini setelah reload halaman untuk memperoleh token in-memory (yang diperlukan oleh WebSocket via query string ?token=), sementara cookie httpOnly tetap menjadi sumber kebenaran di browser.
Code
Response:
Code
POST /api/auth/logout
Membersihkan cookie access_token dengan mengeluarkan cookie kosong ber-Max-Age=0 dan mencatat aktivitas logout. Token JWT itu sendiri tetap sah hingga exp-nya karena tidak ada denylist — frontend harus berhenti memakainya setelah logout.
Code
Kapan memakai cookie vs Bearer
| Klien | Rekomendasi |
|---|---|
| SvelteKit frontend OmniStream | Cookie (otomatis di-set oleh /api/auth/login) |
| Browser pihak ketiga pada origin yang sama | Cookie |
| Skrip server-to-server, worker, CLI | Header Authorization: Bearer |
| Webhook receiver (pihak ketiga memanggil kita) | Tidak relevan — webhook memakai HMAC signature, bukan JWT |
| WebSocket (browser) | Query string ?token= karena browser WebSocket tidak bisa menambah header |
Troubleshooting
- Frontend login 200, tetapi request berikutnya
401— kemungkinan origin berbeda. Periksa cookie di DevTools → Application → Cookies. Jika tidak ada cookie, perhatikanSameSite=Strictdan pastikan frontend berada di origin yang sama denganapi-gateway. Set-Cookiemuncul di response tetapi browser tidak menyimpannya — bisa karena atributSecurepada koneksi non-HTTPS (beberapa browser menolak di luarlocalhost). Pakai HTTPS di produksi.- Request dari Postman gagal
401— Postman tidak otomatis meneruskan cookie antar request. Pakai headerAuthorization: Beareryang diambil dari body/api/auth/login. - Token dari cookie dan header berbeda — pastikan Anda memakai token yang sama. Middleware memproses header lebih dulu sehingga Bearer "menang".
Catatan keamanan
- Jangan menaruh token di URL path/query kecuali untuk WebSocket — URL sering dicatat oleh server log dan histori browser.
- Rotasi
JWT_SECRETmenginvalidasi semua token aktif termasuk cookie yang sudah diset; browser masih mengirim cookie lama sampaiMax-Agehabis, tetapi middleware akan menolaknya dengan401, memaksa login ulang. - Serangan CSRF dicegah oleh
SameSite=Strict. Jangan pernah mengubahnya keLax/Nonetanpa menambahkan token CSRF header terpisah.