Dokumen ini menjelaskan secara rinci penerapan JSON Web Token (JWT) untuk autentikasi dan Cross-Origin Resource Sharing (CORS) untuk keamanan akses API pada aplikasi Cemilan KasirPOS.
Dokumen ini mencakup panduan konfigurasi untuk dua skenario deployment yang umum:
- Subfolder Deployment (Satu Domain)
- Cross-Domain Deployment (Beda Domain/Subdomain)
Backend menggunakan kombinasi JWT (JSON Web Token) dan HttpOnly Cookies untuk keamanan sesi yang optimal.
- Token Storage: Mendukung dual-mode:
- Bearer Token: Disimpan di LocalStorage (Header
Authorization: Bearer ...). - HttpOnly Cookie: Token disimpan di cookie bernama
pos_tokenyang tidak bisa diakses oleh JavaScript (Mitigasi XSS).
- Bearer Token: Disimpan di LocalStorage (Header
- Algoritma: HS256 (
hash_hmac). - Payload: Menyimpan User ID, Role, dan Expiration (default 24 jam).
Untuk mencegah serangan Brute Force pada halaman login, sistem menggunakan mekanisme File Locking:
- Tracking: Mencatat IP address dan jumlah percobaan gagal di file
login_attempts.json. - Locking: Menggunakan
flock($fp, LOCK_EX)untuk mencegah Race Condition saat penulisan file dari request yang bersamaan. - Limit: Maksimal 5 percobaan gagal.
- Penalty: Lockout selama 15 menit jika terlampaui.
Setiap data yang masuk ke server melewati lapisan validasi ketat:
- Schema Whitelisting:
- Hanya kolom yang didefinisikan dalam
$schemas(diindex.php) yang diterima. Kolom tak dikenal otomatis dibuang.
- Hanya kolom yang didefinisikan dalam
- Type Validation:
- Menggunakan
filterDataBySchemauntuk memastikan data sesuai struktur tabel.
- Menggunakan
- XSS Protection:
- String input dibersihkan menggunakan
strip_tags()untuk menghapus potensi script berbahaya.
- String input dibersihkan menggunakan
- SQL Injection Protection:
- Murni menggunakan PDO Prepared Statements.
- Tidak ada query konkatenasi string untuk nilai parameter.
- Nama kolom divalidasi dengan regex
/^[a-zA-Z0-9_]+$/untuk mencegah injeksi via nama kolom dynamic.
File terkait: php_server/auth.php
PENTING: Anda WAJIB mengganti JWT_SECRET di production agar token tidak bisa dipalsukan.
Cara Mengganti Secret:
- Pastikan file
.envsudah dibuat di folderphp_server(copy dari.env.example). - Edit baris
JWT_SECRET:JWT_SECRET=GantiStringIniDenganKarakterAcakYangSangatPanjangDanRumit!@#123
- Simpan file. Backend otomatis akan menggunakan value ini.
CORS adalah mekanisme keamanan browser yang membatasi bagaimana halaman web di satu domain bisa meminta resource dari domain lain.
File konfigurasi: php_server/config.php
Secara default, backend dikonfigurasi untuk fleksibilitas (Development Mode):
// php_server/config.php
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// Mengizinkan origin dari mana saja (kurang aman untuk production)
header("Access-Control-Allow-Origin: " . ($origin ? $origin : '*'));
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With");
// Handle Preflight Request (OPTIONS)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}Berikut adalah detail konfigurasi untuk dua skenario penempatan web yang berbeda.
Pada skenario ini, Frontend dan Backend berada di domain yang sama, hanya beda folder.
Contoh URL:
- Frontend:
https://tokocemilan.com/app/(atau di roothttps://tokocemilan.com/) - Backend:
https://tokocemilan.com/api/
Analisis CORS:
- Karena Frontend dan Backend memiliki Origin yang sama (Protocol, Domain, dan Port sama), maka CORS tidak terlalu ketat. Browser menganggap ini "Same-Origin".
- Namun, konfigurasi CORS tetap disarankan untuk keamanan tambahan atau jika ada aset yang di-load dari subdomain lain.
Konfigurasi yang Disarankan:
-
Backend (
php_server/config.php): Anda bisa membatasi origin secara spesifik atau membiarkannya dinamis karena masih satu domain.// Lebih aman: Spesifik header("Access-Control-Allow-Origin: https://tokocemilan.com");
-
Frontend (
.env.production): Set URL API relatif atau absolut.VITE_API_URL=https://tokocemilan.com/api
-
Web Server (.htaccess): Pastikan routing Frontend tidak menabrak routing Backend. Lihat panduan di
README_CPANEL_HOSTING.mdbagian.htaccessuntuk mengecualikan folder/apidari routing React.
Skenario ini memisahkan Frontend dan Backend di domain atau subdomain berbeda. Ini adalah arsitektur modern yang umum.
Contoh URL:
- Frontend:
https://app.tokocemilan.com(atauhttps://tokocemilan.vercel.app) - Backend:
https://api.tokocemilan.com(atauhttps://backend-saya.com)
Analisis CORS:
- Ini adalah Cross-Origin Request. Browser akan memblokir request jika backend tidak mengirim header CORS yang benar.
- Browser akan mengirim request
OPTIONS(Preflight) terlebih dahulu sebelum mengirim requestPOST,PUT, atauDELETE. Backend harus meresponOPTIONSdengan status 200 OK.
Konfigurasi yang Disarankan:
-
Backend (
php_server/config.php): Sangat Disarankan untuk membatasi origin hanya ke domain Frontend Anda demi keamanan. Jangan gunakan*.// php_server/config.php (Otomatis membaca dari .env) // Ambil dari .env, default ke array kosong jika tidak ada $allowed_origins_env = getenv('ALLOWED_ORIGINS'); $allowed_origins = $allowed_origins_env ? explode(',', $allowed_origins_env) : []; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowed_origins)) { header("Access-Control-Allow-Origin: $origin"); } header("Access-Control-Allow-Credentials: true"); // ...
Cara Setting di .env:
ALLOWED_ORIGINS=https://app.tokocemilan.com,https://tokocemilan.vercel.app -
Frontend (
.env.production): Gunakan URL lengkap Backend.VITE_API_URL=https://api.tokocemilan.com
-
Cookies (Opsional): Jika Anda berencana menggunakan Cookies di masa depan (bukan LocalStorage), Anda wajib set
Access-Control-Allow-Credentials: truedanAccess-Control-Allow-Origintidak boleh*.
- Penyebab: Backend tidak mengirim header CORS, atau Origin frontend tidak ada di daftar yang diizinkan backend.
- Solusi: Cek
php_server/config.php. Pastikan domain frontend Anda tertulis persis (termasukhttps://dan tanpa slash di akhir).
- Penyebab: Token JWT tidak terkirim di header, atau Token expired, atau JWT Secret di backend berubah (misal setelah deploy ulang tanpa .env yang persisten).
- Solusi: Cek tab Network di browser, lihat request header. Pastikan ada
Authorization: Bearer eyJ.... Jika tidak, coba logout dan login ulang.
- Penyebab: Web server (Apache/Nginx) memblokir method
OPTIONSatau routing PHP tidak menangani method tersebut. - Solusi: Pastikan blok kode penanganan
OPTIONSdiconfig.phpberada di paling atas sebelum logika database atau auth.
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}HTTP Strict Transport Security (HSTS) adalah mekanisme kebijakan keamanan web yang memaksa browser web untuk berinteraksi dengan situs web hanya menggunakan koneksi HTTPS yang aman, dan tidak pernah melalui protokol HTTP yang tidak aman. Ini membantu mencegah serangan protocol downgrade dan cookie hijacking.
- Keamanan: Mencegah pengguna mengakses versi HTTP yang tidak aman dari situs Anda.
- Kepercayaan: Meningkatkan kepercayaan pengguna dan mesin pencari (SEO).
- Kepatuhan: Memenuhi standar keamanan modern.
Di file php_server/config.php, HSTS sekarang dikonfigurasi secara otomatis:
// Security Headers
if (isset($_SERVER['REQUEST_METHOD'])) {
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("X-XSS-Protection: 1; mode=block");
// HSTS Logic: Only enabled if HTTPS is detected
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");
}
}PENTING:
- Prasyarat: Fitur ini hanya akan aktif jika server mendeteksi koneksi secure (
HTTPS). - Max-Age: Nilai
31536000detik setara dengan 1 tahun. - Production: Pastikan SSL certificate terinstall dengan benar di server production Anda.