Распространенным требованием при создании веб-приложения является реализация системы входа в систему, чтобы пользователи могли аутентифицировать себя перед получением доступа к защищенным представлениям или ресурсам. К счастью для тех, кто создает приложения Node, есть промежуточное ПО под названием Passport, которое можно добавить в любое веб-приложение на основе Express, чтобы обеспечить механизмы аутентификации всего несколькими командами.
В этом руководстве я продемонстрирую, как использовать Passport для реализации локальной аутентификации (то есть входа в систему с именем пользователя и паролем) с серверной частью MongoDB. Если вы хотите реализовать аутентификацию через Facebook или GitHub, обратитесь к этому руководству.
Как всегда, весь код для этой статьи доступен для загрузки на GitHub.
Предпосылки
Чтобы следовать этому руководству, на вашем компьютере должны быть установлены Node и MongoDB.
Вы можете установить Node, перейдя на официальную страницу загрузки Node и загрузив нужные двоичные файлы для своей системы. В качестве альтернативы вы можете использовать диспетчер версий — программу, которая позволяет вам установить несколько версий Node и переключаться между ними по желанию. Если вы хотите пойти по этому пути, ознакомьтесь с нашим кратким советом » Установка нескольких версий Node.js с помощью nvm «.
MongoDB поставляется в различных редакциях. Нас интересует MongoDB Community Edition.
На домашней странице проекта есть отличная документация, и я не буду пытаться повторить ее здесь. Скорее предлагаю вам ссылки на инструкции для каждой из основных операционных систем:
- Установите MongoDB Community Edition в Windows
- Установите MongoDB Community Edition на macOS
- А также установите MongoDB Community Edition на Ubuntu
Если вы используете версию Linux, отличную от Ubuntu, вы можете проверить эту страницу для получения инструкций по установке для других дистрибутивов. MongoDB также обычно доступен через официальные каналы программного обеспечения Linux, но иногда для этого потребуется устаревшая версия.
Примечание: вам не нужно вводить свое имя и адрес для загрузки MongoDB. Если будет предложено, вы можете закрыть диалоговое окно.
Если вы хотите быстро освежить в памяти использование MongoDB, ознакомьтесь с нашим руководством для начинающих » Введение в MongoDB «.
Стратегии аутентификации:Session против JWT
Прежде чем мы начнем, давайте кратко поговорим о вариантах аутентификации.
Многие из онлайн-руководств сегодня будут выбирать аутентификацию на основе токенов с использованием веб-токенов JSON (JWT). Этот подход, наверное, самый простой и популярный в настоящее время. Он перекладывает часть ответственности за аутентификацию на клиента и заставляет его подписывать токен, который отправляется с каждым запросом, чтобы пользователь оставался аутентифицированным.
Сессионная аутентификация существует дольше. Этот метод перекладывает вес аутентификации на сервер. Он использует файлы cookie и видит, что приложение Node и база данных работают вместе, чтобы отслеживать состояние аутентификации пользователя.
В этом руководстве мы будем использовать аутентификацию на основе сеанса, которая лежит в основе стратегии локального паспорта.
У обоих методов есть свои достоинства и недостатки. Если вы хотите больше узнать о различиях между ними, эта ветка переполнения стека может быть хорошим местом для начала.
Создание проекта
После того, как все необходимое программное обеспечение установлено, мы можем приступить к работе.
Мы начнем с создания папки для нашего приложения, а затем доступа к этой папке на терминале:
mkdir AuthApp cd AuthApp
Чтобы создать приложение узла, мы будем использовать следующую команду:
npm init
Вам будет предложено предоставить некоторую информацию для Node’s package.json. Просто продолжайте нажимать, Returnчтобы принять конфигурацию по умолчанию (или использовать -yфлаг).
Настройка Express
Теперь нам нужно установить Express. Зайдите в терминал и введите эту команду:
npm install express
Нам также потребуется установить промежуточное ПО для парсера тела, которое используется для анализа тела запроса, которое Passport использует для аутентификации пользователя. И нам нужно установить промежуточное ПО для экспресс-сеанса.
Давайте сделаем это. Выполните следующую команду:
npm install body-parser express-session
Когда это будет сделано, создайте index.jsфайл в корневой папке вашего приложения и добавьте в него следующее содержимое:
/* EXPRESS SETUP */ const express = require('express'); const app = express(); app.use(express.static(__dirname)); const bodyParser = require('body-parser'); const expressSession = require('express-session')({ secret: 'secret', resave: false, saveUninitialized: false }); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(expressSession); const port = process.env.PORT || 3000; app.listen(port, () => console.log('App listening on port ' + port));
Сначала мы requireвыражаем и создаем наше приложение Express, вызывая express (). Затем мы определяем каталог, из которого будут обслуживать наши статические файлы.
В следующей строке мы видим requireпромежуточное программное обеспечение парсера тела, которое помогает нам анализировать тело наших запросов. Мы также добавляем промежуточное программное обеспечение экспресс-сеанса, чтобы помочь нам сохранить файл cookie сеанса.
Как вы можете, видеть, что мы настройку экспресс-сессию с secretподписать идентификатор сеанса печенья (вы должны выбрать уникальное значение здесь), а два других полей, пересохраните и saveUninitialized. В resaveсилах поля сессия будет сохранена обратно в магазин сеанса, и saveUninitializedполе сила сеансов, «неинициализированный», чтобы быть сохранены в хранилище. Чтобы узнать о них больше, ознакомьтесь с их документацией, но пока достаточно знать, что в нашем случае мы хотим их сохранить false.
Затем мы используем process.env.PORTдля установки порта переменную порта среды, если она существует. В противном случае мы по умолчанию выберем 3000тот порт, который мы будем использовать локально. Это дает вам достаточную гибкость для перехода от разработки непосредственно к производственной среде, где порт может быть установлен поставщиком услуг, например, Heroku. Прямо под этим мы вызвали app.listen () с настроенной переменной порта и простым журналом, чтобы сообщить нам, что все работает нормально и на каком порту приложение прослушивает.
Это все, что касается настройки Express. Теперь приступим к настройке Passport.
Настройка паспорта
Сначала мы устанавливаем Passport с помощью следующей команды:
npm install passport
Затем нам нужно добавить следующие строки в конец index.jsфайла:
/* PASSPORT SETUP */ const passport = require('passport'); app.use(passport.initialize()); app.use(passport.session());
Здесь мы требуем passportи инициализируем его вместе с промежуточным программным обеспечением аутентификации сеанса непосредственно внутри нашего приложения Express.
Создание хранилища данных MongoDB
Поскольку мы предполагаем, что вы уже установили Mongo, вы сможете запустить оболочку Mongo, используя следующую команду:
mongo
В оболочке введите следующую команду:
use MyDatabase;
Это просто создает хранилище данных с именем MyDatabase.
Оставьте терминал там; мы вернемся к этому позже.
Подключение Mongo к Node с помощью Mongoose
Теперь, когда у нас есть база данных с записями, нам нужен способ связи с ней из нашего приложения. Для этого мы будем использовать Mongoose. Почему бы нам просто не использовать простой Монго? Ну, как любят говорить разработчики Mongoose, A href = «https://mongoosejs.com/docs/unstable/index.html»> на своем веб-сайте:
написание шаблона проверки, преобразования и бизнес-логики MongoDB — это непосильная задача.
Mongoose просто сделает нашу жизнь проще, а наш код — более элегантным.
Давайте продолжим и установим его с помощью следующей команды:
npm install mongoose
Мы также будем использовать паспорт-локальный-мангуст, что упростит интеграцию между Mongoose и Passport для локальной аутентификации. Это добавит hashи saltполе для нашей схемы для того, чтобы хранить хэш пароля и значение соли. Это замечательно, так как пароли никогда не должны храниться в базе данных в виде обычного текста.
Установим пакет:
npm install passport-local-mongoose
Теперь нам нужно настроить Mongoose. Надеюсь, вы уже знакомы с этим упражнением: добавьте следующий код в конец index.jsфайла:
/* MONGOOSE SETUP */ const mongoose = require('mongoose'); const passportLocalMongoose = require('passport-local-mongoose'); mongoose.connect('mongodb://localhost/MyDatabase', { useNewUrlParser: true, useUnifiedTopology: true }); const Schema = mongoose.Schema; const UserDetail = new Schema({ username: String, password: String }); UserDetail.plugin(passportLocalMongoose); const UserDetails = mongoose.model('userInfo', UserDetail, 'userInfo');
Здесь нам потребуются ранее установленные пакеты. Затем мы подключаемся к нашей базе данных с помощью mongoose.connectи указываем путь к нашей базе данных. Затем мы используем схему для определения нашей структуры данных. В этом случае, мы создаем UserDetailсхему с usernameи passwordпол.
Наконец, мы добавляем passportLocalMongooseв нашу схему как плагин. Это сработает часть волшебства, о котором мы говорили ранее. Затем мы создаем модель из этой схемы. Первый параметр — это имя коллекции в базе данных. Второй — это ссылка на нашу схему, а третий — это имя, которое мы присваиваем коллекции внутри Mongoose.
Это все, что касается настройки Mongoose. Теперь мы можем перейти к реализации нашей стратегии Passport.
Реализация локальной аутентификации
И, наконец, вот для чего мы сюда пришли! Настроим локальную аутентификацию. Как вы увидите ниже, мы просто напишем код, который настроит его для нас:
/* PASSPORT LOCAL AUTHENTICATION */ passport.use(UserDetails.createStrategy()); passport.serializeUser(UserDetails.serializeUser()); passport.deserializeUser(UserDetails.deserializeUser());
Здесь творится какое-то волшебство. Во-первых, мы passportиспользуем локальную стратегию, вызывая createStrategy()нашу UserDetailsмодель — любезно passport-local-mongoose- которая позаботится обо всем, так что нам не нужно настраивать стратегию. Довольно удобно.
Затем мы используем serializeUserи deserializeUserобратные вызовы. Первый будет вызываться при аутентификации, и его задача — сериализовать пользовательский экземпляр с информацией, которую мы ему передаем, и сохранить ее в сеансе через cookie. Второй будет вызываться каждый последующий запрос для десериализации экземпляра, предоставляя ему уникальный идентификатор файла cookie в качестве «учетных данных». Подробнее об этом можно прочитать в документации по паспорту.
Маршруты
Теперь давайте добавим несколько маршрутов, чтобы связать все вместе. Сначала мы добавим последний пакет. Зайдите в терминал и выполните следующую команду:
npm install connect-ensure-login
Пакет подключения-обеспечения-входа в систему представляет собой промежуточное программное обеспечение, которое обеспечивает вход пользователя в систему. Если получен запрос, который не прошел проверку подлинности, запрос будет перенаправлен на страницу входа. Мы будем использовать это для защиты наших маршрутов.
Теперь добавьте следующее в конец index.js:
/* ROUTES */ const connectEnsureLogin = require('connect-ensure-login'); app.post('/login', (req, res, next) => { passport.authenticate('local', (err, user, info) => { if (err) { return next(err); } if (!user) { return res.redirect('/login?info=' + info); } req.logIn(user, function(err) { if (err) { return next(err); } return res.redirect('/'); }); })(req, res, next); }); app.get('/login', (req, res) => res.sendFile('html/login.html', { root: __dirname }) ); app.get('/', connectEnsureLogin.ensureLoggedIn(), (req, res) => res.sendFile('html/index.html', {root: __dirname}) ); app.get('/private', connectEnsureLogin.ensureLoggedIn(), (req, res) => res.sendFile('html/private.html', {root: __dirname}) ); app.get('/user', connectEnsureLogin.ensureLoggedIn(), (req, res) => res.send({user: req.user}) );
Вверху мы требуем connect-ensure-login. Мы вернемся к этому позже.
Затем мы настраиваем маршрут для обработки запроса POST к /loginпути. Внутри обработчика мы используем метод password.authenticate, который пытается аутентифицироваться со стратегией, которую он получает в качестве первого параметра — в данном случае local. Если аутентификация не удалась, он перенаправит нас на /login, но добавит параметр запроса — info-, который будет содержать сообщение об ошибке. В противном случае, если аутентификация прошла успешно, он перенаправит нас на ’/’маршрут.
Затем мы настраиваем /loginмаршрут, по которому будет отправляться страница входа. Для этого мы используем res.sendFile () и передаем путь к файлу и наш корневой каталог, над которым мы работаем — отсюда и расширение__dirname.
/loginМаршрут будет доступен для всех, но наши следующих из них не будет. В /и /privateмаршрутах, которые мы будем посылать их соответствующие HTML — страницу, и вы заметите, что — то другое здесь. Перед обратным вызовом мы добавляем connectEnsureLogin.ensureLoggedIn()вызов. Это наш стражник маршрута. Его задача — проверить сеанс, чтобы убедиться, что вам разрешено просматривать этот маршрут. Теперь вы понимаете, что я имел в виду ранее, говоря «позволить серверу делать тяжелую работу»? Мы аутентифицируем пользователя каждый раз.
Наконец, нам понадобится /userмаршрут, который вернет объект с нашей пользовательской информацией. Это просто, чтобы показать вам, как вы можете получить информацию с сервера. Мы запросим этот маршрут у клиента и отобразим результат.
Говоря о клиенте, давайте сделаем это сейчас.
Клиент
Клиент должен быть довольно простым. Мы создадим несколько HTMLстраниц и CSSфайл. Начнем с домашней страницы или index. В корне вашего проекта создайте папку с именем htmlи добавьте файл с именем index.html. Добавьте к нему следующее:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title> Home </title> <link rel="stylesheet" href="css/styles.css"> </head> <body> <div class="message-box"> <h1 id="welcome-message"></h1> <a href="/private">Go to private area</a> </div> <script> const req = new XMLHttpRequest(); req.onreadystatechange = function () { if (req.readyState == 4 && req.status == 200) { const user = JSON.parse(req.response).user; document.getElementById("welcome-message").innerText = `Welcome ${user.username}!!`; } }; req.open("GET", "http://localhost:3000/user", true); req.send(); </script> </body> </html>
Здесь у нас есть пустой h1тег, в который мы поместим приветственное сообщение, а под ним ссылку на /private. Важнейшей частью здесь является scriptтег внизу, где мы будем обрабатывать получение имени пользователя для создания приветственного сообщения.
Он разделен на четыре части:
- Мы создаем объект запроса, используя new XMLHttpRequest().
- Мы устанавливаем onreadystatechangeсвойство с функцией, которая будет вызываться после того, как мы получим наш ответ. В обратном вызове мы проверяем, получили ли мы успешный ответ, и если да, мы анализируем ответ, получаем объект пользователя (тот, который мы отправили в /userмаршруте, помните?), И мы находим welcome-messageэлемент, чтобы установить его innerTextв наш username.
- Мы запрос к пользователю, и мы установили последний параметр, чтобы сделать это.open()GETURLtrueasynchronous
- Наконец, мы send()просим.
Теперь мы создадим страницу входа в систему. Как и раньше, в папке HTML создайте файл с именем login.htmlи добавьте в него следующее содержимое:
<!DOCTYPE html> <html lang="en"> <head> <title> Login </title> <link rel="stylesheet" href="css/styles.css"> </head> <body> <form action="/login" method="post"> <div class="title"> <h3>Login</h3> </div> <div class="field"> <label>Username:</label> <input type="text" name="username" /> <br /> </div> <div class="field"> <label>Password:</label> <input type="password" name="password" required /> </div> <div class="field"> <input class="submit-btn" type="submit" value="Submit" required /> </div> <label id="error-message"></label> </form> <script> const urlParams = new URLSearchParams(window.location.search); const info = urlParams.get('info'); if(info) { const errorMessage = document.getElementById("error-message"); errorMessage.innerText = info; errorMessage.style.display = "block"; } </script> </body> </html>
На этой странице, мы имеем простую регистрационную форму, с usernameи passwordполем, а также Submit кнопки. Ниже у нас есть ярлык, на котором мы будем отображать сообщения об ошибках. Помните, что они содержатся в строке запроса.
scriptТег в нижней части гораздо проще, на этот раз. Мы создаем экземпляр URLSearchParamsобъекта, передающего window.location.searchсвойство, которое содержит строку параметров в нашем URL-адресе. Затем мы используем URLSearchParams.get()метод, передавая имя параметра, который мы ищем.
На данный момент у нас либо есть информационное сообщение, либо нет. Итак, если мы это сделаем, мы получим error-messageэлемент и установим innerTextдля него то же самое, что и сообщение, а затем установим его style.displayсвойство на block. Это сделает его видимым, учитывая, что по умолчанию у него есть display: «none«значение.
Теперь давайте настроим приватную страницу. Снова создайте файл в папке HTML с именем private.htmlи добавьте следующий контент:
<!DOCTYPE html> <html lang="en"> <head> <title> Private </title> <link rel="stylesheet" href="css/styles.css"> </head> <body> <div class="message-box"> <h2>This is a private area</h2> <h3>Only you can see it</h3> <a href="/">Go back</a> </div> </body> </html>
Очень просто. Просто простое сообщение и Go backссылка, по которой мы вернемся на главную страницу.
Вот и все, что касается HTML, но, как вы, наверное, заметили, мы ссылаемся на CSSфайл с headтегами. Давайте сейчас добавим этот файл. Создайте папку с именем cssв корне нашего проекта и добавьте styles.cssв нее файл со следующим содержимым:
body { display: flex; align-items: center; background: #37474F; font-family: monospace; color: #cfd8dc; justify-content: center; font-size: 20px; } .message-box { text-align: center; } a { color: azure; } .field { margin: 10px; } input { font-family: monospace; font-size: 20px; border: none; background: #1c232636; color: #CFD8DC; padding: 7px; border: #4c5a61 solid 2px; width: 300px; } .submit-btn { width: 100% } .title { margin: 10px 0px 20px 10px } #error-message { color: #E91E63; display: block; margin: 10px; font-size: large; max-width: fit-content; }
Это сделает наши страницы достаточно приличными. Давай проверим!
Возьмите терминал, указывающий на корень проекта, и выполните следующую команду:
node index.js
Теперь перейдите по адресу http: // localhost: 3000 / в своем браузере. Вы должны быть перенаправлены на страницу входа. Если вы попытаетесь перейти по адресу http: // localhost: 3000 / private, он снова перенаправит вас на страницу входа. Наш маршрутный стражник делает свое дело.
Нажмите Ctrl+ Cв окне терминала, чтобы остановить наш сервер. Затем вернитесь к index.jsфайлу и в его нижней части добавьте следующие строки:
/* REGISTER SOME USERS */ UserDetails.register({username:'paul', active: false}, 'paul'); UserDetails.register({username:'jay', active: false}, 'jay'); UserDetails.register({username:'roy', active: false}, 'roy');
Он использует метод паспорта-местного-мангуста, registerчтобы солить пароль для нас. Нам просто нужно передать это в виде обычного текста.
Теперь бежим node index.js. Пользователи будут созданы. Вы должны прокомментировать эти последние строки сейчас.
Помните терминал оболочки MongoDB, который мы оставили открытым? Вернитесь к нему и введите:
db.userInfo.find()
Это должно показать ваших трех пользователей, и, как вы можете видеть, соль и хеш теперь занимают значительную часть пространства на терминале.
Это все, что нам нужно для работы приложения. Были сделаны!
Вернитесь в браузер, попробуйте войти в систему с одним из учетных данных, которые мы ввели, и вы увидите сообщение для входа с данным именем пользователя в нем.
Следующие шаги
Мы только добавили необходимые модули для работы этого приложения — ни больше, ни меньше. Для производственного приложения вам потребуется добавить другое промежуточное ПО и разделить код на модули. Вы можете принять это как задачу создать чистую и масштабируемую среду и превратить ее во что-то полезное!
Первое и самое простое, что вы должны попробовать, — это добавить с logoutпомощью метода Passport req.logout ().
Затем вы можете попробовать реализовать поток регистров. Вам понадобится регистрационная форма и маршрут для разговора. Вы должны использовать UserDetails.register()добавленный ранее как шаблон. Для подтверждения по электронной почте вы должны проверить nodemailer.
Еще вы могли бы попробовать применить эти концепции к одностраничному приложению. Возможно, используя Vue.js и его роутер. И вот ваши выходные!
Заключение
Что ж, мы наконец-то подошли к концу. В этой статье мы узнали, как реализовать локальную аутентификацию Passportв Node.jsприложении. В процессе мы также узнали, как подключиться к MongoDBusing Mongoose.
Возможно, для вас это было не так просто, как я пытался это нарисовать, но, по крайней мере, вы должны увидеть, что становится легче с этими инструментами, которые творят некоторую магию в фоновом режиме, позволяя нам беспокоиться только о том, что мы пытаемся построить.
«Волшебные» инструменты не всегда идеальны, но авторитетные и активно поддерживаемые инструменты помогают нам писать меньше кода — и код, который вы не пишете, — это код, который вы не поддерживаете, а код, который вы не поддерживаете, — это код, который вы не нарушаете.
Также имейте в виду, что если инструмент активно поддерживается основной командой, скорее всего, они знают, что делают лучше, чем любой из нас. Делегируйте, когда это возможно.
Надеюсь, вам понравился этот урок, и, возможно, вы получили вдохновение для своего следующего проекта. Удачного кодирования!