passport.js SANGAT auth


155

Bagaimana seseorang menangani otentikasi (lokal dan Facebook, misalnya) menggunakan passport.js, melalui RESTful API alih-alih melalui antarmuka web?

Masalah khusus adalah menangani pengalihan data dari panggilan balik ke respons RESTful (JSON) vs menggunakan res.send khas ({data: req.data}), menyiapkan titik akhir awal / login yang mengalihkan ke Facebook (/ login tidak dapat diakses melalui AJAX, karena itu bukan respons JSON - itu adalah pengalihan ke Facebook dengan panggilan balik).

Saya telah menemukan https://github.com/halrobertson/test-restify-passport-facebook , tapi saya kesulitan memahaminya.

Lebih lanjut, bagaimana cara passport.js menyimpan kredensial autentik? Server (atau layanannya?) Didukung oleh MongoDB, dan saya berharap kredensial (login & hash dari pw) disimpan di sana, tapi saya tidak tahu apakah passport.js memiliki kemampuan seperti ini.


Karena Anda baru mengenal Node, mulailah dengan mudah dan lihat contoh aplikasi untuk passport-facebook. Setelah Anda berhasil, langkah selanjutnya adalah mulai memahami cara kerja Paspor, dan cara menyimpan kredensial. Menyambungkannya ke Restify ( lihat di sini untuk versi terbaru dari yang Anda sebutkan) akan menjadi salah satu langkah terakhir (atau Anda bisa mengimplementasikan antarmuka REST di Express).
robertklep

Jawaban:


312

Ada banyak pertanyaan yang diajukan di sini, dan tampaknya meskipun pertanyaan-pertanyaan itu ditanyakan dalam konteks Node dan paspor. Namun pertanyaan sebenarnya lebih banyak tentang alur kerja daripada bagaimana melakukan ini dengan teknologi tertentu.

Mari kita gunakan contoh setup @Keith, dimodifikasi sedikit untuk keamanan tambahan:

  • Server web di https://example.commelayani aplikasi klien Javascript satu halaman
  • Layanan web RESTful di https://example.com/apimenyediakan dukungan server ke aplikasi klien kaya
  • Server diimplementasikan dalam Node dan passport.js.
  • Server memiliki basis data (apa pun) dengan tabel "pengguna".
  • Nama pengguna / kata sandi dan Facebook Connect ditawarkan sebagai opsi otentikasi
  • Klien kaya membuat permintaan REST https://example.com/api
  • Mungkin ada klien lain (aplikasi telepon, misalnya) yang menggunakan layanan web di https://example.com/apitetapi tidak tahu tentang server web di https://example.com.

Perhatikan bahwa saya menggunakan HTTP aman. Ini menurut pendapat saya suatu keharusan untuk setiap layanan yang tersedia di tempat terbuka, karena informasi sensitif seperti kata sandi dan token otorisasi lewat antara klien dan server.

Otentikasi nama pengguna / kata sandi

Mari kita lihat cara kerja otentikasi lama yang sederhana terlebih dahulu.

  • Pengguna terhubung ke https://example.com
  • Server menyajikan aplikasi Javascript yang kaya yang merender halaman awal. Entah di halaman itu ada formulir login.
  • Banyak bagian dari aplikasi satu halaman ini belum diisi dengan data karena pengguna tidak masuk. Semua bagian ini memiliki pendengar acara pada acara "masuk". Semua ini adalah hal-hal sisi klien, server tidak mengetahui peristiwa ini.
  • Pengguna memasukkan login dan kata sandi dan menekan tombol kirim, yang memicu penangan Javascript untuk merekam nama pengguna dan kata sandi dalam variabel sisi klien. Kemudian pawang ini memicu acara "masuk". Sekali lagi, ini semua tindakan sisi klien, kredensial belum dikirim ke server .
  • Pendengar acara "masuk" dipanggil. Masing-masing sekarang perlu mengirim satu atau lebih permintaan ke RESTful API di https://example.com/apiuntuk mendapatkan data spesifik pengguna untuk di-render di halaman. Setiap permintaan tunggal yang mereka kirim ke layanan web akan menyertakan nama pengguna dan kata sandi, mungkin dalam bentuk otentikasi HTTP Basic , karena layanan yang RESTful tidak diizinkan untuk mempertahankan status klien dari satu permintaan ke yang berikutnya. Karena layanan web menggunakan HTTP aman, kata sandi dienkripsi dengan aman selama transit.
  • Layanan web di https://example.com/apimenerima banyak permintaan individu, masing-masing dengan informasi otentikasi. Nama pengguna dan kata sandi dalam setiap permintaan diperiksa terhadap basis data pengguna dan jika ditemukan benar, fungsi yang diminta dijalankan dan data dikembalikan ke klien dalam format JSON. Jika nama pengguna dan kata sandi tidak cocok dengan kesalahan dikirim ke klien dalam bentuk kode kesalahan HTTP 401.
  • Alih-alih memaksa klien untuk mengirim nama pengguna dan kata sandi dengan setiap permintaan, Anda dapat memiliki fungsi "get_access_token" di layanan RESTful Anda yang mengambil nama pengguna dan kata sandi dan merespons dengan token, yang merupakan semacam hash kriptografi yang unik dan memiliki kedaluwarsa tanggal yang terkait dengannya. Token ini disimpan dalam database dengan setiap pengguna. Kemudian klien mengirimkan token akses dalam permintaan berikutnya. Token akses kemudian akan divalidasi terhadap basis data alih-alih nama pengguna dan kata sandi.
  • Aplikasi klien non browser seperti aplikasi telepon melakukan hal yang sama seperti di atas, mereka meminta pengguna untuk memasukkan kredensial mereka, kemudian mengirimkannya (atau token akses yang dihasilkan dari mereka) dengan setiap permintaan ke layanan web.

Poin penting yang diambil dari contoh ini adalah bahwa layanan web RESTful memerlukan otentikasi dengan setiap permintaan .

Lapisan keamanan tambahan dalam skenario ini akan menambahkan otorisasi aplikasi klien selain otentikasi pengguna. Misalnya, jika Anda memiliki aplikasi klien web, iOS dan Android semua menggunakan layanan web, Anda mungkin ingin server mengetahui mana dari ketiga klien dari permintaan yang diberikan, terlepas dari siapa pengguna yang diautentikasi. Ini dapat memungkinkan layanan web Anda untuk membatasi fungsi tertentu untuk klien tertentu. Untuk ini, Anda bisa menggunakan kunci API dan rahasia, lihat jawaban ini untuk beberapa ide tentang itu.

Otentikasi Facebook

Alur kerja di atas tidak berfungsi untuk koneksi Facebook karena login melalui Facebook memiliki pihak ketiga, Facebook sendiri. Prosedur masuk mengharuskan pengguna untuk diarahkan ke situs web Facebook di mana kredensial dimasukkan di luar kendali kami.

Jadi mari kita lihat bagaimana hal berubah:.

  • Pengguna terhubung ke https://example.com
  • Server menyajikan aplikasi Javascript yang kaya yang merender halaman awal. Di halaman tersebut ada formulir login yang menyertakan tombol "Login dengan Facebook".
  • Pengguna mengklik tombol "Masuk dengan Facebook", yang hanya berupa tautan yang dialihkan ke (misalnya) https://example.com/auth/facebook.
  • The https://example.com/auth/facebookrute ditangani oleh passport.js (lihat dokumentasi )
  • Semua yang dilihat pengguna adalah bahwa halaman berubah dan sekarang mereka berada di halaman yang dihosting Facebook di mana mereka perlu login dan mengotorisasi aplikasi web kami. Ini sepenuhnya di luar kendali kami.
  • Pengguna masuk ke Facebook dan memberikan izin ke aplikasi kami, jadi Facebook sekarang mengarahkan kembali ke URL panggilan balik yang kami konfigurasikan di pengaturan passport.js, yang mengikuti contoh dalam dokumentasi ini adalahhttps://example.com/auth/facebook/callback
  • Handler passport.js untuk https://example.com/auth/facebook/callbackrute akan memanggil fungsi panggilan balik yang menerima token akses Facebook dan beberapa informasi pengguna dari Facebook, termasuk alamat email pengguna.
  • Dengan email kami dapat menemukan pengguna di basis data kami dan menyimpan token akses Facebook dengannya.
  • Hal terakhir yang Anda lakukan dalam panggilan balik Facebook adalah mengarahkan kembali ke aplikasi klien yang kaya, tetapi kali ini kami harus meneruskan nama pengguna dan token akses ke klien agar dapat menggunakannya. Ini dapat dilakukan dengan beberapa cara. Misalnya, variabel Javascript dapat ditambahkan ke halaman melalui mesin template sisi-server, atau cookie dapat dikembalikan dengan informasi ini. (terima kasih kepada @RyanKimber karena menunjukkan masalah keamanan dengan meneruskan data ini di URL, seperti yang saya sarankan pada awalnya).
  • Jadi sekarang kita memulai aplikasi satu halaman sekali lagi, tetapi klien memiliki nama pengguna dan token akses.
  • Aplikasi klien dapat memicu acara "masuk" dengan segera dan membiarkan berbagai bagian aplikasi meminta informasi yang mereka butuhkan dari layanan web.
  • Semua permintaan yang dikirim https://example.com/apiakan menyertakan token akses Facebook untuk otentikasi, atau token akses aplikasi sendiri yang dihasilkan dari token Facebook melalui fungsi "get_access_token" di REST API.
  • Aplikasi non-browser sedikit lebih sulit di sini, karena OAuth membutuhkan browser web untuk masuk. Untuk masuk dari ponsel atau aplikasi desktop, Anda harus memulai browser untuk melakukan pengalihan ke Facebook, dan yang lebih buruk, Anda perlu cara bagi browser untuk melewatkan token akses Facebook kembali ke aplikasi melalui beberapa mekanisme.

Saya harap ini menjawab sebagian besar pertanyaan. Tentu saja Anda dapat mengganti Facebook dengan Twitter, Google, atau layanan otentikasi berbasis OAuth lainnya.

Saya akan tertarik untuk mengetahui apakah seseorang memiliki cara yang lebih sederhana untuk menghadapi ini.


5
Terima kasih atas tanggapan terinci Anda. Hanya satu pertanyaan: Anda mengatakan itu Every single request they send to the web service will include the username and password, namun Anda mengatakannya you can have a "get_access_token" function in your RESTful service. Tampaknya bertentangan untuk mengatakan bahwa REST perlu stateless, tetapi menyimpan sisi akses token server adalah OK, karena tindakan menyimpan token akses berarti bahwa server sekarang stateful. Saya akan sangat menghargai klarifikasi atau pembenaran tentang ini. Terima kasih! :)
ryanrhee

6
Ketika saya memikirkan persyaratan kewarganegaraan, saya berpikir tentang keadaan untuk sesi tertentu dengan klien. Saya tidak melihat menyimpan data yang terkait dengan pengguna dalam database, seperti kata sandi atau token akses sebagai "status", setidaknya bukan "status sesi". Server Anda jelas perlu tahu siapa pengguna dan kata sandinya, tetapi ini adalah data aplikasi yang tidak terkait dengan suatu sesi. Yang mengatakan, banyak orang menggunakan cookie di layanan yang seharusnya RESTful, jadi seberapa ketat Anda ingin mematuhi spesifikasi REST benar-benar terserah masing-masing pelaksana.
Miguel

1
@Dexter: Dalam kasus login tradisional, pengguna memasukkan nama pengguna dan kata sandi ke dalam formulir dan ketika dia menekan tombol Kirim, informasi ini diposting ke server web. Dalam hal ini ini tidak terjadi, pengguna mengisi formulir dan ketika dia menekan Kirim Javascript handler (acara onClick di tombol kirim) menangkap data dan menyimpannya dalam konteks klien. Saya tidak memiliki contoh yang siap untuk ditampilkan, tetapi perhatikan bagian kedua dari tutorial ini di blog saya di mana saya akan menunjukkan bagaimana melakukannya: blog.miguelgrinberg.com/post/…
Miguel

2
Ini adalah penulisan yang dipikirkan dengan sangat baik, tetapi mengandung satu kesalahan atau pengawasan penting. Saat menangani login Facebook (atau Github, twitter, dll), akan lebih disukai untuk meneruskan token kembali ke klien dalam cookie dan kemudian menghapus cookie di sisi klien setelah ditemukan. Melewati token sebagai bagian dari string URL akan menambahkan URL ini ke riwayat browser dan (jika semuanya ditangani dengan tidak tepat) dapat menyebabkan URL ini diminta oleh browser. Entah membuat token terlihat. Siapa pun yang menyalin URL dapat menipu pengguna ini di aplikasi Anda.
Ryan Kimber

1
@ Nathan: auth dasar melalui https aman, jadi ya, itu adalah mekanisme yang dapat diterima.
Miguel

11

Saya sangat menghargai penjelasan @ Miguel dengan alur lengkap di setiap kasus, tetapi saya ingin menambahkan beberapa di bagian Otentikasi Facebook.

Facebook menyediakan Javascript SDK yang dapat Anda gunakan untuk mendapatkan token akses pada klien-akhir secara langsung, yang kemudian diteruskan ke server dan digunakan untuk menarik lebih lanjut semua informasi pengguna dari Facebook. Jadi Anda tidak perlu mengarahkan ulang pada dasarnya.

Selain itu, Anda juga dapat menggunakan titik akhir API yang sama untuk aplikasi seluler. Cukup gunakan Android / iOS SDK untuk Facebook, dapatkan Facebook access_token di sisi klien dan berikan ke server.

Mengenai sifat stateless seperti yang dijelaskan, ketika get_access_token digunakan untuk menghasilkan token dan diteruskan ke klien, token ini juga disimpan di server. Jadi sama baiknya dengan token sesi dan saya yakin ini membuatnya stateful?

Hanya 2 sen saya ..


1
Ini sangat penting, karena dengan cara ini Anda bisa melakukan auth satu halaman saja-ajax menggunakan facebook. Token sama sekali aman sebagai sesi auth, satu-satunya perbedaan adalah bahwa token dapat diteruskan ke domain lain, dan sesi hanya digunakan dalam satu domain tertentu. Saat menggunakan expressjs dan paspor, Anda dapat membuat api yang menyimpan status, dan memanfaatkan sesi auth.
jperelli

Api javascript sangat bagus jika Anda ingin mengautentikasi pengguna untuk melakukan tindakan terhadap Facebook, tetapi sia-sia jika Anda ingin memvalidasi pengguna terhadap server / database Anda sejauh yang saya tahu.
James Westgate

4
Anda juga dapat menggunakan metode yang dijelaskan oleh Miguel di atas, tetapi kemudian mengeluarkan token JWT Anda sendiri sebagai cookie ketika mengarahkan klien dalam panggilan balik auth. Hal ini memungkinkan yang terbaik dari kedua dunia - aplikasi satu halaman Anda dapat mementingkan hanya satu jenis otentikasi (JWT), sambil menjaga tingkat keamanan yang sama dan memberikan fleksibilitas untuk mendukung semua login sosial tanpa harus memanfaatkan API JavaScript khusus untuk setiap jejaring sosial (Facebook, Twitter, LinkedIn, Google, dll). Ini juga memungkinkan Anda mempertahankan dukungan gaya AJAX untuk nama pengguna / kata sandi dan akses REST.
Ryan Kimber

Facebook Javascript SDK saat ini tidak berfungsi dengan Chrome iOS. Mungkin masalah bagi sebagian orang.
demisx

@RyanKimber dapatkah Anda menulis git repo kecil atau serupa di mana ini dilakukan sebagai contoh saya benar-benar macet
Simon Dragsbæk

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.