OmniStream Docs
  • Panduan Pengguna
  • Developer
  • API Reference
Developer Hub
Pendahuluan
Autentikasi
Model Data
Webhook
WebSocket
Self-Hosting
    Prasyarat Self-HostingDocker ComposeEnvironment VariablesDatabase MigrationsMeta App SetupSMTP ConfigReverse Proxy + CORS ProduksiCORS di Produksi
Error & Rate Limit
Self-Hosting

CORS di Produksi

CORS di Produksi

Halaman ini adalah dokumentasi, bukan perubahan backend. Kode api-gateway saat ini masih mengirim header CORS permisif. Perubahan backend yang sebenarnya dilacak sebagai item BE1 di docs-site/SCOPE.md — "Backend follow-up register" Section 4 — dan harus dikerjakan sebelum Try-It playground di docs-chat.misindo.id dibuka ke api-gateway produksi.

State saat ini

api-gateway memasang satu CORS layer global di router utama, tepatnya di crates/api-gateway/src/main.rs:253:

Code
let app = routes::build_router(state) .layer(GovernorLayer::new(governor_conf)) .layer(TraceLayer::new_for_http()) .layer(CorsLayer::permissive());

CorsLayer::permissive() dari tower_http menghasilkan header yang mengizinkan origin manapun, method manapun, dan header apapun. Secara konkret, respons preflight yang keluar kurang lebih:

Code
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH Access-Control-Allow-Headers: * Access-Control-Expose-Headers: *

Cocok untuk pengembangan lokal — frontend di http://localhost:4000, curl dari mana saja, Try-It playground di http://localhost:5000 semuanya langsung jalan tanpa konfigurasi. Tapi tidak cocok untuk produksi.

Kenapa permisif di produksi berbahaya

  1. Cross-origin credential leakage. Karena Allow-Origin dipasang ke *, browser tidak akan mengirim Authorization: Bearer ... secara otomatis pada fetch cross-origin (spec CORS memblokir kombinasi Allow-Origin: * + kredensial). Tapi banyak klien membaca JWT dari localStorage dan mengirimnya manual di header — kebijakan "terima semua origin" berarti situs pihak ketiga yang berhasil mencuri token bisa langsung memanggil endpoint kita.
  2. CSRF surface expansion. Endpoint idempotent yang hanya terlindungi oleh cookie session mudah di-replay dari tab manapun.
  3. Meng-ajarkan pola yang salah. Dokumentasi dan tutorial yang mencontohkan CorsLayer::permissive() di produksi merupakan anti-pattern — kita tidak mau itu muncul di developer onboarding OmniStream.

Token bucket rate limiter (tower_governor, 60 req/menit per IP — lihat developer/error-dan-rate-limit) tidak menyelesaikan ini. Rate limit membatasi volume, bukan origin.

Pola allowlist yang direkomendasikan

Ganti CorsLayer::permissive() dengan allowlist yang eksplisit, dibangun dari variabel lingkungan yang sudah dikenal konfigurasi (FRONTEND_URL) plus origin docs-site:

Code
use axum::http::{HeaderValue, Method}; use tower_http::cors::{AllowOrigin, CorsLayer}; // Di main.rs, sebelum build router: let frontend_origin: HeaderValue = config .frontend_url // mis. "https://app.omnistream.example" .parse() .expect("FRONTEND_URL must be a valid origin"); let docs_origin: HeaderValue = "https://docs-chat.misindo.id" .parse() .expect("docs origin"); let cors = CorsLayer::new() .allow_origin(AllowOrigin::list([frontend_origin, docs_origin])) .allow_methods([ Method::GET, Method::POST, Method::PUT, Method::PATCH, Method::DELETE, Method::OPTIONS, ]) .allow_headers([ axum::http::header::AUTHORIZATION, axum::http::header::CONTENT_TYPE, axum::http::header::ACCEPT, ]) .allow_credentials(true) .max_age(std::time::Duration::from_secs(600)); let app = routes::build_router(state) .layer(GovernorLayer::new(governor_conf)) .layer(TraceLayer::new_for_http()) .layer(cors);

Hal-hal yang membuat pola ini aman:

  • allow_origin eksplisit. Hanya dua origin yang valid: frontend utama (dari env) dan dokumentasi docs-chat.misindo.id. Semua origin lain mendapat preflight 403.
  • allow_methods eksplisit. Tidak ada wildcard. TRACE dan CONNECT otomatis tertolak.
  • allow_headers terkurasi. Hanya header yang benar-benar dipakai klien (Authorization, Content-Type, Accept). Klien tidak bisa memaksa header custom yang belum direview.
  • allow_credentials(true). Aman karena allow_origin sudah bukan wildcard. Browser akan mengirim cookie/Authorization sesuai spec.
  • max_age 600 detik. Mengurangi jumlah preflight tanpa memberi cache yang berlebihan — cocok untuk iterasi cepat saat debugging.

Kalau anda menjalankan api-gateway sendiri

Selama backend belum di-update, tiga jalur yang aman untuk produksi:

  1. Taruh di belakang reverse proxy yang sama domain-nya dengan frontend (mis. Nginx yang menyajikan frontend dan melakukan proxy_pass ke api-gateway pada path /api). Dengan begitu browser melihat semuanya same-origin dan CORS tidak pernah dieksekusi. Lihat developer/self-hosting/reverse-proxy.
  2. Jangan paparkan api-gateway langsung ke internet. Akses hanya dari VPS/LAN yang sama; buka port publik hanya untuk Nginx frontend-nya.
  3. Tunda membuka Try-It playground di docs-chat.misindo.id sampai BE1 selesai. Sementara itu, tetap gunakan Try-It untuk mengirim request ke http://localhost:3000 dari dev environment (localhost-to-localhost tidak mempedulikan CORS ketat).

Roadmap BE1

Item BE1 di docs-site/SCOPE.md Section 4 ber-status HIGH priority. Definition of Done:

  • CorsLayer::permissive() diganti dengan allowlist berbasis env + origin docs.
  • Tambahkan unit test di api-gateway yang memverifikasi origin yang tidak terdaftar dapat preflight 403 dan origin yang terdaftar mendapat 200 dengan header yang benar.
  • Update dokumentasi ini untuk mencoret peringatan bagian atas dan menunjukkan state baru sebagai "current".

Selama hal di atas belum terjadi, halaman ini berfungsi ganda: referensi bagi operator, dan checklist bagi pengembang backend yang akan mengerjakan BE1.

Last modified on June 8, 2026
Reverse Proxy + CORS ProduksiError & Rate Limit
On this page
  • State saat ini
  • Kenapa permisif di produksi berbahaya
  • Pola allowlist yang direkomendasikan
  • Kalau anda menjalankan api-gateway sendiri
  • Roadmap BE1
Rust
Rust