Статьи

Аутентификация приложений Node.js по паспорту

Реализация надежных стратегий аутентификации для любого приложения может быть сложной задачей, и приложения Node.js не являются исключением из этого.

В этом руководстве мы с нуля разработаем приложение Node.js и будем использовать относительно новое, но очень популярное промежуточное ПО для аутентификации — Passport, чтобы позаботиться о наших проблемах с аутентификацией.

Документация Passport описывает его как «простое, ненавязчивое промежуточное ПО аутентификации для Node», и это правильно.

Предоставляя себя в качестве промежуточного программного обеспечения, Passport отлично справляется с отделением других задач веб-приложения от его потребностей в аутентификации. Он позволяет легко настраивать Passport в любом веб-приложении на основе Express , точно так же, как мы настраиваем другое промежуточное ПО Express, такое как ведение журнала , анализ тела, анализ файлов cookie, обработка сеанса и т. Д.

Это руководство предполагает базовое понимание инфраструктуры Node.js и Express и пытается сосредоточиться на аутентификации, хотя мы создаем пример приложения Express с нуля и развиваемся путем добавления к нему маршрутов и аутентификации некоторых из этих маршрутов.

Если вам нужна помощь во всем, от отладки до новых функций, попробуйте поработать с некоторыми опытными разработчиками JavaScript в Envato Studio.

Разработчики JavaScript на Envato Studio
Разработчики 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 должен выглядеть так:

Добавлены зависимости от Mongoose

Теперь установите все зависимости и запустите шаблонное приложение, выполнив команду 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 соответственно. Обратите внимание, что указывать стратегии на пути маршрута не обязательно, и он может называться как угодно.

Далее мы создаем следующие два представления для нашего приложения:

  1. layout.jade содержит основную информацию о макете и стиле
  2. 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 наша страница входа теперь выглядит

Страница входа в приложение "Наш паспорт"

Нам нужно еще два просмотра для регистрации деталей и для домашней страницы приложения:

  1. register.jade содержит регистрационную форму
  2. 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 базы данных .