Bagaimana Mengaktifkan CORS dengan Cookie HTTPOnly untuk Mengamankan Token?

Diterbitkan: 2021-12-29

Pada artikel ini, kita melihat cara mengaktifkan CORS (Cross-Origin Resource Sharing) dengan cookie HTTPOnly untuk mengamankan token akses kita.

Saat ini, server backend dan klien frontend digunakan di domain yang berbeda. Oleh karena itu server harus mengaktifkan CORS untuk memungkinkan klien berkomunikasi dengan server di browser.

Selain itu, server menerapkan otentikasi stateless untuk skalabilitas yang lebih baik. Token disimpan dan dipelihara di sisi klien, tetapi tidak di sisi server seperti sesi. Untuk keamanan, lebih baik menyimpan token di cookie HTTPOnly.

Mengapa permintaan Cross-Origin diblokir?

Mari kita asumsikan bahwa aplikasi frontend kita disebarkan di https://app.geekflare.com . Skrip yang dimuat di https://app.geekflare.com hanya dapat meminta sumber daya yang berasal dari yang sama.

Setiap kali kami mencoba mengirim permintaan lintas-Asal ke domain lain https://api.geekflare.com atau port lain https://app.geekflare.com:3000 atau skema lain http://app.geekflare.com , permintaan lintas-asal akan diblokir oleh browser.

Tetapi mengapa permintaan yang sama diblokir oleh browser dikirim dari server backend mana pun menggunakan permintaan curl atau dikirim dengan menggunakan alat seperti tukang pos tanpa masalah CORS. Ini sebenarnya untuk keamanan untuk melindungi pengguna dari serangan seperti CSRF (Pemalsuan Permintaan Lintas Situs).

Mari kita ambil contoh, misalkan jika ada pengguna yang masuk ke akun PayPal mereka sendiri di browser mereka. Jika kami dapat mengirim permintaan lintas-asal ke paypal.com dari skrip yang dimuat di domain lain yang malicious.com tanpa kesalahan/pemblokiran CORS seperti kami mengirim permintaan asal yang sama.

Penyerang dapat dengan mudah mengirim halaman berbahaya mereka https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account dengan mengubahnya menjadi short-URL untuk menyembunyikan URL yang sebenarnya. Ketika pengguna mengklik tautan berbahaya, skrip yang dimuat di domain malicious.com akan mengirimkan permintaan lintas-asal ke PayPal untuk mentransfer jumlah pengguna ke akun PayPal penyerang yang akan dieksekusi. Semua pengguna yang telah masuk ke akun PayPal mereka dan mengklik tautan berbahaya ini akan kehilangan uang mereka. Siapapun dapat dengan mudah mencuri uang tanpa sepengetahuan pengguna akun PayPal.

Untuk alasan di atas, browser memblokir semua permintaan lintas-asal.

Apa itu CORS (Cross-Origin Resource Sharing)?

CORS adalah mekanisme keamanan berbasis header yang digunakan oleh server untuk memberi tahu browser agar mengirim permintaan lintas-asal dari domain tepercaya.
Server diaktifkan dengan header CORS yang digunakan untuk menghindari permintaan lintas asal yang diblokir oleh browser.

Bagaimana CORS bekerja?

Karena server telah menentukan domain tepercayanya dalam konfigurasi CORS-nya. Saat kami mengirim permintaan ke server, responsnya akan memberi tahu browser bahwa domain yang diminta dipercaya atau tidak di header-nya.

Ada dua jenis permintaan CORS:

  • Permintaan sederhana
  • Permintaan Pra-penerbangan

Permintaan Sederhana:

Alur permintaan sederhana CORS memberi tahu bahwa ia mengirim permintaan lintas-asal tetapi ketika menerima respons. Ini memeriksa header.

  • Browser mengirimkan permintaan ke domain lintas asal dengan Origin(https://app.geekflare.com).
  • Server mengirimkan kembali respons yang sesuai dengan metode yang diizinkan dan asal yang diizinkan.
  • Setelah menerima permintaan, browser akan memeriksa nilai header asal yang dikirim ( https://app.geekflare.com ) dan nilai access-control-allow-origin yang diterima ( https://app.geekflare.com ) sama atau wildcard(*). Jika tidak, itu akan menimbulkan kesalahan CORS.

Permintaan Prapenerbangan:

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • Bergantung pada parameter permintaan khusus dari permintaan lintas-asal seperti metode (PUT, DELETE) atau header khusus atau tipe konten yang berbeda, dll. Browser akan memutuskan untuk mengirim permintaan OPSI pra-penerbangan untuk memeriksa apakah permintaan yang sebenarnya aman untuk dikirim atau tidak.
  • Setelah menerima respons (kode status: 204, yang berarti tidak ada konten), browser akan memeriksa parameter akses-kontrol-izinkan untuk permintaan yang sebenarnya. Jika parameter permintaan diizinkan oleh server. Permintaan lintas-asal yang sebenarnya dikirim dan diterima

Jika access-control-allow-origin: * , maka respons diizinkan untuk semua asal. Tapi itu tidak aman kecuali Anda membutuhkannya.

Bagaimana cara mengaktifkan CORS?

Untuk mengaktifkan CORS untuk domain apa pun, aktifkan tajuk CORS untuk mengizinkan Asal, metode, tajuk khusus, kredensial, dll.

Browser membaca header CORS dari server dan mengizinkan permintaan aktual dari klien hanya setelah memverifikasi parameter permintaan.

  • Access-Control-Allow-Origin: Untuk menentukan domain yang tepat (https://app.geekflate.com, https://lab.geekflare.com) atau wildcard (*)
  • Access-Control-Allow-Methods: Untuk mengizinkan metode HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) yang hanya kita butuhkan.
  • Access-Control-Allow-Headers: Untuk mengizinkan hanya Header tertentu (Otorisasi, csrf-token)
  • Access-Control-Allow-Credentials: Nilai Boolean yang digunakan untuk mengizinkan kredensial lintas-Asal (cookie, header otorisasi).
  • Access-Control-Max-Age: Memberi tahu browser untuk men-cache respons preflight selama beberapa waktu.
  • Access-Control-Expose-Headers: Tentukan header yang dapat diakses oleh skrip sisi klien.

Untuk mengaktifkan CORS di server web Apache dan Nginx, ikuti tutorial ini.

Mengaktifkan CORS di ExpressJS

Mari kita ambil contoh aplikasi ExpressJS tanpa CORS:

 const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })

Dalam contoh di atas, kami telah mengaktifkan titik akhir API pengguna untuk metode POST, PUT, GET tetapi bukan metode DELETE.

Untuk mengaktifkan CORS dengan mudah di aplikasi ExpressJS, Anda dapat menginstal cors

 npm install cors

Akses-Kontrol-Izinkan-Asal

Mengaktifkan CORS untuk semua domain

 app.use(cors({ origin: '*' }));

Mengaktifkan CORS untuk satu domain

 app.use(cors({ origin: 'https://app.geekflare.com' }));

Jika Anda ingin mengizinkan CORS untuk asal https://app.geekflare.com dan https://lab.geekflare.com

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));

Akses-Kontrol-Izinkan-Metode

Untuk mengaktifkan CORS untuk semua metode, hilangkan opsi ini di modul CORS di ExpressJS. Tetapi untuk mengaktifkan metode tertentu (GET, POST, PUT).

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));

Akses-Kontrol-Izinkan-Header

Digunakan untuk mengizinkan header selain default untuk dikirim dengan permintaan yang sebenarnya.

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));

Akses-Kontrol-Izinkan-Kredensial

Abaikan ini jika Anda tidak ingin memberi tahu browser untuk mengizinkan kredensial berdasarkan permintaan bahkan pada withCredentials disetel ke true .

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));

Akses-Kontrol-Maks-Usia

Untuk menginstruksikan browser untuk men-cache informasi respons preflight dalam cache selama detik yang ditentukan. Abaikan ini jika Anda tidak ingin men-cache respons.

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));

Respons pra-penerbangan yang di-cache akan tersedia selama 10 menit di browser.

Access-Control-Expose-Header

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));

Jika kita meletakkan wildcard(*) di openHeaders, itu tidak akan mengekspos header Otorisasi. Jadi kita harus mengekspos secara eksplisit seperti di bawah ini

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));

Di atas akan mengekspos semua tajuk dan tajuk Otorisasi juga.

Apa itu cookie HTTP?

Cookie adalah sepotong kecil data yang akan dikirim server ke browser klien. Pada permintaan selanjutnya, browser akan mengirimkan semua cookie yang terkait dengan domain yang sama pada setiap permintaan.

Cookie memiliki atributnya sendiri, yang dapat didefinisikan untuk membuat cookie bekerja secara berbeda sesuai kebutuhan.

  • Nama Nama kue.
  • nilai: data cookie masing-masing ke nama cookie
  • Domain: cookie akan dikirim hanya ke domain yang ditentukan
  • Jalur: cookie dikirim hanya setelah jalur awalan URL yang ditentukan. Misalkan jika kita telah mendefinisikan jalur cookie kita seperti path='admin/'. Cookie tidak dikirim untuk URL https://geekflare.com/expire/ tetapi dikirim dengan awalan URL https://geekflare.com/admin/
  • Max-Age/Expires (angka dalam detik): Kapan cookie harus kedaluwarsa. Seumur hidup cookie membuat cookie tidak valid setelah waktu yang ditentukan.
  • HTTPOnly(Boolean): Server backend dapat mengakses cookie HTTPOnly itu tetapi bukan skrip sisi klien jika benar.
  • Aman (Boolean): Cookie hanya dikirim melalui domain SSL/TLS jika benar.
  • sameSite(string [Strict, Lax, None]): Digunakan untuk mengaktifkan/membatasi cookie yang dikirim pada permintaan lintas situs. Untuk mengetahui lebih detail tentang cookie sameSite lihat MDN. Ia menerima tiga opsi Ketat, Lax, Tidak Ada. Nilai keamanan cookie disetel ke true untuk konfigurasi cookie sameSite=None.

Mengapa cookie HTTPOnly untuk token?

Menyimpan token akses yang dikirim dari server di penyimpanan sisi klien seperti penyimpanan lokal , DB yang diindeks, dan cookie (HTTPOnly not set to true) lebih rentan terhadap serangan XSS. Misalkan jika salah satu halaman Anda lemah terhadap serangan XSS. Penyerang dapat menyalahgunakan token pengguna yang disimpan di browser.

Cookie HTTPOnly hanya disetel/didapatkan oleh server/backend tetapi tidak di sisi klien.

Skrip sisi klien dibatasi untuk mengakses cookie HTTPonly itu. Jadi cookie HTTPOnly tidak rentan terhadap Serangan XSS dan lebih aman. Karena itu hanya dapat diakses oleh server.

Aktifkan cookie HTTPOnly di backend yang diaktifkan CORS

Mengaktifkan Cookie di CORS memerlukan konfigurasi di bawah ini di aplikasi/server.

  • Setel header Access-Control-Allow-Credentials ke true.
  • Access-Control-Allow-Origin dan Access-Control-Allow-Headers tidak boleh berupa wildcard (*).
  • Atribut cookie sameSite seharusnya Tidak Ada.
  • Untuk mengaktifkan nilai sameSite ke none, setel nilai aman ke true: Aktifkan backend dengan sertifikat SSL/TLS agar berfungsi di nama domain.

Mari kita lihat contoh kode yang menetapkan token akses di cookie HTTPOnly setelah memeriksa kredensial login.

 const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });

Anda dapat mengonfigurasi cookie CORS dan HTTPOnly dengan menerapkan empat langkah di atas dalam bahasa backend dan server web Anda.

Anda dapat mengikuti tutorial ini untuk Apache dan Nginx untuk mengaktifkan CORS dengan mengikuti langkah-langkah di atas.

dengan Kredensial untuk permintaan Lintas-Asal

Kredensial (Cookie, Otorisasi) dikirim dengan permintaan asal yang sama secara default. Untuk cross-origin, kita harus menentukan withCredentials menjadi true.

XMLHttpRequest API:

 var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.geekflare.com/user', true); xhr.withCredentials = true; xhr.send(null);

Ambil API:

 fetch('http://api.geekflare.com/user', { credentials: 'include' });

JQuery Ajax:

 $.ajax({ url: 'http://api.geekflare.com/user', xhrFields: { withCredentials: true } });

aksio:

 axios.defaults.withCredentials = true

Kesimpulan

Saya harap artikel di atas membantu Anda memahami cara kerja CORS dan mengaktifkan CORS untuk permintaan lintas asal di server. Mengapa menyimpan cookie di HTTPOnly aman dan bagaimana withCredentials digunakan di klien untuk permintaan lintas-asal.