Praktik terbaik SPA untuk otentikasi dan manajemen sesi

309

Ketika membangun aplikasi gaya SPA menggunakan kerangka kerja seperti Angular, Ember, React, dll. Apa yang orang yakini sebagai beberapa praktik terbaik untuk otentikasi dan manajemen sesi? Saya dapat memikirkan beberapa cara untuk mempertimbangkan mendekati masalah.

  1. Perlakukan itu tidak berbeda dengan otentikasi dengan aplikasi web biasa dengan asumsi API dan UI memiliki domain asal yang sama.

    Ini kemungkinan akan melibatkan memiliki cookie sesi, penyimpanan sesi sisi server dan mungkin beberapa titik akhir API sesi yang dapat dicapai UI web terautentikasi untuk mendapatkan informasi pengguna saat ini untuk membantu dengan personalisasi atau bahkan mungkin menentukan peran / kemampuan di sisi klien. Server masih akan memberlakukan aturan yang melindungi akses ke data tentu saja, UI hanya akan menggunakan informasi ini untuk menyesuaikan pengalaman.

  2. Perlakukan seperti layaknya klien pihak ketiga yang menggunakan API publik dan autentikasi dengan semacam sistem token yang mirip dengan OAuth. Mekanisme token ini akan digunakan oleh UI klien untuk mengotentikasi setiap permintaan yang dibuat ke API server.

Saya tidak terlalu ahli di sini, tetapi # 1 tampaknya cukup untuk sebagian besar kasus, tetapi saya benar-benar ingin mendengar pendapat yang lebih berpengalaman.

Chris Nicola
sumber
Saya perfer dengan cara ini, stackoverflow.com/a/19820685/454252
allenhwkim

Jawaban:

478

Pertanyaan ini telah ditanggapi, dalam bentuk yang sedikit berbeda, panjangnya, di sini:

Otentikasi tenang

Tapi ini mengatasinya dari sisi server. Mari kita lihat ini dari sisi klien. Namun, sebelum kita melakukan itu, ada pembuka penting:

Javascript Crypto Tidak Ada Harapan

Artikel Matasano tentang ini terkenal, tetapi pelajaran yang terkandung di dalamnya cukup penting:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Untuk meringkas:

  • Serangan man-in-the-middle dengan mudah dapat mengganti kode crypto Anda <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Serangan man-in-the-middle sepele terhadap halaman yang melayani sumber daya apa pun melalui koneksi non-SSL.
  • Setelah Anda memiliki SSL, Anda menggunakan crypto nyata.

Dan untuk menambahkan akibat wajar saya sendiri:

  • Serangan XSS yang berhasil dapat mengakibatkan penyerang mengeksekusi kode pada browser klien Anda, bahkan jika Anda menggunakan SSL - jadi bahkan jika Anda memiliki setiap lubang palka, browser Anda crypto masih bisa gagal jika penyerang Anda menemukan cara untuk mengeksekusi kode javascript di browser orang lain.

Ini membuat banyak skema otentikasi TENANG mustahil atau konyol jika Anda berniat untuk menggunakan klien JavaScript. Mari lihat!

HTTP Basic Auth

Pertama dan terpenting, HTTP Basic Auth. Skema yang paling sederhana: cukup berikan nama dan kata sandi dengan setiap permintaan.

Ini, tentu saja, benar-benar memerlukan SSL, karena Anda memberikan nama dan kata sandi yang disandikan Base64 (reversibel) dengan setiap permintaan. Siapa pun yang mendengarkan di saluran dapat mengekstraksi nama pengguna dan kata sandi sepele. Sebagian besar argumen "Basic Auth is insecure" berasal dari tempat "Basic Auth over HTTP" yang merupakan ide buruk.

Peramban menyediakan dukungan HTTP Basic Auth yang dipanggang, tetapi itu jelek dan Anda seharusnya tidak menggunakannya untuk aplikasi Anda. Namun alternatifnya adalah menyembunyikan nama pengguna dan kata sandi dalam JavaScript.

Ini adalah solusi yang paling tenang. Server tidak memerlukan pengetahuan tentang keadaan apa pun dan mengotentikasi setiap interaksi individu dengan pengguna. Beberapa penggemar REST (kebanyakan strawmen) bersikeras bahwa mempertahankan segala bentuk negara adalah bid'ah dan akan berbusa di mulut jika Anda memikirkan metode otentikasi lainnya. Ada manfaat teoretis untuk kepatuhan standar seperti ini - didukung oleh Apache di luar kotak - Anda dapat menyimpan objek Anda sebagai file dalam folder yang dilindungi oleh file .htaccess jika hati Anda menginginkannya!

The masalah ? Anda caching di sisi klien nama pengguna dan kata sandi. Hal ini memberikan celah yang lebih baik bagi evil.ru - bahkan kerentanan XSS yang paling dasar dapat mengakibatkan klien mengirimkan nama pengguna dan kata sandi ke server jahat. Anda dapat mencoba mengurangi risiko ini dengan hashing dan salting password, tetapi ingat: JavaScript Crypto is Hopeless . Anda dapat mengurangi risiko ini dengan menyerahkannya ke dukungan Auth Basic Browser, tetapi .. jelek seperti dosa, seperti yang disebutkan sebelumnya.

HTTP Digest Auth

Apakah otentikasi Digest dapat dilakukan dengan jQuery?

Auth yang lebih "aman", ini adalah tantangan hash permintaan / respons. Kecuali JavaScript, Crypto tidak memiliki harapan , sehingga hanya berfungsi di atas SSL dan Anda masih harus men-cache nama pengguna dan kata sandi di sisi klien, membuatnya lebih rumit daripada HTTP Auth Dasar tapi tidak lebih aman .

Otentikasi kueri dengan Parameter Tanda Tangan Tambahan.

Auth lain yang lebih "aman", di mana Anda mengenkripsi parameter Anda dengan data nonce dan timing (untuk melindungi terhadap serangan repeat dan timing) dan mengirimkan. Salah satu contoh terbaik dari hal ini adalah protokol OAuth 1.0, yang sejauh yang saya tahu, merupakan cara yang sangat sulit untuk mengimplementasikan otentikasi pada server REST.

http://tools.ietf.org/html/rfc5849

Oh, tapi tidak ada klien OAuth 1.0 untuk JavaScript. Mengapa?

JavaScript Crypto Tidak Ada Harapan , ingat. JavaScript tidak dapat berpartisipasi dalam OAuth 1.0 tanpa SSL, dan Anda masih harus menyimpan nama pengguna dan kata sandi klien secara lokal - yang menempatkan ini dalam kategori yang sama dengan Digest Auth - ini lebih rumit daripada HTTP Basic Auth tetapi tidak lebih aman .

Token

Pengguna mengirimkan nama pengguna dan kata sandi, dan sebagai gantinya mendapat token yang dapat digunakan untuk mengotentikasi permintaan.

Ini sedikit lebih aman daripada HTTP Basic Auth, karena begitu transaksi nama pengguna / kata sandi selesai, Anda dapat membuang data sensitif. Ini juga kurang tenang, karena token merupakan "negara" dan membuat implementasi server lebih rumit.

SSL Masih

Namun, masalahnya, Anda masih harus mengirim nama pengguna dan kata sandi awal itu untuk mendapatkan token. Informasi sensitif masih menyentuh JavaScript Anda yang dapat dikompromikan.

Untuk melindungi kredensial pengguna Anda, Anda masih harus menjaga penyerang keluar dari JavaScript Anda, dan Anda masih perlu mengirim nama pengguna dan kata sandi melalui kabel. Diperlukan SSL.

Token Kadaluwarsa

Sudah umum untuk menerapkan kebijakan token seperti "hei, ketika token ini sudah terlalu lama, buang dan buat pengguna mengautentikasi lagi." atau "Saya cukup yakin bahwa satu-satunya alamat IP yang diizinkan menggunakan token ini adalah XXX.XXX.XXX.XXX". Banyak dari kebijakan ini adalah ide yang cukup bagus.

Perapian

Namun, menggunakan token Tanpa SSL masih rentan terhadap serangan yang disebut 'sidejacking': http://codebutler.github.io/firesheep/

Penyerang tidak mendapatkan kredensial pengguna Anda, tetapi mereka masih bisa berpura-pura menjadi pengguna Anda, yang bisa sangat buruk.

tl; dr: Mengirim token yang tidak dienkripsi di atas kawat berarti bahwa penyerang dapat dengan mudah menangkap token itu dan berpura-pura menjadi pengguna Anda. FireSheep adalah program yang membuatnya sangat mudah.

Zona Terpisah, Lebih Aman

Semakin besar aplikasi yang Anda jalankan, semakin sulit untuk memastikan bahwa mereka tidak akan dapat menyuntikkan beberapa kode yang mengubah cara Anda memproses data sensitif. Apakah Anda benar-benar mempercayai CDN Anda? Pengiklan Anda? Basis kode Anda sendiri?

Umum untuk perincian kartu kredit dan kurang umum untuk nama pengguna dan kata sandi - beberapa pelaksana menyimpan 'entri data sensitif' pada halaman terpisah dari aplikasi mereka yang lain, halaman yang dapat dikontrol dan dikunci sebaik mungkin, lebih disukai yang sulit untuk menipu pengguna dengan.

Cookie (artinya Token)

Dimungkinkan (dan umum) untuk memasukkan token otentikasi ke dalam cookie. Ini tidak mengubah salah satu properti auth dengan token, itu lebih merupakan hal yang mudah. Semua argumen sebelumnya masih berlaku.

Sesi (masih berarti Token)

Session Auth hanyalah otentikasi Token, tetapi dengan beberapa perbedaan yang membuatnya tampak seperti hal yang sedikit berbeda:

  • Pengguna mulai dengan token yang tidak diautentikasi.
  • Backend mempertahankan objek 'state' yang terkait dengan token pengguna.
  • Token disediakan dalam cookie.
  • Lingkungan aplikasi memisahkan detail dari Anda.

Selain itu, sebenarnya tidak ada bedanya dengan Token Auth.

Ini mengembara lebih jauh dari implementasi RESTful - dengan objek negara Anda akan semakin jauh di jalur RPC biasa di server stateful.

OAuth 2.0

OAuth 2.0 melihat masalah "Bagaimana Perangkat Lunak A memberi akses Perangkat Lunak B ke data Pengguna X tanpa Perangkat B memiliki akses ke kredensial login Pengguna X."

Implementasinya sangat sederhana hanya cara standar bagi pengguna untuk mendapatkan token, dan kemudian untuk layanan pihak ketiga untuk pergi "ya, pengguna ini dan kecocokan token ini, dan Anda bisa mendapatkan beberapa data mereka dari kami sekarang."

Namun, pada dasarnya, OAuth 2.0 hanyalah sebuah protokol token. Ini menunjukkan properti yang sama dengan protokol token lainnya - Anda masih membutuhkan SSL untuk melindungi token tersebut - itu hanya mengubah cara token tersebut dihasilkan.

Ada dua cara yang OAuth 2.0 dapat membantu Anda:

  • Memberikan Otentikasi / Informasi kepada Orang Lain
  • Mendapatkan Otentikasi / Informasi dari Orang Lain

Tapi ketika tiba saatnya, Anda hanya ... menggunakan token.

Kembali ke pertanyaan Anda

Jadi, pertanyaan yang Anda tanyakan adalah "haruskah saya menyimpan token saya di cookie dan manajemen sesi otomatis lingkungan saya mengurus detailnya, atau haruskah saya menyimpan token saya di Javascript dan menangani detail itu sendiri?"

Dan jawabannya adalah: lakukan apa pun yang membuat Anda bahagia .

Masalahnya tentang manajemen sesi otomatis, adalah ada banyak keajaiban terjadi di belakang layar untuk Anda. Seringkali lebih baik untuk mengendalikan detail-detail itu sendiri.

Saya berusia 21 tahun sehingga SSL adalah ya

Jawaban lainnya adalah: Gunakan https untuk semuanya atau perampok akan mencuri kata sandi dan token pengguna Anda.

Curtis Lassam
sumber
3
Jawaban yang bagus Saya menghargai kesetaraan antara sistem token auth dan cookie dasar auth (yang sering dibangun ke dalam kerangka web). Itulah yang saya cari. Saya menghargai Anda meliput begitu banyak masalah potensial untuk dipertimbangkan juga. Bersulang!
Chris Nicola
11
Saya tahu ini sudah lama tetapi saya bertanya-tanya apakah ini harus diperluas untuk memasukkan JWT? auth0.com/blog/2014/01/07/...
Chris Nicola
14
Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST mengharuskan server menjadi stateless. Sisi klien yang disimpan token tidak mewakili keadaan dengan cara apa pun yang berarti untuk server. (2) Kode sisi server yang sedikit lebih rumit tidak ada hubungannya dengan RESTfulness.
anjing sup
10
lol_nope_send_it_to_me_insteadSaya menyukai nama fungsi ini: D
Leo
6
Satu hal yang Anda abaikan: Cookie adalah XSS aman ketika ditandai httpOnly, dan dapat dikunci lebih lanjut dengan secure dan samesite. Dan penanganan cookie sudah ada jauh lebih lama === lebih banyak pertempuran mengeras. Mengandalkan JS dan penyimpanan lokal untuk menangani keamanan token adalah permainan yang bodoh.
Martijn Pieters
57

Anda dapat meningkatkan keamanan dalam proses otentikasi dengan menggunakan JWT (JSON Web Tokens) dan SSL / HTTPS.

ID Auth / Sesi Dasar dapat dicuri melalui:

  • Serangan MITM (Man-In-The-Middle) - tanpa SSL / HTTPS
  • Seorang penyusup mendapatkan akses ke komputer pengguna
  • XSS

Dengan menggunakan JWT Anda mengenkripsi detail otentikasi pengguna dan menyimpannya di klien, dan mengirimkannya bersama setiap permintaan ke API, di mana server / API memvalidasi token. Itu tidak dapat didekripsi / dibaca tanpa kunci pribadi (yang disimpan secara diam-diam oleh server / API). Baca pembaruan .

Aliran baru (lebih aman) adalah:

Gabung

  • Pengguna masuk dan mengirim kredensial masuk ke API (lebih dari SSL / HTTPS)
  • API menerima kredensial masuk
  • Jika valid:
    • Daftarkan sesi baru di pembaruan Baca basis data
    • Enkripsi ID Pengguna, ID Sesi, alamat IP, cap waktu, dll. Dalam JWT dengan kunci pribadi.
  • API mengirimkan token JWT kembali ke klien (melalui SSL / HTTPS)
  • Klien menerima token JWT dan menyimpannya di Penyimpanan / cookie lokal

Setiap permintaan ke API

  • Pengguna mengirimkan permintaan HTTP ke API (melalui SSL / HTTPS) dengan token JWT yang disimpan di header HTTP
  • API membaca header HTTP dan mendekripsi token JWT dengan kunci privatnya
  • API memvalidasi token JWT, cocok dengan alamat IP dari permintaan HTTP dengan yang ada di token JWT dan memeriksa apakah sesi telah kedaluwarsa
  • Jika valid:
    • Kembalikan respons dengan konten yang diminta
  • Jika tidak valid:
    • Melempar pengecualian (403/401)
    • Tandai intrusi dalam sistem
    • Kirim email peringatan kepada pengguna.

Diperbarui 30.07.15:

Muatan / klaim JWT sebenarnya dapat dibaca tanpa kunci pribadi (rahasia) dan tidak aman untuk menyimpannya di Penyimpanan lokal. Saya minta maaf tentang pernyataan palsu ini. Namun mereka tampaknya bekerja pada standar JWE (JSON Web Encryption) .

Saya menerapkan ini dengan menyimpan klaim (userID, exp) di JWT, menandatanganinya dengan kunci pribadi (rahasia) API / backend hanya tahu tentang dan menyimpannya sebagai cookie HttpOnly aman pada klien. Dengan cara itu tidak dapat dibaca melalui XSS dan tidak dapat dimanipulasi, jika tidak JWT gagal verifikasi tanda tangan. Juga dengan menggunakan cookie HttpOnly yang aman , Anda memastikan bahwa cookie tersebut dikirim hanya melalui permintaan HTTP (tidak dapat diakses ke skrip) dan hanya dikirim melalui koneksi aman (HTTPS).

Diperbarui 17.07.16:

JWT pada dasarnya tidak memiliki kewarganegaraan. Itu berarti mereka membatalkan / kedaluwarsa sendiri. Dengan menambahkan SessionID di klaim token yang Anda buat statusnya, karena validitasnya sekarang tidak hanya bergantung pada verifikasi tanda tangan dan tanggal kedaluwarsa, itu juga tergantung pada status sesi di server. Namun kelebihannya adalah Anda dapat membatalkan token / sesi dengan mudah, yang sebelumnya tidak dapat Anda lakukan dengan JWT yang tidak memiliki kewarganegaraan.

Gaui
sumber
1
Pada akhirnya JWT masih 'hanya tanda' dari sudut pandang keamanan saya pikir. Server masih dapat mengaitkan id pengguna, alamat IP, stempel waktu, dll. Dengan token sesi buram dan tidak akan lebih atau kurang aman daripada JWT. Namun, sifat kewarganegaraan JWT memang membuat implementasi lebih mudah.
James
1
@ James JWT memiliki keuntungan karena dapat diverifikasi dan mampu membawa perincian penting. Ini cukup berguna untuk berbagai skenario API, seperti di mana auth lintas-domain diperlukan. Sesuatu sesi tidak akan sebaik untuk. Ini juga merupakan spesifikasi yang ditentukan (atau setidaknya sedang dalam proses), yang berguna untuk implementasi. Itu bukan untuk mengatakan itu lebih baik daripada implementasi token baik lainnya, tetapi itu didefinisikan dengan baik dan nyaman.
Chris Nicola
1
@ Chris Ya, saya setuju dengan semua poin Anda. Namun, aliran yang dijelaskan dalam jawaban di atas tidak inheren aliran yang lebih aman seperti yang diklaim karena penggunaan JWT. Selain itu, JWT tidak dapat dibatalkan dalam skema yang dijelaskan di atas kecuali Anda mengaitkan pengidentifikasi dengan JWT dan menyimpan status di server. Kalau tidak, Anda perlu mendapatkan JWT baru secara rutin dengan meminta nama pengguna / kata sandi (pengalaman pengguna yang buruk) atau mengeluarkan JWT dengan waktu kedaluwarsa yang sangat lama (buruk jika token dicuri).
James
1
Jawaban saya tidak 100% benar, karena JWT sebenarnya dapat didekripsi / dibaca tanpa kunci pribadi (rahasia) dan tidak aman untuk menyimpannya di penyimpanan lokal. Saya menerapkan ini dengan menyimpan klaim (userID, exp) di JWT, menandatanganinya dengan kunci pribadi (rahasia) API / backend hanya tahu tentang dan menyimpannya sebagai cookie HttpOnly pada klien. Dengan begitu tidak bisa dibaca oleh XSS. Tetapi Anda harus menggunakan HTTPS karena token bisa dicuri dengan serangan MITM. Saya akan memperbarui jawaban saya untuk merenungkan hal ini.
Gaui
1
@vsenko Cookie dikirim dengan setiap permintaan dari klien. Anda tidak mengakses cookie dari JS, itu terkait dengan setiap permintaan HTTP dari klien ke API.
Gaui
7

Saya akan memilih yang kedua, sistem token.

Tahukah Anda tentang bara-auth atau bara-sederhana-auth ? Keduanya menggunakan sistem berbasis token, seperti status bara-sederhana-auth:

Pustaka yang ringan dan tidak mencolok untuk menerapkan otentikasi berbasis token dalam aplikasi Ember.js. http://ember-simple-auth.simplabs.com

Mereka memiliki manajemen sesi, dan mudah untuk dihubungkan ke proyek yang ada juga.

Ada juga Ember App Kit versi contoh bara-simple-auth: Contoh kerja bara-app-kit menggunakan bara-simple-auth untuk otentikasi OAuth2.

DelphiLynx
sumber