Bagaimana menerapkan API REST yang aman dengan node.js

204

Saya mulai merencanakan API REST dengan node.js, express dan mongodb. API menyediakan data untuk situs web (area publik dan pribadi) dan mungkin nanti aplikasi seluler. Frontend akan dikembangkan dengan AngularJS.

Selama beberapa hari saya membaca banyak tentang mengamankan API REST, tetapi saya tidak mendapatkan solusi final. Sejauh yang saya mengerti adalah menggunakan HTTPS untuk memberikan keamanan dasar. Tapi bagaimana saya bisa melindungi API dalam kasus penggunaan itu:

  • Hanya pengunjung / pengguna situs web / aplikasi yang diizinkan untuk mendapatkan data untuk area publik situs web / aplikasi tersebut

  • Hanya pengguna yang diautentikasi dan diotorisasi yang diizinkan untuk mendapatkan data untuk area pribadi (dan hanya data, di mana pengguna diberikan izin)

Saat ini saya berpikir untuk hanya mengizinkan pengguna dengan sesi aktif untuk menggunakan API. Untuk mengotorisasi pengguna, saya akan menggunakan paspor dan untuk izin saya perlu menerapkan sesuatu untuk diri saya sendiri. Semua ada di atas HTTPS.

Adakah yang bisa memberikan praktik atau pengalaman terbaik? Apakah ada kekurangan dalam "arsitektur" saya?

tschiela
sumber
2
Saya menduga API hanya akan digunakan dari frontend yang Anda berikan? Dalam hal ini, menggunakan sesi untuk memastikan pengguna valid sepertinya merupakan solusi yang baik. Untuk izin, Anda bisa melihat simpul-peran .
robertklep
2
Apa yang akhirnya Anda lakukan untuk ini? Adakah kode pelat ketel (server / klien aplikasi seluler) yang dapat Anda bagikan?
Morteza Shahriari Nia

Jawaban:

176

Saya memiliki masalah yang sama dengan yang Anda gambarkan. Situs web yang saya bangun dapat diakses dari ponsel dan dari browser sehingga saya membutuhkan api untuk memungkinkan pengguna mendaftar, masuk, dan melakukan beberapa tugas tertentu. Selanjutnya, saya perlu mendukung skalabilitas, kode yang sama berjalan pada proses / mesin yang berbeda.

Karena pengguna dapat MENCIPTAKAN sumber daya (alias tindakan POST / PUT), Anda perlu mengamankan api Anda. Anda dapat menggunakan oauth atau Anda dapat membangun solusi Anda sendiri tetapi perlu diingat bahwa semua solusi dapat dipecahkan jika kata sandi itu benar-benar mudah ditemukan. Ide dasarnya adalah untuk mengotentikasi pengguna menggunakan nama pengguna, kata sandi, dan token, alias apitoken. Apitoken ini dapat dibuat menggunakan node-uuid dan kata sandi dapat di-hash menggunakan pbkdf2

Kemudian, Anda perlu menyimpan sesi di suatu tempat. Jika Anda menyimpannya dalam memori di objek biasa, jika Anda membunuh server dan reboot lagi sesi akan dihancurkan. Juga, ini tidak dapat diskalakan. Jika Anda menggunakan haproxy untuk memuat keseimbangan antar mesin atau jika Anda hanya menggunakan pekerja, status sesi ini akan disimpan dalam satu proses sehingga jika pengguna yang sama dialihkan ke proses / mesin lain, ia perlu melakukan otentikasi lagi. Karena itu Anda perlu menyimpan sesi di tempat umum. Ini biasanya dilakukan dengan menggunakan redis.

Ketika pengguna diautentikasi (nama pengguna + kata sandi + apitoken) buat token lain untuk sesi ini, alias accesstoken. Sekali lagi, dengan node-uuid. Kirim kepada pengguna accesstoken dan userid. Userid (kunci) dan accesstoken (nilai) disimpan dalam redis dengan dan berakhir waktu, misalnya 1 jam.

Sekarang, setiap kali pengguna melakukan operasi menggunakan api sisanya, ia harus mengirim userid dan accesstoken.

Jika Anda mengizinkan pengguna untuk mendaftar menggunakan api sisanya, Anda harus membuat akun admin dengan admin apitoken dan menyimpannya di aplikasi seluler (mengenkripsi nama pengguna + kata sandi + apitoken) karena pengguna baru tidak akan memiliki apitoken ketika mereka mendaftar.

Web juga menggunakan api ini tetapi Anda tidak perlu menggunakan apitokens. Anda dapat menggunakan express dengan toko redis atau menggunakan teknik yang sama seperti dijelaskan di atas, tetapi melewati pemeriksaan apitoken dan kembali ke pengguna, userid + accesstoken dalam cookie.

Jika Anda memiliki area pribadi, bandingkan nama pengguna dengan pengguna yang diizinkan saat mereka mengautentikasi. Anda juga dapat menerapkan peran kepada pengguna.

Ringkasan:

diagram urutan

Alternatif tanpa apitoken adalah dengan menggunakan HTTPS dan untuk mengirim nama pengguna dan kata sandi di header Otorisasi dan cache nama pengguna di redis.

Gabriel Llamas
sumber
1
Saya juga menggunakan mongodb tetapi cukup mudah untuk mengelola jika Anda menyimpan sesi (accesstoken) menggunakan redis (menggunakan operasi atom). Apitoken dihasilkan di server ketika pengguna membuat akun dan mengirimkannya kembali ke pengguna. Kemudian, ketika pengguna ingin mengotentikasi, pengguna harus mengirim nama pengguna + kata sandi + apitoken (masukkan ke dalam tubuh http). Perlu diingat bahwa HTTP tidak mengenkripsi tubuh sehingga kata sandi dan apitoken dapat diendus. Gunakan HTTPS jika ini mengkhawatirkan Anda.
Gabriel Llamas
1
apa gunanya menggunakan apitoken? apakah ini kata sandi "sekunder"?
Salvatorelab
2
@TheBronx Apitoken memiliki 2 kasus penggunaan: 1) dengan apitoken Anda dapat mengontrol akses pengguna ke sistem Anda dan Anda dapat memantau dan membangun statistik setiap pengguna. 2) Ini adalah langkah pengamanan tambahan, kata sandi "sekunder".
Gabriel Llamas
1
Mengapa Anda harus mengirim id pengguna lagi dan lagi setelah otentikasi berhasil. Token harus menjadi satu-satunya rahasia yang Anda perlukan untuk melakukan panggilan API.
Axel Napolitano
1
Gagasan token - selain menyalahgunakannya untuk melacak aktivitas pengguna - adalah, bahwa pengguna idealnya tidak memerlukan nama pengguna dan kata sandi untuk menggunakan aplikasi: Token adalah kunci akses unik. Ini memungkinkan pengguna untuk menjatuhkan kunci apa saja kapan saja yang hanya memengaruhi aplikasi tetapi tidak ke akun pengguna. Untuk layanan web, token sangat tidak mudah - itu sebabnya login awal untuk sesi adalah tempat di mana pengguna mendapatkan token itu - untuk klien "biasa", token tidak masalah: Masukkan sekali dan Anda hampir selesai ;)
Axel Napolitano
22

Saya ingin berkontribusi kode ini sebagai solusi struktural untuk pertanyaan yang diajukan, sesuai (saya harap begitu) dengan jawaban yang diterima. (Anda dapat dengan mudah menyesuaikannya).

// ------------------------------------------------------
// server.js 

// .......................................................
// requires
var fs = require('fs');
var express = require('express'); 
var myBusinessLogic = require('../businessLogic/businessLogic.js');

// .......................................................
// security options

/*
1. Generate a self-signed certificate-key pair
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out certificate.pem

2. Import them to a keystore (some programs use a keystore)
keytool -importcert -file certificate.pem -keystore my.keystore
*/

var securityOptions = {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('certificate.pem'),
    requestCert: true
};

// .......................................................
// create the secure server (HTTPS)

var app = express();
var secureServer = require('https').createServer(securityOptions, app);

// ------------------------------------------------------
// helper functions for auth

// .............................................
// true if req == GET /login 

function isGETLogin (req) {
    if (req.path != "/login") { return false; }
    if ( req.method != "GET" ) { return false; }
    return true;
} // ()

// .............................................
// your auth policy  here:
// true if req does have permissions
// (you may check here permissions and roles 
//  allowed to access the REST action depending
//  on the URI being accessed)

function reqHasPermission (req) {
    // decode req.accessToken, extract 
    // supposed fields there: userId:roleId:expiryTime
    // and check them

    // for the moment we do a very rigorous check
    if (req.headers.accessToken != "you-are-welcome") {
        return false;
    }
    return true;
} // ()

// ------------------------------------------------------
// install a function to transparently perform the auth check
// of incoming request, BEFORE they are actually invoked

app.use (function(req, res, next) {
    if (! isGETLogin (req) ) {
        if (! reqHasPermission (req) ){
            res.writeHead(401);  // unauthorized
            res.end();
            return; // don't call next()
        }
    } else {
        console.log (" * is a login request ");
    }
    next(); // continue processing the request
});

// ------------------------------------------------------
// copy everything in the req body to req.body

app.use (function(req, res, next) {
    var data='';
    req.setEncoding('utf8');
    req.on('data', function(chunk) { 
       data += chunk;
    });
    req.on('end', function() {
        req.body = data;
        next(); 
    });
});

// ------------------------------------------------------
// REST requests
// ------------------------------------------------------

// .......................................................
// authenticating method
// GET /login?user=xxx&password=yyy

app.get('/login', function(req, res){
    var user = req.query.user;
    var password = req.query.password;

    // rigorous auth check of user-passwrod
    if (user != "foobar" || password != "1234") {
        res.writeHead(403);  // forbidden
    } else {
        // OK: create an access token with fields user, role and expiry time, hash it
        // and put it on a response header field
        res.setHeader ('accessToken', "you-are-welcome");
        res.writeHead(200); 
    }
    res.end();
});

// .......................................................
// "regular" methods (just an example)
// newBook()
// PUT /book

app.put('/book', function (req,res){
    var bookData = JSON.parse (req.body);

    myBusinessLogic.newBook(bookData, function (err) {
        if (err) {
            res.writeHead(409);
            res.end();
            return;
        }
        // no error:
        res.writeHead(200);
        res.end();
    });
});

// .......................................................
// "main()"

secureServer.listen (8081);

Server ini dapat diuji dengan curl:

echo "----   first: do login "
curl -v "https://localhost:8081/login?user=foobar&password=1234" --cacert certificate.pem

# now, in a real case, you should copy the accessToken received before, in the following request

echo "----  new book"
curl -X POST  -d '{"id": "12341324", "author": "Herman Melville", "title": "Moby-Dick"}' "https://localhost:8081/book" --cacert certificate.pem --header "accessToken: you-are-welcome" 
warga negara1
sumber
Terima kasih atas sampel ini yang sangat membantu, namun saya mencoba untuk mengikuti ini, dan ketika saya terhubung untuk login mengatakan ini: curl: (51) SSL: nama subjek sertifikat 'xxxx' tidak cocok dengan nama host target 'xxx.net'. Saya telah hardcoded / etc / hosts saya untuk memungkinkan https menghubungkan pada mesin yang sama
mastervv
11

Saya baru saja menyelesaikan aplikasi sampel yang melakukan ini dengan cara yang cukup mendasar, tetapi jelas. Ini menggunakan luwak dengan mongodb untuk menyimpan pengguna dan paspor untuk manajemen auth.

https://github.com/Khelldar/Angular-Express-Train-Seed

dentang
sumber
7
Anda menggunakan cookie untuk mengamankan api. Saya pikir itu tidak benar.
Vince Yuan
9

Ada banyak pertanyaan tentang pola autentikasi ISTIRAHAT di sini di SO. Ini adalah yang paling relevan untuk pertanyaan Anda:

Pada dasarnya Anda perlu memilih antara menggunakan kunci API (setidaknya aman karena kunci dapat ditemukan oleh pengguna yang tidak sah), kunci aplikasi dan kombo token (sedang), atau implementasi OAuth penuh (paling aman).

Zim
sumber
Saya membaca banyak tentang oauth 1.0 dan oauth 2.0 dan kedua versi tampaknya tidak terlalu aman. Wikipedia menulis bahwa ada beberapa kebocoran keamanan di Oauth 1.0. Juga saya menemukan sebuah artikel yang tentang salah satu pengembang inti meninggalkan tim karena 2.0 tidak aman.
tschiela
12
@tschiela Anda harus menambahkan referensi ke apa pun yang Anda kutip di sini.
mikemaccana
3

Jika Anda ingin mengamankan aplikasi Anda, maka Anda harus memulai dengan menggunakan HTTPS alih-alih HTTP , ini memastikan terciptanya saluran aman antara Anda & pengguna yang akan mencegah mengendusnya data yang dikirim kembali & keluar kepada pengguna & akan membantu menjaga data bertukar rahasia.

Anda dapat menggunakan JWT (JSON Web Tokens) untuk mengamankan API RESTful , ini memiliki banyak manfaat jika dibandingkan dengan sesi sisi server, manfaatnya terutama:

1- Lebih scalable, karena server API Anda tidak perlu mengelola sesi untuk setiap pengguna (yang bisa menjadi beban besar ketika Anda memiliki banyak sesi)

2 - JWT mandiri & memiliki klaim yang menentukan peran pengguna misalnya & apa yang dapat dia akses & dikeluarkan pada tanggal & tanggal kedaluwarsa (setelah itu JWT tidak akan valid)

3 - Lebih mudah untuk menangani lintas-penyeimbang & jika Anda memiliki beberapa server API karena Anda tidak perlu berbagi data sesi atau mengkonfigurasi server untuk merutekan sesi ke server yang sama, setiap kali permintaan dengan JWT mengenai server mana pun, itu dapat diautentikasi & resmi

4- Lebih sedikit tekanan pada DB Anda dan Anda tidak perlu terus-menerus menyimpan & mengambil id sesi & data untuk setiap permintaan

5- JWT tidak dapat dirusak jika Anda menggunakan kunci yang kuat untuk menandatangani JWT, sehingga Anda dapat mempercayai klaim dalam JWT yang dikirim dengan permintaan tanpa harus memeriksa sesi pengguna & apakah dia berwenang atau tidak , Anda cukup memeriksa JWT & kemudian Anda siap untuk mengetahui siapa & apa yang dapat dilakukan pengguna ini.

Banyak perpustakaan menyediakan cara mudah untuk membuat & memvalidasi JWT di sebagian besar bahasa pemrograman, misalnya: di node.js salah satu yang paling populer adalah jsonwebtoken

Karena REST API umumnya bertujuan untuk menjaga agar server tidak memiliki status kewarganegaraan, maka JWT lebih kompatibel dengan konsep itu karena setiap permintaan dikirim dengan token Otorisasi yang lengkap (JWT) tanpa server harus melacak sesi pengguna dibandingkan dengan sesi yang membuat server stateful sehingga ia mengingat pengguna & perannya, namun, sesi juga banyak digunakan & memiliki pro mereka, yang dapat Anda cari jika Anda mau.

Satu hal penting yang perlu diperhatikan adalah bahwa Anda harus mengirim JWT dengan aman ke klien menggunakan HTTPS & menyimpannya di tempat yang aman (misalnya di penyimpanan lokal).

Anda dapat mempelajari lebih lanjut tentang JWT dari tautan ini

Ahmed Elkoussy
sumber
1
Saya suka jawaban Anda yang tampaknya pembaruan terbaik dari pertanyaan lama ini. Saya telah bertanya pada diri sendiri pertanyaan lain tentang topik yang sama dan Anda mungkin bisa membantu juga. => stackoverflow.com/questions/58076644/…
pbonnefoi
Terima kasih, senang saya bisa membantu, saya memposting jawaban untuk pertanyaan Anda
Ahmed Elkoussy
2

Jika Anda ingin memiliki area aplikasi web yang benar-benar dikunci yang hanya dapat diakses oleh administrator dari perusahaan Anda, maka otorisasi SSL mungkin untuk Anda. Ini akan memastikan bahwa tidak ada yang dapat membuat koneksi ke server contoh kecuali mereka memiliki sertifikat resmi yang terpasang di browser mereka. Minggu lalu saya menulis artikel tentang cara mengatur server: Artikel

Ini adalah salah satu pengaturan paling aman yang akan Anda temukan karena tidak ada nama pengguna / kata sandi yang terlibat sehingga tidak ada yang bisa mendapatkan akses kecuali salah satu pengguna Anda menyerahkan file kunci ke peretas potensial.

ExxKA
sumber
artikel yang bagus. Tetapi area pribadi adalah untuk pengguna.
tschiela
Terima kasih - benar, maka Anda harus mencari solusi lain, mendistribusikan sertifikat akan merepotkan.
ExxKA