Penulis artikel, terjemahan yang kami terbitkan hari ini, mengatakan bahwa sekarang Anda dapat mengamati semakin populernya layanan otentikasi seperti Google Firebase Authentication, AWS Cognito dan Auth0. Solusi umum seperti passport.js telah menjadi standar industri. Namun, mengingat situasi saat ini, sudah menjadi hal biasa bahwa pengembang tidak pernah sepenuhnya memahami mekanisme mana yang terlibat dalam pengoperasian sistem otentikasi.
Materi ini dikhususkan untuk masalah pengorganisasian otentikasi pengguna di Node.js. Di dalamnya, pada contoh praktis, organisasi pendaftaran pengguna dalam sistem dan organisasi masuknya mereka ke dalam sistem dipertimbangkan. Ini akan mengangkat masalah seperti bekerja dengan teknologi JWT dan peniruan pengguna.

Juga, perhatikan repositori GitHub
ini , yang berisi kode untuk proyek Node.js, beberapa contohnya diberikan dalam artikel ini. Anda dapat menggunakan repositori ini sebagai dasar untuk eksperimen Anda sendiri.
Persyaratan proyek
Berikut adalah persyaratan untuk proyek yang akan kami tangani di sini:
- Kehadiran database di mana alamat email dan kata sandi pengguna akan disimpan, baik clientId dan clientSecret, atau semacam kombinasi kunci pribadi dan publik.
- Menggunakan algoritma kriptografi yang kuat dan efisien untuk mengenkripsi kata sandi.
Saat ini ketika saya sedang menulis materi ini, saya percaya bahwa yang terbaik dari algoritma kriptografi yang ada adalah Argon2. Saya meminta Anda untuk tidak menggunakan algoritma kriptografi sederhana seperti SHA256, SHA512 atau MD5.
Selain itu, saya sarankan Anda melihat materi yang luar biasa
ini , di mana Anda dapat menemukan detail tentang memilih algoritma untuk hashing password.
Registrasi pengguna dalam sistem
Ketika pengguna baru dibuat dalam sistem, kata sandinya harus diacak dan disimpan dalam database. Kata sandi disimpan dalam database bersama dengan alamat email dan informasi lain tentang pengguna (misalnya, di antaranya adalah profil pengguna, waktu pendaftaran, dan sebagainya).
import * as argon2 from 'argon2'; class AuthService { public async SignUp(email, password, name): Promise<any> { const passwordHashed = await argon2.hash(password); const userRecord = await UserModel.create({ password: passwordHashed, email, name, }); return {
Informasi akun pengguna akan terlihat seperti berikut ini.
Data pengguna diambil dari MongoDB menggunakan Robo3TLogin Pengguna
Berikut adalah diagram tindakan yang dilakukan ketika pengguna mencoba masuk.
Login PenggunaInilah yang terjadi ketika pengguna masuk:
- Klien mengirimkan kombinasi server dari pengidentifikasi publik dan kunci pribadi pengguna. Ini biasanya alamat email dan kata sandi.
- Server mencari pengguna dalam database berdasarkan alamat email.
- Jika pengguna ada di database, server hash kata sandi yang dikirim kepadanya dan membandingkan apa yang terjadi dengan hash kata sandi yang disimpan dalam database.
- Jika verifikasi berhasil, server menghasilkan apa yang disebut token atau token otentikasi - JSON Web Token (JWT).
JWT adalah kunci sementara. Klien harus mengirim kunci ini ke server dengan setiap permintaan ke titik akhir yang diautentikasi.
import * as argon2 from 'argon2'; class AuthService { public async Login(email, password): Promise<any> { const userRecord = await UserModel.findOne({ email }); if (!userRecord) { throw new Error('User not found') } else { const correctPassword = await argon2.verify(userRecord.password, password); if (!correctPassword) { throw new Error('Incorrect password') return { user: { email: userRecord.email, name: userRecord.name, }, token: this.generateJWT(userRecord), }
Verifikasi kata sandi dilakukan menggunakan pustaka argon2. Ini untuk mencegah apa yang disebut "
serangan waktu ". Ketika melakukan serangan seperti itu, seorang penyerang mencoba untuk memecahkan kata sandi dengan kekerasan, berdasarkan pada analisis berapa lama waktu yang dibutuhkan server untuk membentuk respons.
Sekarang mari kita bicara tentang cara menghasilkan JWT.
Apa itu JWT?
JSON Web Token (JWT) adalah objek JSON yang disandikan dalam bentuk string. Token dapat diambil sebagai pengganti cookie, yang memiliki beberapa keunggulan dibandingkan cookie.
Token terdiri dari tiga bagian. Ini tajuk, payload, dan tanda tangan. Gambar berikut menunjukkan penampilannya.
JwtData token dapat didekodekan pada sisi klien tanpa menggunakan kunci rahasia atau tanda tangan.
Ini bisa berguna untuk mentransfer, misalnya, metadata yang disandikan di dalam token. Metadata tersebut dapat menggambarkan peran pengguna, profilnya, durasi token, dan sebagainya. Mereka dapat dimaksudkan untuk digunakan dalam aplikasi front-end.
Inilah yang terlihat seperti token yang diterjemahkan.
Token TerdekodeMenghasilkan JWT di Node.js
Mari kita buat fungsi
generateToken
yang kita perlukan untuk menyelesaikan pekerjaan pada layanan otentikasi pengguna.
Anda dapat membuat JWT menggunakan perpustakaan jsonwebtoken. Anda dapat menemukan perpustakaan ini di npm.
import * as jwt from 'jsonwebtoken' class AuthService { private generateToken(user) { const data = { _id: user._id, name: user.name, email: user.email }; const signature = 'MySuP3R_z3kr3t'; const expiration = '6h'; return jwt.sign({ data, }, signature, { expiresIn: expiration }); }
Yang paling penting di sini adalah data yang disandikan. Jangan mengirim informasi pengguna rahasia di token.
Tanda tangan (di sini adalah
signature
konstan) adalah data rahasia yang digunakan untuk menghasilkan JWT. Sangat penting untuk memastikan bahwa tanda tangan tidak jatuh ke tangan yang salah. Jika tanda tangan dikompromikan, penyerang akan dapat menghasilkan token atas nama pengguna dan mencuri sesi mereka.
Perlindungan Endpoint dan Validasi JWT
Sekarang kode klien perlu mengirim JWT di setiap permintaan ke titik akhir yang aman.
Disarankan agar Anda memasukkan JWT dalam header permintaan. Mereka biasanya dimasukkan dalam header Otorisasi.
Header OtorisasiSekarang, di server, Anda perlu membuat kode yang middleware untuk rute ekspres. Masukkan kode ini ke file
isAuth.ts
:
import * as jwt from 'express-jwt';
Berguna untuk memperoleh informasi lengkap tentang akun pengguna dari basis data dan melampirkannya pada permintaan. Dalam kasus kami, fitur ini diimplementasikan menggunakan middleware dari file
attachCurrentUser.ts
. Berikut ini kode yang disederhanakan:
export default (req, res, next) => { const decodedTokenData = req.tokenData; const userRecord = await UserModel.findOne({ _id: decodedTokenData._id }) req.currentUser = userRecord; if(!userRecord) { return res.status(401).end('User not found') } else { return next(); }
Setelah menerapkan mekanisme ini, rute akan dapat menerima informasi tentang pengguna yang mengeksekusi permintaan:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import ItemsModel from '../models/items'; export default (app) => { app.get('/inventory/personal-items', isAuth, attachCurrentUser, (req, res) => { const user = req.currentUser; const userItems = await ItemsModel.find({ owner: user._id }); return res.json(userItems).status(200); })
Rute
inventory/personal-items
sekarang dilindungi. Untuk mengaksesnya, pengguna harus memiliki JWT yang valid. Rute, di samping itu, dapat menggunakan informasi pengguna untuk mencari di database untuk informasi yang dibutuhkan.
Mengapa token dilindungi dari penyusup?
Setelah membaca tentang menggunakan JWT, Anda dapat bertanya pada diri sendiri pertanyaan berikut: "Jika data JWT dapat diterjemahkan pada sisi klien, apakah mungkin untuk memproses token sedemikian rupa untuk mengubah ID pengguna atau data lainnya?".
Dekode Token - operasi ini sangat sederhana. Namun, Anda tidak dapat "mengulang" token ini tanpa memiliki tanda tangan itu, data rahasia yang digunakan saat menandatangani JWT di server.
Itulah sebabnya perlindungan data sensitif ini sangat penting.
Server kami memverifikasi tanda tangan di middleware isAuth. Perpustakaan express-jwt bertanggung jawab untuk memeriksa.
Sekarang, setelah kami menemukan cara kerja teknologi JWT, mari kita bicara tentang beberapa fitur tambahan menarik yang diberikannya kepada kita.
Bagaimana cara menyamar sebagai pengguna?
Peniruan pengguna adalah teknik yang digunakan untuk masuk ke sistem sebagai pengguna tertentu tanpa mengetahui kata sandinya.
Fitur ini sangat berguna untuk administrator super, pengembang, atau staf pendukung. Peniruan memungkinkan mereka untuk memecahkan masalah yang muncul hanya saat pengguna bekerja dengan sistem.
Anda dapat bekerja dengan aplikasi atas nama pengguna tanpa mengetahui kata sandinya. Untuk melakukan ini, cukup menghasilkan JWT dengan tanda tangan yang benar dan dengan metadata yang diperlukan yang menggambarkan pengguna.
Buat titik akhir yang dapat menghasilkan token untuk memasuki sistem dengan kedok pengguna tertentu. Hanya super-administrator sistem yang dapat menggunakan titik akhir ini.
Sebagai permulaan, kami harus menetapkan peran pengguna ini dengan tingkat hak istimewa yang lebih tinggi daripada pengguna lain. Ini dapat dilakukan dengan berbagai cara. Misalnya, cukup menambahkan bidang
role
ke informasi pengguna yang disimpan dalam database.
Mungkin terlihat seperti yang ditunjukkan di bawah ini.
Bidang baru dalam informasi penggunaNilai bidang
role
super-admin
adalah
super-admin
.
Selanjutnya, Anda perlu membuat middleware baru yang memeriksa peran pengguna:
export default (requiredRole) => { return (req, res, next) => { if(req.currentUser.role === requiredRole) { return next(); } else { return res.status(401).send('Action not allowed'); }
Itu harus ditempatkan setelah isAuth dan attachCurrentUser. Sekarang buat titik akhir yang menghasilkan JWT untuk pengguna atas nama siapa super administrator ingin login:
import isAuth from '../middlewares/isAuth'; import attachCurrentUser from '../middlewares/attachCurrentUser'; import roleRequired from '../middlwares/roleRequired'; import UserModel from '../models/user'; export default (app) => { app.post('/auth/signin-as-user', isAuth, attachCurrentUser, roleRequired('super-admin'), (req, res) => { const userEmail = req.body.email; const userRecord = await UserModel.findOne({ email: userEmail }); if(!userRecord) { return res.status(404).send('User not found'); return res.json({ user: { email: userRecord.email, name: userRecord.name }, jwt: this.generateToken(userRecord) }) .status(200); })
Seperti yang Anda lihat, tidak ada yang misterius. Administrator super tahu alamat email pengguna atas nama siapa Anda ingin masuk. Logika kode di atas sangat mengingatkan pada bagaimana kode bekerja, memberikan input ke sistem pengguna biasa. Perbedaan utama adalah bahwa kata sandi tidak diperiksa di sini.
Kata sandi tidak diverifikasi di sini karena memang tidak diperlukan di sini. Keamanan endpoint disediakan oleh middleware.
Ringkasan
Tidak ada yang salah dengan mengandalkan layanan otentikasi dan perpustakaan pihak ketiga. Ini membantu pengembang menghemat waktu. Tetapi mereka juga perlu tahu tentang prinsip-prinsip yang menjadi dasar pengoperasian sistem otentikasi, dan apa yang memastikan berfungsinya sistem tersebut.
Pada artikel ini, kami mengeksplorasi kemungkinan otentikasi JWT, berbicara tentang pentingnya memilih algoritma kriptografi yang baik untuk hashing password. Kami memeriksa pembuatan mekanisme peniruan pengguna.
Melakukan hal yang sama dengan sesuatu seperti passport.js jauh dari mudah. Otentikasi adalah topik yang sangat besar. Mungkin kita akan kembali padanya.
Pembaca yang budiman! Bagaimana Anda membuat sistem otentikasi untuk proyek Node.js Anda?