Ukrainian (українська мова) translation by Nadia Gonzales (you can also view the original English article)
Реалізація надійної стратегії автентифікації для будь-якого застосунку може бути надзвичайно важкою задачею, і Node-застосунки – не виняток.
У цьому посібнику ми розробимо застосунок Node.js з нуля та будемо використовувати нове, але дуже популярне ПЗ (* програмне забезпечення. Тут і надалі примітка перекладача) проміжного шару для реалізації автентифікації – Passport для вирішення наших питань із аутентифікацією.
В документації по Passport воно описується як «просте невибагливе ПЗ проміжного шару для реалізації автентифікації Node-застосунків», і вірно.
Постачаючись як ПЗ проміжного шару, Passport відмінно упорується з розділом питань автентифікації від інших. Завдяки цьому Passport можна легко вбудовувати до будь-якого веб-додатку на основі Express настільки ж легко як ми вбудовуємо інше ПЗ проміжного шару для Express, наприклад, для забезпечення входу до системи, розбору тіла запиту, розбору кукі, реалізації сесії тощо.
У посібнику припускається, що ви маєте базові уявлення про Node.js та фреймворк Express, і робиться спроба звернути увагу на реалізацію автентифікації, хоча ми й створимо приклад застосунку Express з нуля та покращимо його, додавши маршрути та реалізувавши аутентифікацію для деяких із них.
Стратегії автентифікації
Passport надає нам можливість вибору з більш ніж 140 способів автентифікації. Ви можете реалізувати автентифікацію за допомогою локального/віддаленого екземпляра бази даних, чи використовувати SSO (* single sign-on – технологія єдиного входу) за допомогою провайдерів OAuth (* відкритий стандарт авторизації, який дозволяє користувачам відкривати доступ до своїх приватних даних (фотографії, відео, списки контактів), що зберігаються на одному сайті, іншому сайту, без необхідності вводу імені користувача та паролю) для Facebook, Twitter, Google і т. ін. для автентифікації користувачів за допомогою їх акаунтів у соціальних мережах, чи ви маєте можливість вибрати з великого списку провайдерів, що підтримують автентифікацію за допомогою Passport та надають для цього відповідний модуль Node.
Проте не турбуйтеся: вам нема необхідності підключати будь-які стратегії, що не потрібні для вашого застосунку. Всі ці стратегії не залежать одна від одної та реалізовані у вигляді
окремих модулів Node, що не підключаються за налаштуванням при
встановленні Passport за допомогою npm install passport.
У цьому посібнику ми будемо використовувати локальну стратегію автентифікації Passport і здійснювати автентифікацію користувачів за допомогою локально налаштованого екземпляру MongoDB, зберігаючи дані користувачів до бази даних. Для використання локальної стратегії автентифікації нам необхідно встановити модуль passport-local за допомогою npm install passport-local.
Але зачекайте: До того як ви запустите вашу консоль та почнете виконувати ці команди, давайте почнемо зі створення застосування Express з нуля та додамо декілька маршрутів до нього (для входу до застосування, реєстрації та головної сторінки) і потім спробуємо додати до нього наше ПЗ проміжного шару для реалізації автентифікації. Зверніть увагу, що у цілях цього посібника ми будемо використовувати Express 4, проте з невеликими поправками Passport також буде працювати й з Express 3.
Встановлення застосунку
Якщо ви ще цього не зробили, то встановіть Express і express-generator для створення шаблону застосунку просто за допомогою виконання в консолі express passport-mongo.
Структура створеного застосунку повинна виглядати наступним чином:



авайте усунемо деякі функціональні можливості, що ми не будемо використовувати: видаліть файл users.js
та посилання на нього з файлу app.js.
Додавання залежностей проекту
Відкрийте package.json
та додайте залежності для модулів passport
та passport-local.
"passport": "~0.2.0", "passport-local": "~1.0.0"
Оскільки ми будемо зберігати дані користувачів до MongoDB, то будемо використовувати Mongoose (* ODM (Object Document Mapper - об'єктно-документний відображувач)) у якості інструмента для моделювання об'єктних даних. Встановити та зберегти залежності до package.json
можна й таким чином:
npm install mongoose --save
package.json
повинен виглядати наступним чином:



Тепер встановіть усі залежності та запустіть шаблон застосунку за допомогою npm install && npm start.
У результаті цього буде завантажено та встановлено всі залежності та
запущено сервер Node. Ви можете ознайомитися з базовим застосунком
Express за адресою http://localhost:3000/, проте там ні на що дивитися.
Дуже скоро ми змінимо це, коли створимо повноцінний застосунок Express, що надає сторінку реєстрації для нового користувача, сторінку входу для зареєстрованого користувача та здійснює автентифікацію зареєстрованого користувача за допомогою Passport.
Створення моделі Mongoose
Оскільки ми будемо зберігати дані користувача до Mongo, то давайте створимо модель User в Mongoose та збережемо її до models/user.js
нашого застосунку.
var mongoose = require('mongoose'); module.exports = mongoose.model('User',{ username: String, password: String, email: String, gender: String, address: String });
По суті, ми створюємо модель Mongoose, за допомогою якої ми можемо виконувати операції CRUD (* create, read, update, delete - створити, прочитати, оновити, видалити) над даними бази, що лежить в основі.
Налаштування Mongo
Якщо у вас не встановлена локально Mongo, то ми рекомендуємо вам використовувати хмарні сервіси надання баз даних, наприклад, Modulus или MongoLab. Створити робочий екземпляр MongoDB за допомогою них можна не тільки безкоштовно, але й усього за декілька клацань.
Після створення бази даних на одному з цих сервісів вам буде надано URI на зразок наступного: mongodb
://:@novus.modulusmongo.net:27017/, який можна використовувати для здійснення операцій CRUD над базами даних. Рекомендується зберігати конфігурацію бази даних в окремому файлі, що
можна підключити за необхідності. Тому ми створюємо модуль Node db.js,
який виглядає наступним чином:
module.exports = { 'url' : 'mongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName>' }
Якщо ви схожі на мене та використовуєте локальний екземпляр Mongo, то прийшов час запустити демон (* прихований від користувача процес (часто виконуваний у фоновому режимі), який викликають під час виконання якоїсь функції (або в конкретний момент часу)) mongod
. db.js
повинен виглядати наступним чином:
module.exports = { 'url' : 'mongodb://localhost/passport' }
Тепер ми використовуємо цю конфігурацію в app.js
і підключаємося до бази даних за допомогою Mongoose API:
var dbConfig = require('./db.js'); var mongoose = require('mongoose'); mongoose.connect(dbConfig.url);
Налаштування Passport
Passport надає тільки механізм для реалізації автентифікації і покладає відповідальність за реалізацію підтримки сесій на нас; для цього ми будемо використовувати модуль express-session. Відкрийте app.js
і вставте наведений нижче код перед налаштуванням маршрутів:
// Configuring Passport var passport = require('passport'); var expressSession = require('express-session'); app.use(expressSession({secret: 'mySecretKey'})); app.use(passport.initialize()); app.use(passport.session());
Це необхідно для забезпечення стабільності нашої сесії на сайті. До запуску застосунку нам необхідно встановити express-session та додати
його до нашого списку залежностей у package.json.
Для цього виконайте
npm install --save express-session
Серіалізація та десеріалізація екземплярів користувачів
Для Passport також необхідно забезпечити серіалізацію та десеріалізацію екземплярів користувачів із сховища для сесій, щоб підтримувати сесії входу таким чином, що кожний наступний запит не буде містити мандат (* обліковий запис із параметрами доступу користувача, створеними після його вдалої автентифікації) користувача. Для цього Passport має методи serializeUser
і deserializeUser
:
passport.serializeUser(function(user, done) { done(null, user._id); }); passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); });
Використання стратегій Passport
Зараз ми визначимо стратегії Passport для реалізації можливостей входу до застосунку та реєстрації. Кожна з них буде екземпляром Local Authentication Strategy (* локальної
стратегії автентифікації) Passport та буде створена за допомогою функції
passport.use().
. Ми використовуємо модуль connect-flash, який допомагає
нам в обробленні помилок, надаючи флеш-повідомлення, які можуть бути
показані користувачеві у разі помилки.
Стратегія входу до застосунку
Стратегія входу до застосунку виглядає наступним чином:
// passport/login.js passport.use('login', new LocalStrategy({ passReqToCallback : true }, function(req, username, password, done) { // check in mongo if a user with username exists or not User.findOne({ 'username' : username }, function(err, user) { // In case of any error, return using the done method if (err) return done(err); // Username does not exist, log error & redirect back if (!user){ console.log('User Not Found with username '+username); return done(null, false, req.flash('message', 'User Not found.')); } // User exists but wrong password, log the error if (!isValidPassword(user, password)){ console.log('Invalid Password'); return done(null, false, req.flash('message', 'Invalid Password')); } // User and password both match, return user from // done method which will be treated like success return done(null, user); } ); }));
Перший параметр passport.use()
– це ім'я стратегії, яке буде використовуватися для розпізнавання цієї стратегії при подальшому використанні. Другий параметр – це тип стратегії, яку ви хочете створити; тут ми використовуємо тип username-password, чи LocalStrategy. Варто зазначити, що за налаштуванням LocalStrategy буде шукати мандат користувача в параметрах username
і password
, але також цей тип дозволяє використовувати будь-які інші іменовані параметри. Змінна конфігурації passReqToCallback
дозволяє нам отримати доступ до
об'єкта запиту
в функції зворотного виклику, таким чином надаючи нам
можливість використання будь-якого параметра, доступного в об'єкті
запиту.
Далі ми використовуємо API Mongoose для пошуку даного користувача в нашій головній колекції Users, щоб перевірити, чи існує він у базі даних. Останній параметр у нашій функції зворотного виклику, done
, являє собою корисний метод, за допомогою якого ми мали би можливість сигналізувати модулю Passport про успіх або невдачу. Щоб повідомити про невдачу, необхідно, щоб або перший параметр містив
помилку, або результатом обчислення значення другого параметра було
значення false
. Для повідомлення про успіх необхідно, щоб першим параметром було null
і результатом обчислення значення другого параметра було значення truthy
, у випадку чого воно буде доступен в объекте request
.
Оскільки паролі ненадійні за соїм характером, нам необхідно завжди їх шифрувати перед зберіганням до бази даних. Для цього ми використовуємо модуль bcrypt-nodejs, який допомагає нам у шифруванні/дешифруванні паролів.
var isValidPassword = function(user, password){ return bCrypt.compareSync(password, user.password); }
Якщо вам хотілося би подивитися на повний код у дії, а не на фрагменти коду, то ознайомтеся з кодом за посиланням.
Стратегія для реєстрації
Тепер ми визначаємо наступну стратегію, за допомогою якої реалізується можливість реєстрації нового користувача та створюється його чи її запис у MongoDB, що полягає в основі:
passport.use('signup', new LocalStrategy({ passReqToCallback : true }, function(req, username, password, done) { findOrCreateUser = function(){ // find a user in Mongo with provided username User.findOne({'username':username},function(err, user) { // In case of any error return if (err){ console.log('Error in SignUp: '+err); return done(err); } // already exists if (user) { console.log('User already exists'); return done(null, false, req.flash('message','User Already Exists')); } else { // if there is no user with that email // create the user var newUser = new User(); // set the user's local credentials newUser.username = username; newUser.password = createHash(password); newUser.email = req.param('email'); newUser.firstName = req.param('firstName'); newUser.lastName = req.param('lastName'); // save the user newUser.save(function(err) { if (err){ console.log('Error in Saving user: '+err); throw err; } console.log('User Registration succesful'); return done(null, newUser); }); } }); }; // Delay the execution of findOrCreateUser and execute // the method in the next tick of the event loop process.nextTick(findOrCreateUser); }); );
Тут ми знову використовуємо API Mongoose, щоб дізнатися, чи існує вже користувач із цим іменем або ні. Якщо ні, то створюємо нового користувача та зберігаємо інформацію про
нього до Mongo. В іншому випадку повертаємо помилку, використовуючи
функцію зворотного виклику done
та флеш-повідомлення. Зверніть увагу на
те, що ми використовуємо bcrypt-nodejs
для створення гешу пароля перед
зберіганням:
// Generates hash using bCrypt var createHash = function(password){ return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null); }
Створення маршрутів
Якщо би ми поглянули на наш додаток із висоти пташиного польоту, то він би виглядав наступним чином:



Тепер ми визначаємо маршрути для нашого додатка в наступному модулі, який використовує екземпляр Passport, створений вище в app.js.
Збережіть цей модуль до routes/index.js.
module.exports = function(passport){ /* GET login page. */ router.get('/', function(req, res) { // Display the Login page with any flash message, if any res.render('index', { message: req.flash('message') }); }); /* Handle Login POST */ router.post('/login', passport.authenticate('login', { successRedirect: '/home', failureRedirect: '/', failureFlash : true })); /* GET Registration Page */ router.get('/signup', function(req, res){ res.render('register',{message: req.flash('message')}); }); /* Handle Registration POST */ router.post('/signup', passport.authenticate('signup', { successRedirect: '/home', failureRedirect: '/signup', failureFlash : true })); return router; }
Найбільш важлива частина приведеного вище коду – це використання passport.authenticate()
для доручення здійснення автентифікації стратегіям login
и signup
, коли
відбуваються HTTP-запити по методу POST
до маршрутів /login
та /signup
.
Зверніть увагу, що додавання до назви шляхів маршрутів назв стратегій не
є обов'язковим і може бути будь-яким. Зверніть увагу, що додавання до назви шляхів маршрутів назв стратегій не є обов'язковим і може бути будь-яким.
Створення представлень на Jade (* перейменований у Pug)
Далі створюємо наступні два представлення для нашого додатка:
- в
layout.jade
міститься базовий макет та інформація про стильове оформлення - в
index.jade
міститься сторінка входу, що надає форму для входу та можливість створити новий акаунт
extends layout block content div.container div.row div.col-sm-6.col-md-4.col-md-offset-4 h1.text-center.login-title Sign in to our Passport app div.account-wall img(class='profile-img', src='https://lh5.googleusercontent.com/-b0-k99FZlyE/AAAAAAAAAAI/AAAAAAAAAAA/eu7opA4byxI/photo.jpg?sz=120') form(class='form-signin', action='/login', method='POST') input(type='text', name='username' class='form-control', placeholder='Email',required, autofocus) input(type='password', name='password' class='form-control', placeholder='Password', required) button(class='btn btn-lg btn-primary btn-block', type='submit') Sign in span.clearfix a(href='/signup', class='text-center new-account') Create an account #message if message h1.text-center.error-message #{message}
Завдяки Bootstrap (* веб-фреймворк для дизайну веб-сайтів та веб-додатків) наша сторінка для входу виглядає наступним чином:



Нам ще необхідні представлення для заповнення реєстраційних даних і для домашньої сторінки нашого додатка:
- в
register.jade
міститься форма для реєстрації - в
home.jade
виводиться привітання та наводяться дані користувача, що увійшов
Якщо погано знаєте Jade , то ознайомтесь із документацією.
Реалізація функціональної можливості виходу із додатка
Passport, будучи ПЗ проміжного шару, має можливість додавати певні властивості та методи до об'єктів запиту та відповіді і вдало її використовує, додаючи дуже корисний метод request.logout(),
що дозволяє зробити недійсною тільки сесію на сайті, не чіпаючи інші можливості.
/* Handle Logout */ router.get('/signout', function(req, res) { req.logout(); res.redirect('/'); });
Захист маршрутів
Passport також надає можливість захистити доступ до маршруту, доступ до якого необхідно обмежити для анонімних користувачів. Це означає, що у випадку отримання доступу якого-небудь користувача до http://localhost:3000/home без проходження автентифікації в застосунку його буде переадресовано до початкової сторінки за допомогою:
/* GET Home Page */ router.get('/home', isAuthenticated, function(req, res){ res.render('home', { user: req.user }); }); // As with any middleware it is quintessential to call next() // if the user is authenticated var isAuthenticated = function (req, res, next) { if (req.isAuthenticated()) return next(); res.redirect('/'); }
Завершення
Passport не єдиний гравець на арені, коли мова заходить про автентифікацію додатків Node.js. Існують альтернативи, наприклад, EveryAuth, проте завдяки принципу модульності, гнучкості, підтримці товариства та факту, що Passport – це ПЗ проміжного шару, обговорюваний у посібнику модуль є більш вдалим вибором.
Ось порівняння цих двох варіантів від самого розробника Passport.