Otentikasi REST dan mengekspos kunci API

93

Saya telah membaca di REST dan ada banyak pertanyaan tentang SO, serta di banyak situs dan blog lain. Meskipun saya belum pernah melihat pertanyaan spesifik ini ditanyakan ... untuk beberapa alasan, saya tidak dapat memahami konsep ini ...

Jika saya membuat RESTful API, dan saya ingin mengamankannya, salah satu metode yang saya lihat adalah menggunakan token keamanan. Ketika saya menggunakan API lain, ada token dan rahasia bersama ... masuk akal. Apa yang saya tidak mengerti adalah, permintaan untuk operasi layanan istirahat dilakukan melalui javascript (XHR / Ajax), apa untuk mencegah seseorang mengendusnya dengan sesuatu yang sederhana seperti FireBug (atau "lihat sumber" di browser) dan menyalin kunci API, lalu meniru identitas orang itu menggunakan kunci dan rahasia?

tjan
sumber
salah satu metode yang pernah saya lihat adalah dengan menggunakan token keamanan , sebenarnya ada banyak metode di luar sana. Apakah Anda punya contoh konkret. Saya mungkin berpikir Anda bingung dengan "REST" vs. "menyediakan API javascript hanya untuk pengguna terdaftar" (mis. Google maps).
PeterMmm
1
Sejak Anda bertanya hampir 2 tahun yang lalu: apa yang akhirnya Anda gunakan sendiri?
Arjan
Saya tidak benar-benar menggunakan apa pun, saya lebih dari sekadar mencoba membungkus kepala saya untuk menciptakan konsep. Komentar PeterMmm di atas mungkin benar ... masih belum perlu menerapkan semua ini, tapi saya ingin memperbaiki diri ... terima kasih telah menindaklanjuti.
tjan

Jawaban:

22

api rahasia tidak diteruskan secara eksplisit, rahasia digunakan untuk menghasilkan tanda permintaan saat ini, di sisi server, server menghasilkan tanda mengikuti proses yang sama, jika dua tanda cocok, maka permintaan berhasil diautentikasi - jadi hanya tanda melewati permintaan, bukan rahasia.

James.Xu
sumber
9
Jadi jika itu hanya tanda yang berlalu ... bukankah itu masih terekspos di javascript ... jadi jika saya meletakkan foto flicker di halaman web saya melalui API mereka (disebut dengan javascript), dan Anda mengunjungi halaman saya, aren ' t Saya mengekspos kunci API saya kepada siapa pun yang mengunjungi halaman saya?
tjans
6
Saya tidak berpikir saya mengajukan pertanyaan saya dengan benar ... mungkin bagian dari alasan saya tidak menemukan apa yang saya cari sejak awal. ketika saya melakukan panggilan ajax, katakanlah menggunakan jquery, saya harus menyematkan kunci api di panggilan ajax agar dapat diteruskan ke server ... pada saat itu seseorang dapat melihat kunci API. Jika saya salah memahami itu, bagaimana kunci API dikirim dengan permintaan jika tidak disematkan ke dalam skrip klien?
tjans
4
untuk menyimpulkan: orang akan diberi pasangan rahasia + apikey sebelum menggunakan openapi / restapi, tanda apikey + akan ditransfer ke sisi server untuk memastikan server tahu siapa yang membuat permintaan, rahasia rahasia tidak akan pernah ditransfer ke sisi server untuk keamanan .
James.Xu
7
Jadi pernyataan @ James.Xu bahwa 'rahasia digunakan untuk menghasilkan tanda permintaan saat ini' adalah SALAH! Karena klien tidak mengetahui rahasianya, karena tidak aman untuk mengirimkannya kepadanya (dan bagaimana lagi dia akan tahu itu?) 'Rahasia' yang secara teknis merupakan 'kunci pribadi' HANYA digunakan OLEH SERVER (karena tidak ada orang lain yang mengetahuinya) untuk menghasilkan tanda untuk dibandingkan dengan tanda klien. Jadi pertanyaannya: Jenis data apa yang digabungkan dengan 'kunci api' yang tidak diketahui orang lain selain klien dan server? Tanda = api_key + apa?
AC
1
Anda benar, @ACs. Bahkan jika kedua server (situs web dan API pihak ketiga) mengetahui rahasia yang sama, seseorang tidak dapat menghitung beberapa tanda tangan di server situs web dan kemudian memasukkan hasilnya ke dalam HTML / JavaScript, dan kemudian membuat browser meneruskannya ke API. Dengan melakukan itu, server lain mana pun dapat meminta HTML tersebut dari server web pertama, mendapatkan tanda tangan dari respons, dan menggunakannya dalam HTML di situs web mereka sendiri. (Menurut saya postingan di atas tidak menjawab pertanyaan tentang bagaimana kunci API publik di HTML bisa aman.)
Arjan
61

Kami mengungkap API yang hanya dapat digunakan mitra di domain yang mereka daftarkan dengan kami. Isinya sebagian untuk publik (tetapi sebaiknya hanya ditampilkan di domain yang kami ketahui), tetapi sebagian besar bersifat pribadi bagi pengguna kami. Begitu:

  • Untuk menentukan apa yang ditampilkan, pengguna kami harus masuk dengan kami, tetapi ini ditangani secara terpisah.

  • Untuk menentukan di mana data ditampilkan, kunci API publik digunakan untuk membatasi akses ke domain yang kita ketahui, dan yang terpenting untuk memastikan data pengguna pribadi tidak rentan terhadap CSRF .

Kunci API ini memang terlihat oleh siapa pun, kami tidak mengautentikasi mitra kami dengan cara lain, dan kami tidak memerlukan REFERER . Tetap saja, ini aman:

  1. Saat kami get-csrf-token.js?apiKey=abc123diminta:

    1. Cari kunci abc123di database dan dapatkan daftar domain yang valid untuk kunci itu.

    2. Cari cookie validasi CSRF. Jika tidak ada, buat nilai acak yang aman dan masukkan ke dalam cookie sesi khusus HTTP . Jika cookie memang ada, dapatkan nilai acak yang ada.

    3. Buat token CSRF dari kunci API dan nilai acak dari cookie, dan tanda tangani . (Daripada menyimpan daftar token di server, kami menandatangani nilainya. Kedua nilai akan dapat dibaca dalam token yang ditandatangani, itu bagus.)

    4. Setel tanggapan agar tidak di-cache, tambahkan cookie, dan kembalikan skrip seperti:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }

    Catatan:

    • Hal di atas tidak mencegah skrip sisi server memalsukan permintaan, tetapi hanya memastikan bahwa domain tersebut cocok jika diminta oleh browser.

    • The kebijakan asal yang sama untuk JavaScript memastikan bahwa browser tidak dapat menggunakan XHR (Ajax) untuk memuat dan kemudian memeriksa sumber JavaScript. Sebaliknya, browser biasa hanya dapat memuatnya menggunakan <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(atau yang setara dinamis), dan kemudian akan menjalankan kode tersebut. Tentu saja, server Anda tidak boleh mendukung Cross-Origin Resource Sharing atau JSONP untuk JavaScript yang dihasilkan.

    • Sebuah skrip browser dapat mengubah nilai document.domainsebelum memuat skrip di atas. Tetapi kebijakan asal yang sama hanya memungkinkan untuk memperpendek domain dengan menghapus prefiks, seperti menulis ulang subdomain.example.comke hanya example.com, atau myblog.wordpress.comke wordpress.com, atau di beberapa browser bahkan bbc.co.ukke co.uk.

    • Jika file JavaScript diambil menggunakan beberapa skrip sisi server maka server juga akan mendapatkan cookie. Namun, server pihak ketiga tidak dapat membuat browser pengguna mengaitkan cookie itu ke domain kami. Karenanya, token CSRF dan cookie validasi yang telah diambil menggunakan skrip sisi server, hanya dapat digunakan oleh panggilan sisi server berikutnya, bukan di browser. Namun, panggilan sisi server tersebut tidak akan pernah menyertakan cookie pengguna, dan karenanya hanya dapat mengambil data publik. Ini adalah data yang sama yang dapat dikikis oleh skrip sisi server dari situs web mitra secara langsung.

  2. Saat pengguna masuk, setel beberapa cookie pengguna dengan cara apa pun yang Anda suka. (Pengguna mungkin sudah masuk sebelum JavaScript diminta.)

  3. Semua permintaan API berikutnya ke server (termasuk permintaan GET dan JSONP) harus menyertakan token CSRF, cookie validasi CSRF, dan (jika masuk) cookie pengguna. Server sekarang dapat menentukan apakah permintaan tersebut dapat dipercaya:

    1. Kehadiran token CSRF yang valid memastikan JavaScript dimuat dari domain yang diharapkan, jika dimuat oleh browser.

    2. Kehadiran token CSRF tanpa cookie validasi menunjukkan adanya pemalsuan.

    3. Kehadiran token CSRF dan cookie validasi CSRF tidak memastikan apa pun: ini bisa berupa permintaan sisi server palsu, atau permintaan valid dari browser. (Tidak boleh merupakan permintaan dari browser yang dibuat dari domain yang tidak didukung.)

    4. Kehadiran cookie pengguna memastikan pengguna masuk, tetapi tidak memastikan pengguna adalah anggota dari mitra yang diberikan, atau bahwa pengguna melihat situs web yang benar.

    5. Kehadiran cookie pengguna tanpa cookie validasi CSRF menunjukkan adanya pemalsuan.

    6. Kehadiran cookie pengguna memastikan permintaan saat ini dibuat melalui browser. (Dengan asumsi pengguna tidak akan memasukkan kredensial mereka di situs web yang tidak dikenal, dan dengan asumsi kami tidak peduli tentang pengguna yang menggunakan kredensial mereka sendiri untuk membuat beberapa permintaan sisi server.) Jika kami juga memiliki cookie validasi CSRF, maka cookie validasi CSRF itu adalah juga diterima menggunakan browser. Selanjutnya, jika kita juga memiliki token CSRF dengan tanda tangan yang valid, dannomor acak dalam cookie validasi CSRF cocok dengan yang ada di token CSRF itu, kemudian JavaScript untuk token itu juga diterima selama permintaan yang sama sebelumnya saat cookie CSRF ditetapkan, karenanya juga menggunakan browser. Ini kemudian juga menyiratkan kode JavaScript di atas dijalankan sebelum token disetel, dan pada saat itu domain tersebut valid untuk kunci API yang diberikan.

      Jadi: server sekarang dapat dengan aman menggunakan kunci API dari token yang ditandatangani.

    7. Jika suatu saat server tidak mempercayai permintaan tersebut, maka 403 Forbidden akan dikembalikan. Widget dapat meresponsnya dengan menampilkan peringatan kepada pengguna.

Tidak perlu menandatangani cookie validasi CSRF, karena kami membandingkannya dengan token CSRF yang ditandatangani. Tidak menandatangani cookie membuat setiap permintaan HTTP lebih pendek, dan validasi server sedikit lebih cepat.

Token CSRF yang dihasilkan berlaku tanpa batas waktu, tetapi hanya dalam kombinasi dengan cookie validasi, sangat efektif hingga browser ditutup.

Kami dapat membatasi masa tanda tangan token. Kami dapat menghapus cookie validasi CSRF saat pengguna logout, untuk memenuhi rekomendasi OWASP . Dan untuk tidak membagikan nomor acak per pengguna di antara beberapa mitra, seseorang dapat menambahkan kunci API ke nama cookie. Tetapi meskipun demikian seseorang tidak dapat dengan mudah menyegarkan cookie validasi CSRF ketika token baru diminta, karena pengguna mungkin menjelajahi situs yang sama di beberapa jendela, berbagi satu cookie (yang, saat menyegarkan, akan diperbarui di semua jendela, setelah itu Token JavaScript di jendela lain tidak lagi cocok dengan cookie tunggal itu).

Bagi mereka yang menggunakan OAuth, lihat juga OAuth dan Widget Sisi Klien , dari mana saya mendapatkan ide JavaScript. Untuk penggunaan API di sisi server , di mana kami tidak dapat mengandalkan kode JavaScript untuk membatasi domain, kami menggunakan kunci rahasia alih-alih kunci API publik.

Arjan
sumber
1
Saat menggunakan CORS, mungkin seseorang dapat memperpanjangnya dengan aman. Alih-alih hal di atas, saat menangani OPTIONSpermintaan yang dilakukan sebelumnya dengan beberapa kunci API publik di URL, server mungkin memberi tahu browser domain mana yang diizinkan (atau membatalkan permintaan). Berhati-hatilah karena beberapa permintaan tidak memerlukan permintaan pra-penerbangan, atau tidak akan menggunakan CORS sama sekali , dan CORS memerlukan IE8 +. Jika beberapa pengganti Flash digunakan untuk IE7, mungkin beberapa dinamika crossdomain.xmldapat membantu mencapai hal yang sama untuk itu. Kami belum mencoba CORS / Flash.
Arjan
10

Pertanyaan ini memiliki jawaban yang diterima tetapi hanya untuk memperjelas, otentikasi rahasia bersama berfungsi seperti ini:

  1. Klien memiliki kunci publik, ini dapat dibagikan dengan siapa saja, tidak masalah, jadi Anda dapat menyematkannya dalam javascript. Ini digunakan untuk mengidentifikasi pengguna di server.
  2. Server memiliki kunci rahasia dan rahasia ini HARUS dilindungi. Oleh karena itu, otentikasi kunci bersama mengharuskan Anda untuk melindungi kunci rahasia Anda. Jadi klien javascript publik yang terhubung langsung ke layanan lain tidak dimungkinkan karena Anda memerlukan perantara server untuk melindungi rahasianya.
  3. Permintaan tanda server menggunakan beberapa algoritme yang menyertakan kunci rahasia (kunci rahasia seperti garam) dan sebaiknya stempel waktu kemudian mengirimkan permintaan tersebut ke layanan. Stempel waktunya adalah untuk mencegah serangan "replay". Tanda tangan permintaan hanya berlaku sekitar n detik. Anda dapat memeriksanya di server dengan mendapatkan header timestamp yang seharusnya berisi nilai timestamp yang disertakan dalam tanda tangan. Jika stempel waktu tersebut kedaluwarsa, permintaan gagal.
  4. Layanan mendapatkan permintaan yang tidak hanya berisi tanda tangan tetapi juga semua bidang yang ditandatangani dalam teks biasa.
  5. Layanan kemudian menandatangani permintaan dengan cara yang sama menggunakan kunci rahasia bersama dan membandingkan tanda tangan.
chris
sumber
Benar, tetapi sesuai desain jawaban Anda tidak mengekspos kunci API. Namun, dalam beberapa API API key adalah terlihat publik, dan bahwa itu apa pertanyaannya adalah tentang: "permintaan untuk operasi layanan sisa [...] dilakukan melalui javascript (XHR / Ajax)" . (Jawaban yang diterima salah tentang itu juga, saya rasa; poin 2 Anda jelas tentang itu, bagus.)
Arjan
1

Saya kira yang Anda maksud adalah kunci sesi, bukan kunci API. Masalah itu diwarisi dari protokol http dan dikenal sebagai Session hijacking . "Solusi" yang normal adalah, seperti di situs web manapun, mengubah ke https.

Untuk menjalankan layanan REST aman Anda harus mengaktifkan https, dan mungkin otentikasi klien. Tapi bagaimanapun, ini di luar ide REST. REST tidak pernah berbicara tentang keamanan.

PeterMmm
sumber
8
Sebenarnya maksudku kuncinya. Jika saya ingat dengan benar, untuk menggunakan API, Anda meneruskan kunci dan rahasia API ke layanan lainnya untuk mengautentikasi, benar? Saya tahu setelah melewati kabel, itu akan dienkripsi oleh SSL, tetapi sebelum dikirim, itu terlihat sempurna oleh kode klien yang menggunakannya ...
tjans
1

Apa yang ingin Anda lakukan di sisi server adalah menghasilkan id sesi kedaluwarsa yang dikirim kembali ke klien saat login atau signup. Klien kemudian dapat menggunakan id sesi itu sebagai rahasia bersama untuk menandatangani permintaan berikutnya.

ID sesi hanya dilewatkan satu kali dan ini HARUS melalui SSL.

Lihat contoh di sini

Gunakan nonce dan stempel waktu saat menandatangani permintaan untuk mencegah pembajakan sesi.

Iain Porter
sumber
1
Tapi bagaimana bisa ada login ketika pihak ketiga menggunakan API Anda? Jika pengguna akan login, maka semuanya mudah: cukup gunakan sesi? Tetapi ketika situs web lain perlu mengautentikasi ke API Anda, itu tidak membantu. (Juga, ini baunya sangat mirip dengan mempromosikan blog Anda.)
Arjan
1

Saya akan mencoba menjawab pertanyaan tersebut dalam konteks aslinya. Jadi pertanyaannya adalah "Apakah kunci rahasia (API) aman untuk ditempatkan dalam JavaScript.

Menurut pendapat saya, ini sangat tidak aman karena mengalahkan tujuan otentikasi antar sistem. Karena kunci akan diekspos ke pengguna, pengguna dapat mengambil informasi yang tidak diizinkan. Karena pada umumnya otentikasi komunikasi istirahat hanya berdasarkan pada API Key.

Solusi menurut saya adalah bahwa panggilan JavaScript pada dasarnya meneruskan permintaan ke komponen server internal yang bertanggung jawab untuk melakukan panggilan istirahat. Komponen server internal katakanlah Servlet akan membaca kunci API dari sumber yang aman seperti sistem file berbasis izin, memasukkan ke header HTTP dan membuat panggilan istirahat eksternal.

Saya harap ini membantu.

Pengembang MG
sumber