Реализация надежных стратегий аутентификации для любого приложения может быть сложной задачей, и приложения Node.js не являются исключением из этого.
В этом руководстве мы с нуля разработаем приложение Node.js и будем использовать относительно новое, но очень популярное промежуточное ПО для аутентификации — Passport, чтобы позаботиться о наших проблемах с аутентификацией.
Документация Passport описывает его как «простое, ненавязчивое промежуточное ПО аутентификации для Node», и это правильно.
Предоставляя себя в качестве промежуточного программного обеспечения, Passport отлично справляется с отделением других задач веб-приложения от его потребностей в аутентификации. Он позволяет легко настраивать Passport в любом веб-приложении на основе Express , точно так же, как мы настраиваем другое промежуточное ПО Express, такое как ведение журнала , анализ тела, анализ файлов cookie, обработка сеанса и т. Д.
Это руководство предполагает базовое понимание инфраструктуры Node.js и Express и пытается сосредоточиться на аутентификации, хотя мы создаем пример приложения Express с нуля и развиваемся путем добавления к нему маршрутов и аутентификации некоторых из этих маршрутов.
Если вам нужна помощь во всем, от отладки до новых функций, попробуйте поработать с некоторыми опытными разработчиками JavaScript в Envato Studio.
Стратегии аутентификации
Паспорт предоставляет нам более 140 механизмов аутентификации на выбор. Вы можете выполнить аутентификацию на локальном / удаленном экземпляре базы данных или использовать единый вход с помощью поставщиков OAuth для Facebook , Twitter , Google и т. Д. Для аутентификации в своих учетных записях в социальных сетях или выбрать из обширного списка поставщиков, поддерживающих аутентификацию. с паспортом и предоставить модуль узла для этого.
Но не беспокойтесь: вам не нужно включать какие-либо стратегии / механизмы, которые не нужны вашему приложению. Все эти стратегии независимы друг от друга и упакованы в виде отдельных узловых модулей, которые не включаются по умолчанию при установке промежуточного программного обеспечения Passport: npm install passport
В этом руководстве мы будем использовать локальную стратегию аутентификации Passport и аутентифицировать пользователей по локально настроенному экземпляру Mongo DB, сохраняя данные пользователя в базе данных. Для использования стратегии локальной аутентификации нам нужно установить модуль passport-local : npm install passport-local
Но подождите: прежде чем запустить свой терминал и начать выполнять эти команды, давайте начнем с создания приложения Express с нуля и добавим к нему несколько маршрутов (для входа в систему, регистрации и дома), а затем попробуем добавить к нему наше промежуточное программное обеспечение для аутентификации. Обратите внимание, что мы будем использовать Express 4 для целей данного руководства, но с некоторыми небольшими отличиями Passport также одинаково хорошо работает с Express 3.
Настройка приложения
Если вы этого еще не сделали, тогда установите Express & express-generator для генерации стандартного приложения, просто выполнив express passport-mongo
на терминале. Созданная структура приложения должна выглядеть следующим образом:
Давайте удалим некоторые функции по умолчанию, которые мы не будем использовать — users.js
маршрут users.js
и удалим его ссылки из файла app.js
Добавление зависимостей проекта
Откройте package.json
и добавьте зависимости для модуля passport
и passport-local
.
1
2
|
«passport»: «~0.2.0»,
«passport-local»: «~1.0.0»
|
Поскольку мы будем сохранять пользовательские данные в MongoDB, мы будем использовать Mongoose в качестве инструмента моделирования объектных данных. Другой способ установить и сохранить зависимость в package.json
— ввести:
1
|
npm install mongoose —save
|
package.json
должен выглядеть так:
Теперь установите все зависимости и запустите шаблонное приложение, выполнив команду npm install && npm start
. Теперь он загрузит и установит все зависимости и запустит сервер узла. Вы можете проверить основное приложение Express по адресу http: // localhost: 3000 /, но там особо нечего смотреть.
Очень скоро мы собираемся изменить это, создав полноценное экспресс-приложение, которое запрашивает отображение страницы регистрации нового пользователя, имени зарегистрированного пользователя и аутентифицирует зарегистрированного пользователя с помощью Passport.
Создание мангустовой модели
Поскольку мы будем сохранять пользовательские данные в Mongo, давайте создадим пользовательскую модель в Mongoose и сохраним ее в models/user.js
в нашем приложении.
1
2
3
4
5
6
7
8
9
|
var mongoose = require(‘mongoose’);
module.exports = mongoose.model(‘User’,{
username: String,
password: String,
email: String,
gender: String,
address: String
});
|
По сути, мы создаем модель Mongoose, используя которую мы можем выполнять операции CRUD с базовой базой данных.
Настройка Монго
Если у вас не установлен Mongo локально, мы рекомендуем вам использовать облачные службы баз данных, такие как Modulus или MongoLab . Создание работающего экземпляра MongoDB с их использованием не только бесплатно, но и всего лишь за несколько кликов.
После создания базы данных на одном из этих сервисов она выдаст вам URI базы данных, например mongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName>
который можно использовать для выполнения операций CRUD. в базе данных. Хорошая идея — хранить конфигурацию базы данных в отдельном файле, который может быть загружен по мере необходимости. Таким образом, мы создаем модуль узла db.js
который выглядит следующим образом:
1
2
3
|
module.exports = {
‘url’ : ‘mongodb://<dbuser>:<dbpassword>@novus.modulusmongo.net:27017/<dbName>’
}
|
Если вы похожи на меня, вы используете локальный экземпляр Mongo, тогда пришло время запустить демон mongod
, и db.js
должен выглядеть так
1
2
3
|
module.exports = {
‘url’ : ‘mongodb://localhost/passport’
}
|
Теперь мы используем эту конфигурацию в app.js
и подключаемся к ней с помощью API Mongoose:
1
2
3
|
var dbConfig = require(‘./db.js’);
var mongoose = require(‘mongoose’);
mongoose.connect(dbConfig.url);
|
Настройка паспорта
Passport просто предоставляет механизм для обработки аутентификации, оставляя ответственность за реализацию обработки сеанса самостоятельно, и для этого мы будем использовать экспресс-сеанс . Откройте app.js
и вставьте приведенный ниже код перед настройкой маршрутов:
1
2
3
4
5
6
|
// 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
Сериализация и десериализация пользовательских экземпляров
Паспорт также должен сериализовать и десериализовать пользовательский экземпляр из хранилища сеансов, чтобы поддерживать сеансы входа в систему, чтобы каждый последующий запрос не содержал учетные данные пользователя. Для этого он предоставляет два метода: serializeUser
и deserializeUser
:
1
2
3
4
5
6
7
8
9
|
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 и будет создан с использованием функции passport.use()
. Мы используем connect-flash, чтобы помочь нам с обработкой ошибок, предоставляя флэш-сообщения, которые могут отображаться пользователю при ошибке.
Стратегия входа
Стратегия входа выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 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()
является имя стратегии, которая будет использоваться для идентификации этой стратегии при последующем применении. Второй параметр — это тип стратегии, которую вы хотите создать, здесь мы используем имя пользователя-пароль или LocalStrategy. Следует отметить, что по умолчанию LocalStrategy ожидает найти учетные данные username
параметрах username
и password
, но также позволяет нам использовать любые другие именованные параметры. passReqToCallback
переменная passReqToCallback
позволяет нам получить доступ к объекту request
в passReqToCallback
, что позволяет нам использовать любой параметр, связанный с запросом.
Затем мы используем API Mongoose, чтобы найти пользователя в нашей основной коллекции пользователей, чтобы проверить, является ли пользователь действительным пользователем или нет. Последний параметр в нашем обратном вызове: done
обозначает полезный метод, с помощью которого мы можем сигнализировать об успешном или неудачном прохождении модуля Passport. Чтобы указать ошибку, либо первый параметр должен содержать ошибку, либо второй параметр должен иметь значение false
. Чтобы обозначить успех, первый параметр должен быть null
а второй параметр должен иметь truthy
значение, и в этом случае он будет доступен для объекта request
Поскольку пароли по своей природе слабые, мы всегда должны шифровать их перед сохранением в базе данных. Для этого мы используем bcrypt-nodejs, чтобы помочь нам с шифрованием и дешифрованием паролей.
1
2
3
|
var isValidPassword = function(user, password){
return bCrypt.compareSync(password, user.password);
}
|
Если вы чувствуете себя неловко с фрагментами кода и предпочитаете видеть весь код в действии, не стесняйтесь просматривать код здесь .
Стратегия регистрации
Теперь мы определим следующую стратегию, которая будет обрабатывать регистрацию нового пользователя и создает его или ее запись в нашей базовой базе данных Mongo:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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
для создания хэша пароля перед его сохранением:
1
2
3
4
|
// Generates hash using bCrypt
var createHash = function(password){
return bCrypt.hashSync(password, bCrypt.genSaltSync(10), null);
}
|
Создание маршрутов
Если бы мы увидели наше приложение с высоты птичьего полета, оно бы выглядело так:
Теперь мы определим наши маршруты для приложения в следующем модуле, который использует экземпляр Passport, созданный в app.js
выше. Сохраните этот модуль в routes/index.js
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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
соответственно. Обратите внимание, что указывать стратегии на пути маршрута не обязательно, и он может называться как угодно.
Создание нефритовых видов
Далее мы создаем следующие два представления для нашего приложения:
-
layout.jade
содержит основную информацию о макете и стиле -
index.jade
содержит страницу входа, содержащую форму входа и возможность создания новой учетной записи.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
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
говорит привет и показывает авторизованные данные пользователя
Если вы не знакомы с Джейд, ознакомьтесь с документацией .
Реализация функции выхода из системы
Паспорт, являющийся промежуточным программным обеспечением, может добавлять определенные свойства и методы к объектам запросов и ответов и правильно их использует, добавляя очень удобный метод request.logout()
который делает недействительным пользовательский сеанс, помимо других свойств.
1
2
3
4
5
|
/* Handle Logout */
router.get(‘/signout’, function(req, res) {
req.logout();
res.redirect(‘/’);
});
|
Защита маршрутов
Паспорт также дает возможность защитить доступ к маршруту, который считается непригодным для анонимного пользователя. Это означает, что если какой-либо пользователь попытается получить доступ к http: // localhost: 3000 / home без аутентификации в приложении, он будет перенаправлен на домашнюю страницу, выполнив
01
02
03
04
05
06
07
08
09
10
11
12
|
/* 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.
Если вы хотите узнать, что еще можно сделать с Node.js, проверьте диапазон элементов Node.js на Envato Market, от отзывчивой контактной формы AJAX до укороченного URL-адреса или даже генератора CRUD базы данных .