Аутентификация между серверов на node js. Теперь запустим все это вместе. Маршруты Index и Users

Node Hero: Глава 8

В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.

Технологии, которые мы будем использовать

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

Что такое Passport.js?

Простая, ненавязчивая аутентификация для Node.js -  passportjs.org

Passport - это middleware для проверки подлинности, которую мы собираемся использовать для управления сессиями.

Что такое Redis?

Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений -  redis.io

Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.

Демонстрационное приложение

Для демонстрационных целей создадим приложение, которое умеет только следующее:

  • предоставляет форму входа
  • предоставляет две защищённые страницы:
  • страницу профиля
  • безопасные заметки
Структура проекта

Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!

Мы собираемся использовать следующую структуру:

├── app
| ├── authentication
| ├── note
| ├── user
| ├── index.js
| └── layout.hbs
├── config
| └── index.js
├── index.js
└── package.json

Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.

(Вы можете скачать исходный код по ссылке https://github.com/RisingStack/nodehero-authentication )

Процесс аутентификации в Node.js

Наша цель - реализовать в нашем приложении следующий процесс аутентификации:

  • Пользователь вводит имя и пароль
  • Приложение проверяет, являются ли они корректными
  • Если имя и пароль корректны, приложение отправляет заголовок Set-Cookie , который будет использоваться для аутентификации дальнейших страниц
  • Когда пользователь посещает страницы в том же домене, ранее установленный cookie будет добавлен ко всем запросам
  • Аутентификация на закрытых страницах происходит с помощью этого файла cookie
  • Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:

  • Настройте Express
  • Настройте Passport
  • Добавьте защищённые разделы на сайте
  • Шаг 1: Настройка Express

    Мы будем использовать Express для серверной среды - вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».

    // file:app/index.js
    const express = require("express")

    const session = require("express-session")
    const RedisStore = require("connect-redis")(session) const app = express()
    app.use(session({
    store: new RedisStore({
    url: config.redisStore.url
    }),
    secret: config.redisStore.secret,
    resave: false,
    saveUninitialized: false
    }))
    app.use(passport.initialize())
    app.use(passport.session())

    Что мы тут делаем?

    Прежде всего, нам нужны все зависимости, которые требуются для управления сессией. После этого мы создали новый экземпляр из модуля express-session , который будет хранить наши сессии.

    Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.

    Шаг 2: Настройка Passport

    Passport - отличный пример библиотеки, использующей плагины. В этом уроке мы добавляем модуль passport-local , который добавляет простую локальную стратегию аутентификации с использованием имён пользователей и паролей.

    Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser будет искать пользователя в базе данных.

    // file:app/authenticate/init.js
    const passport = require("passport")
    const LocalStrategy = require("passport-local").Strategy const user = {
    username: "test-user",
    password: "my-password",
    id: 1
    }
    passport.use(new LocalStrategy(
    function(username, password, done) {
    findUser(username, function (err, user) {
    if (err) {
    return done(err)
    }
    if (!user) {
    return done(null, false)
    }
    if (password !== user.password) {
    return done(null, false)
    }
    return done(null, user)
    })
    }
    ))

    Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароль, чтобы увидеть, есть ли совпадение.

    Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователь в passport - return done(null, user)), если нет - возвращаем ошибку авторизации(путём возврата null - return done(null)).

    Примечание переводчика: В настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.

    Шаг 3: Добавляем защищённые разделы на сайте

    Чтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:

    // file:app/user/init.js
    const passport = require("passport") app.get("/profile", passport.authenticationMiddleware(), renderProfile)

    Она имеет только одну задачу - если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.

    Использование этой middleware также просто, как добавление любой другой middleware в определение роута.

    // file:app/authentication/middleware.js
    function authenticationMiddleware () {
    return function (req, res, next) {
    if (req.isAuthenticated()) {
    return next()
    }
    res.redirect("/")
    }
    }

    Резюме

    В этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/ .

    Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub: https://github.com/RisingStack/nodehero-authentication

    Следующая глава Node Hero будет посвящена юнит-тестированию Node.js приложений. Вы узнаете такие концепции, как юнит-тестирование, пирамида тестирования, дублёры и многое другое!

    В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.

    This article was translated to Russian by Andrey Melikhov, a front-end developer from Yandex.Money and editor of the collective blog about front-end, devSchacht . Find Andrey on: Twitter , GitHub , Medium & SoundCloud

    Перевод этой статьи сделан Андреем Мелиховым, фронтенд-разработчиком из компании Яндекс.Деньги, редактором коллективного блога о фронтенде, devSchacht . Twitter | GitHub | Medium | SoundCloud



    Технологии, которые мы будем использовать

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

    Что такое Passport.js?

    Простая, ненавязчивая аутентификация для Node.js - passportjs.org

    Passport - это middleware для проверки подлинности, которую мы собираемся использовать для управления сессиями.

    Что такое Redis?

    Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений - redis.io

    Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.

    Демонстрационное приложение

    Для демонстрационных целей создадим приложение, которое умеет только следующее:

    • предоставляет форму входа
    • предоставляет две защищённые страницы:
      • страницу профиля
      • безопасные заметки
    Структура проекта

    Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!

    Мы собираемся использовать следующую структуру:

    ├── app | ├── authentication | ├── note | ├── user | ├── index.js | └── layout.hbs ├── config | └── index.js ├── index.js └── package.json

    Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.

    Процесс аутентификации в Node.js

    Наша цель - реализовать в нашем приложении следующий процесс аутентификации:

  • Пользователь вводит имя и пароль
  • Приложение проверяет, являются ли они корректными
  • Если имя и пароль корректны, приложение отправляет заголовок Set-Cookie , который будет использоваться для аутентификации дальнейших страниц
  • Когда пользователь посещает страницы в том же домене, ранее установленный cookie будет добавлен ко всем запросам
  • Аутентификация на закрытых страницах происходит с помощью этого файла cookie
  • Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:

  • Настройте Express
  • Настройте Passport
  • Добавьте защищённые разделы на сайте
  • Шаг 1: Настройка Express

    Мы будем использовать Express для серверной среды - вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».

    // file:app/index.js const express = require("express") const passport = require("passport") const session = require("express-session") const RedisStore = require("connect-redis")(session) const app = express() app.use(session({ store: new RedisStore({ url: config.redisStore.url }), secret: config.redisStore.secret, resave: false, saveUninitialized: false })) app.use(passport.initialize()) app.use(passport.session())

    Что мы тут делаем?

    Прежде всего, нам нужны все зависимости, которые требуются для управления сессией. После этого мы создали новый экземпляр из модуля express-session , который будет хранить наши сессии.

    Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.

    Шаг 2: Настройка Passport

    Passport - отличный пример библиотеки, использующей плагины. В этом уроке мы добавляем модуль passport-local , который добавляет простую локальную стратегию аутентификации с использованием имён пользователей и паролей.

    Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser будет искать пользователя в базе данных.

    File:app/authenticate/init.js const passport = require("passport") const LocalStrategy = require("passport-local").Strategy const user = { username: "test-user", password: "test-password", id: 1 } passport.use(new LocalStrategy(function(username, password, done) { findUser(username, function (err, user) { if (err) { return done(err) } if (!user) { return done(null, false) } if (password !== user.password) { return done(null, false) } return done(null, user) }) }))

    Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароль, чтобы увидеть, есть ли совпадение.

    Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователь в passport - return done(null, user)), если нет - возвращаем ошибку авторизации(путём возврата null - return done(null)).

    Примечание переводчика: В настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.

    Шаг 3: Добавляем защищённые разделы на сайте

    Чтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:

    // file:app/user/init.js const passport = require("passport") app.get("/profile", passport.authenticationMiddleware(), renderProfile)

    Она имеет только одну задачу - если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.

    Использование этой middleware также просто, как добавление любой другой middleware в определение роута.

    // file:app/authentication/middleware.js function authenticationMiddleware () { return function (req, res, next) { if (req.isAuthenticated()) { return next() } res.redirect("/") } }

    Резюме

    В этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/ .

    Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub: https://github.com/RisingStack/nodehero-authentication

    Следующая глава Node Hero будет посвящена юнит-тестированию Node.js приложений. Вы узнаете такие концепции, как юнит-тестирование, пирамида тестирования, дублёры и многое другое!

    Gergely Nemeth

    Co-founder of RisingStack

    Please enable JavaScript to view the

    В этой статье я опишу полноценное решение по авторизации на основе JSON Web Token (JWT) для Node.js и Koa с хранением хэшей паролей в MongoDB. От читателя ожидаются базовые знания Node.js и принципов работы с MongoDB через Mongoose.

    Несколько слов, о чем конкретно пойдет речь и почему.

    Почему Коа. Не смотря на значительно большую популярность фреймворка Express, Koa предоставляет возможность писать приложения используя современный синтаксис async/await. Использование async/await вместо callback’ов является достаточно большим стимулом, чтобы присмотреться к этому фреймворку.

    Почему JWT. Подход к авторизации с помощью сессий можно уже назвать устаревшим, так как он не позволяет использовать его в мобильных приложениях и там, где нет поддержки cookies. Также проблемы с сессиями могут возникнуть в кластерных системах. JWT авторизация не имеет этих недостатков, и обладает еще рядом дополнительных преимуществ. Более подробно про JWT можно прочитать

    В статье будет рассмотрено полноценное решение по авторизации с использованием:

  • passport.js. Де-факто стандарт для работы с авторизацией в Node.js проектах
  • хешированием паролей и хранением хэшей в базе MongoDB
  • аутентификацией для REST API
  • аутентификацией для socket.io, что является обычно более сложной темой, чем п.3
  • Чтобы сохранить образовательную ценность статьи в коде не будет расширенных проверок на ошибки и исключения, которые часто делают код менее понятным. Поэтому перед использованием примеров кода в продакшене, надо поработать над обработкой ошибок и контролем входных данных от клиента.Итак, начнем 1. Подключаем Koa. В отличие от Express, Koa является более легким фреймворком и поэтому, обычно, используется с рядом дополнительных модулей.

    Const Koa = require("koa"); // ядро const Router = require("koa-router"); // маршрутизация const bodyParser = require("koa-bodyparser"); // парсер для POST запросов const serve = require("koa-static"); // модуль, который отдает статические файлы типа index.html из заданной директории const logger = require("koa-logger"); // опциональный модуль для логов сетевых запросов. Полезен при разработке. const app = new Koa(); const router = new Router(); app.use(serve("public")); app.use(logger()); app.use(bodyParser());
    2. Подключаем Passport.js . Passport.js позволяет гибко настраивать авторизацию, используя разные механизмы, которые называются Стратегиями (локальная, социальные сети д.р.). В настоящий момент библиотека насчитывает более 300 вариантов стратегий.

    Const passport = require("koa-passport"); //реализация passport для Koa const LocalStrategy = require("passport-local"); //локальная стратегия авторизации const JwtStrategy = require("passport-jwt").Strategy; // авторизация через JWT const ExtractJwt = require("passport-jwt").ExtractJwt; // авторизация через JWT app.use(passport.initialize()); // сначала passport app.use(router.routes()); // потом маршруты const server = app.listen(3000);// запускаем сервер на порту 3000
    3. Подключаем работу с JWT. В двух словах JWT - это просто JSON в котором может храниться, например, email пользователя. Этот JSON подписывается секретным ключом, что не позволяет этот email изменить, хотя позволяет его прочитать.

    Таким образом, получая с клиента JWT вы уверены, что к вам пришел именно тот пользователь, за которого он себя выдает (при условии, что его JWT не был кем-то украден, но это уже совсем другая история).

    Const jwtsecret = "mysecretkey"; // ключ для подписи JWT const jwt = require("jsonwebtoken"); // аутентификация по JWT для hhtp const socketioJwt = require("socketio-jwt"); // аутентификация по JWT для socket.io
    4. Подключаем socket.io. В двух словах socket.io - это модуль для работы приложений, которые реагируют на изменения происходящие на сервере, например его можно использовать для чата. Если сервер и браузер поддерживают протокол WebSockets, то socket.io будет использовав его, иначе он поищет другие механизмы реализации двустороннего общения браузера с сервером.

    Const socketIO = require("socket.io");
    5. Подключаем MongoDB для хранения объектов пользователей.

    Const mongoose = require("mongoose"); // стандартная прослойка для работы с MongoDB const crypto = require("crypto"); // модуль node.js для выполнения различных шифровальных операций, в т.ч. для создания хэшей.

    Теперь запустим все это вместе Объект пользователя (user ) будет состоять из его имени, e-mail и хэша пароля.

    Для превращения пароля, получаемого из POST запроса в хэш, который будет храниться в базе применяется концепция виртуальных полей. Виртуальное поле - это поле, которое есть в модели Mongoose, но которого нет в базе MongoDB.

    Mongoose.Promise = Promise; // Просим Mongoose использовать стандартные Промисы mongoose.set("debug", true); // Просим Mongoose писать все запросы к базе в консоль. Удобно для отладки кода mongoose.connect("mongodb://localhost/test"); // Подключаемся к базе test на локальной машине. Если базы нет, она будет создана автоматически.
    Создаем схему и модель для Пользователя:

    Const userSchema = new mongoose.Schema({ displayName: String, email: { type: String, required: "Укажите e-mail", unique: "Такой e-mail уже существует" }, passwordHash: String, salt: String, }, { timestamps: true }); userSchema.virtual("password") .set(function (password) { this._plainPassword = password; if (password) { this.salt = crypto.randomBytes(128).toString("base64"); this.passwordHash = crypto.pbkdf2Sync(password, this.salt, 1, 128, "sha1"); } else { this.salt = undefined; this.passwordHash = undefined; } }) .get(function () { return this._plainPassword; }); userSchema.methods.checkPassword = function (password) { if (!password) return false; if (!this.passwordHash) return false; return crypto.pbkdf2Sync(password, this.salt, 1, 128, "sha1") == this.passwordHash; }; const User = mongoose.model("User", userSchema);
    Для более глубокого понимания механизма работы с хэшами паролей можно почитать про команду pbkdf2Sync в доке по Node.js

    Настраиваем работу с Passport.js Процесс авторизации пользователя выглядит следующим образом:

    Шаг 1. Новый пользователь регистрируется, и создается запись о нем в базе MongoDB.
    Шаг 2. Пользователь логинится с паролем на сайте и при успешном вводе логина и пароля получает JWT.
    Шаг3. Пользователь заходит на произвольный ресурс, отсылает свой JWT, по которому и авторизуется уже без ввода пароля.

    Механизм настройки Passport.js состоит из двух этапов:

    Этап 1. Настройка Стратегий. Стратегия при успешной авторизации возвращает объект user, описанный ранее в схеме userSchema.
    Этап 2. Использование полученного на этапе 1 объекта user для последующих действий, например, создания для него JWT.

    Этап 1 Настраиваем Passport Local Strategy. Более подробно, как работает стратегия можно прочитать на .

    Passport.use(new LocalStrategy({ usernameField: "email", passwordField: "password", session: false }, function (email, password, done) { User.findOne({email}, (err, user) => { if (err) { return done(err); } if (!user || !user.checkPassword(password)) { return done(null, false, {message: "Нет такого пользователя или пароль неверен."}); } return done(null, user); }); }));
    Настраиваем Passport JWT Strategy. Более подробно, как работает стратегия можно прочитать на .

    // Ждем JWT в Header const jwtOptions = { jwtFromRequest: ExtractJwt.fromAuthHeader(), secretOrKey: jwtsecret }; passport.use(new JwtStrategy(jwtOptions, function (payload, done) { User.findById(payload.id, (err, user) => { if (err) { return done(err) } if (user) { done(null, user) } else { done(null, false) } }) }));

    Этап 2 Мы создадим REST API, который будет работать с объектом user.

    API будет состоять из трех endpoints, соответствующих трем Шагам процесса авторизации, описанному выше.

    Post запрос на /user – создает нового пользователя. Обычно этот API вызывается при регистрации нового пользователя. В теле запроса мы ожидаем JSON с именем, почтой и паролем пользователя.

    Router.post("/user", async(ctx, next) => { try { ctx.body = await User.create(ctx.request.body); } catch (err) { ctx.status = 400; ctx.body = err; } });
    Post запрос на /login создает JWT для пользоваться. В теле запроса мы ожидаем получить JSON в котором будет почта и пароль пользователя. В продакшене логично JWT выдавать также и при регистрации пользователя.

    Router.post("/login", async(ctx, next) => { await passport.authenticate("local", function (err, user) { if (user == false) { ctx.body = "Login failed"; } else { //--payload - информация которую мы храним в токене и можем из него получать const payload = { id: user.id, displayName: user.displayName, email: user.email }; const token = jwt.sign(payload, jwtsecret); //здесь создается JWT ctx.body = {user: user.displayName, token: "JWT " + token}; } })(ctx, next); });
    GET запрос на /custom проверяет наличие валидного JWT.

    Router.get("/custom", async(ctx, next) => { await passport.authenticate("jwt", function (err, user) { if (user) { ctx.body = "hello " + user.displayName; } else { ctx.body = "No such user"; console.log("err", err) } })(ctx, next) });
    Теперь сделаем финальный аккорд по настройке авторизации для socket.io. Проблема тут в том, что протокол WebSockets работает поверх tcp, а не http и механизмы REST API к нему не применимы. К счастью, для него есть модуль socketio-jwt, который позволяет достаточно лаконично описать авторизацию через JWT.

    Let io = socketIO(server); io.on("connection", socketioJwt.authorize({ secret: jwtsecret, timeout: 15000 })).on("authenticated", function (socket) { console.log("Это мое имя из токена: " + socket.decoded_token.displayName); socket.on("clientEvent", (data) => { console.log(data); }) });
    Более подробно про авторизацию через JWT для socket.io можно почитать .

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

    Полную версию кода с описание того, как его протестировать можно посмотреть в

    Так же как аутентификация важна для API (* Application Programming Interface - программный интерфейс приложения. Здесь и далее примеч. пер.), она является важной особенностью определенных веб-приложений - тех, у которых имеются страницы и секреты, к которым должен быть доступ только у пользователей, прошедших регистрацию и аутентификацию.

    В данном руководстве в процессе изучения реализации регистрации пользователей вы создадите веб-приложение.

    Установка приложения

    Создайте новую папку, в которой вы будете работать. В соответствии с темой данного руководства я назвал мою site-auth . Инициализируйте проект в только что созданной папке. Ниже показано, как вы можете это сделать.

    Npm init -y

    Флаг -y сообщает npm (* менеджер пакетов для JavaScript) о необходимости использования опций по умолчанию.

    Отредактируйте часть файла package.json так, чтобы она выглядела, как моя.

    #package.json { "name": "site-auth", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": , "author": "izuchukwu1", "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.17.1", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.3", "express": "^4.15.2", "express-handlebars": "^3.0.0", "express-messages": "^1.0.1", "express-session": "^1.15.2", "joi": "^13.0.1", "mongoose": "^4.11.12", "morgan": "^1.8.1", "passport": "^0.4.0", "passport-local": "^1.0.0" } }

    После этого запустите команду для установки пакетов.

    Npm install

    Создайте файл в вашей рабочей папке под названием app.js.

    Начните с запрашивания зависимостей, которые вы установили, и необходимых файлов.

    #app.js const express = require("express"); const morgan = require("morgan") const path = require("path"); const cookieParser = require("cookie-parser"); const bodyParser = require("body-parser"); const expressHandlebars = require("express-handlebars"); const flash = require("connect-flash"); const session = require("express-session"); const mongoose = require("mongoose") const passport = require("passport") require("./config/passport")

    Эти зависимости были установлены после выполнения команды npm install. Для того чтобы их использовать в вашем приложении вам необходимо запросить и сохранить их в соответствующие константы.

    В данном руководстве вы будете использовать в качестве базы данных MongoDB. Вам необходимо будет сохранить информацию о пользователе в базу данных. Для работы с MongoDB вы будете использовать Mongoose - инструмент для моделирования в Node.js (* ODM (Object Document Mapper - объектно-документный отобразитель)).Настройка Mongoose не вызывает трудностей. Выполняется следующим образом:

    #app.js mongoose.Promise = global.Promise mongoose.connect("mongodb://localhost:27017/site-auth")

    На данном этапе давайте настроим наше промежуточное ПО (* программное обеспечение).

    // 1 const app = express() app.use(morgan("dev")) // 2 app.set("views", path.join(__dirname, "views")) app.engine("handlebars", expressHandlebars({ defaultLayout: "layout" })) app.set("view engine", "handlebars") // 3 app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, "public"))) app.use(session({ cookie: { maxAge: 60000 }, secret: "codeworkrsecret", saveUninitialized: false, resave: false })); app.use(passport.initialize()) app.use(passport.session()) // 4 app.use(flash()) app.use((req, res, next) => { res.locals.success_mesages = req.flash("success") res.locals.error_messages = req.flash("error") next() }) // 5 app.use("/", require("./routes/index")) app.use("/users", require("./routes/users")) // 6 // catch 404 and forward to error handler app.use((req, res, next) => { res.render("notFound") }); // 7 app.listen(5000, () => console.log("Server started listening on port 5000!"))

  • Результат инициализации Express присваивается константе app .
  • Происходит настройка промежуточного ПО, которое будет заниматься представлениями. Для создания представлений вы будете использовать handlebars .
  • Вы настроили промежуточное ПО для bodyparser , cookie , session и passport . Passport будет использоваться, когда пользователи хотят войти в приложение.
  • В определенные моменты вы будете отображать флэш-сообщения. Поэтому вам необходимо настроить промежуточное ПО для этого и создать необходимый вам тип флэш-сообщений.
  • Промежуточное ПО для настройки маршрутов. Будет обрабатывать любой запрос, сделанный к пути URL (Uniform Resource Locator - унифицированный указатель [местонахождения информационного] ресурса). Здесь указаны пути URL для путей, указанных в файлах index и users.
  • Промежуточное ПО для обработки ошибок 404. Данное промежуточное ПО начинает действовать, если запрос не соответствует никакому из вышеуказанных промежуточному ПО.
  • Сервер настраивается на прослушивание запросов по 5000 порту.
  • Настройка представлений

    views (* представления). Внутри нее создайте две другие папки под названиями layouts и partials . Сформируйте подобную древовидную структуру в папке views. Для этого создайте необходимые файлы в их соответствующих папках.

    ├── dashboard.handlebars ├── index.handlebars ├── layouts │ └── layout.handlebars ├── login.handlebars ├── notFound.handlebars ├── partials │ └── navbar.handlebars └── register.handlebars

    После этого пришло время писать код.

    #dashboard.handlebars User DashBoard

    Это - инструментальная панель, которая должна быть видна только зарегистрированным пользователям. В данном руководстве это будет ваша секретная страница.

    #index.handlebars Site Authentication!

    Welcome aboard.

    Для приложения необходим макет. Ниже приведен макет, который будете использовать.

    #layout/layout.handlebars Site Authentication {{#if success_messages }} {{success_messages}} {{/if}} {{#if error_messages }} {{error_messages}} {{/if}} {{> navbar}} {{{body}}}

    Вам понадобится страница входа для зарегистрированных пользователей.

    #views/login.handlebars Please sign in Email address Password
    Sign in

    Файл notFound .handlebars будет использоваться в качестве страницы для показа ошибок.

    #views/notFound.handlebars Error

    Ваша страница для регистрации должна выглядеть следующим образом.

    Please sign up Email address Username Password Confirm Password
    Sign up

    Последний файл для ваших представлений с кодом навигационной панели приводится ниже.

    #partials/navbar.handlebars Site Authentication

    После этого вы готовы к рассмотрению некоторых сложных частей приложения.

    Проверка данных

    Вам необходима будет модель User. Исходя из кода для представлений выше, вы можете прийти к заключению, что для модели User необходимы свойства email, username и password. Создайте папку под названием models и файл в ней под названием user.js .

    #models/user.js // 1 const mongoose = require("mongoose") const Schema = mongoose.Schema const bcrypt = require("bcryptjs") // 2 const userSchema = new Schema({ email: String, username: String, password: String }, { // 3 timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" } }) // 4 const User = mongoose.model("user", userSchema) module.exports = User

  • Происходит импорт зависимостей и их сохранение в переменных.
  • Создается новая схема. Для каждого пользователя вы сохраняете email , username и password в базу данных. Схема описывает, как должна быть создана модель для каждого документа. Здесь вы указываете, что типом адреса электронной почты, имени пользователя пароль должен быть String.
  • Также для каждого пользователя вы хотите создать timestamps (* время создания/модификации файла). Вы используете Mongoose для получения значений createdAt и updatedAt . Затем сохраняете их в базу данных.
  • Определяется модель, и результат присваивается константе под названием User , которая потом экспортируется в качестве модуля. Поэтому ее можно использовать в других частях приложения.
  • Подсаливание и хеширование пароля

    Не стоит сохранять пароли пользователей в незашифрованном виде. Вот что следует сделать, когда пользователь вводит пароль в незашифрованном виде при регистрации. Пароль в незашифрованном виде должен быть захеширован при помощи соли (* в криптографии - случайное число или текст, которые добавляются к данным, шифруемым с помощью пароля), которая будет сгенерирована вашим приложением (используя модуль bcryptjs). Этот захешированный пароль затем сохраняется в базу данных.

    Звучит замечательно, неправда ли? Давайте реализуем это в файле user.js

    #models/user.js module.exports.hashPassword = async (password) => { try { const salt = await bcrypt.genSalt(10) return await bcrypt.hash(password, salt) } catch(error) { throw new Error("Hashing failed", error) } }

    Вы только что создали метод, который будет вызван при регистрации пользователя. В метод передается незашифрованный пароль, введенный пользователем. Как я упоминал ранее, пароль в незашифрованном виде должен быть захеширован при помощи соли, которая будет сгенерирована вашим приложением. Захешированный пароль будет возвращен как пароль для пользователя.

    Маршруты Index и Users

    Создайте новую папку под названием routes . В этой новой папке создайте два новых файла: index.js и users.js .

    Файл index.js будет очень простым. В нем будет вызываться на исполнение представление index вашего приложения. Помните, как вы настроили промежуточное ПО для ваших маршрутов в вашем файле app.js , когда вы сделали следующее:

    App.use("/", require("./routes/index")) app.use("/users", require("./routes/users"))

    Итак, ваш маршрут в файле index.js, просто исполняющий страницу index, должен выглядеть следующим образом:

    #routes/index.js const express = require("express") const router = express.Router() router.get("/", (req, res) => { res.render("index") }) module.exports = router

    Теперь переходим к маршруту users. Пока что в этом файле для маршрута будут выполняться четыре действия:

  • Запрос зависимостей. Вам необходимо будет запросить зависимости, которые вы установили при помощи npm.
  • Проверка пользовательских данных. Убедитесь, что пользователь не прислал пустую форму. Все поля должны быть заполнены. Введенные данные должны иметь тип String. Адрес электронной почты проходит специальную проверку при помощи метода.email() , благодаря которому гарантируется, что введенные пользователем данные соответствуют формату адреса электронной почты. Пароль же проверяется при помощи регулярного выражения. В пароле подтверждения следует сохранить то же значение, что и во введенном пароле. Эти проверки осуществляются при помощи модуля Joi .
  • При поступлении запросов по методу GET исполняется страница регистрации, в то время как запросы по методу POST поступают после того, как пользователь нажимает кнопку для отправки формы.
  • Маршрутизатор экспортируется в виде модуля.
  • Вот как выглядит код.

    #routes/users.js const express = require("express"); const router = express.Router() const Joi = require("joi") const passport = require("passport") const User = require("../models/user") //validation schema const userSchema = Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), password: Joi.string().regex(/^{6,30}$/).required(), confirmationPassword: Joi.any().valid(Joi.ref("password")).required() }) router.route("/register") .get((req, res) => { res.render("register") }) .post(async (req, res, next) => { try { const result = Joi.validate(req.body, userSchema) if (result.error) { req.flash("error", "Data entered is not valid. Please try again.") res.redirect("/users/register") return } const user = await User.findOne({ "email": result.value.email }) if (user) { req.flash("error", "Email is already in use.") res.redirect("/users/register") return } const hash = await User.hashPassword(result.value.password) delete result.value.confirmationPassword result.value.password = hash const newUser = await new User(result.value) await newUser.save() req.flash("success", "Registration successfully, go ahead and login.") res.redirect("/users/login") } catch(error) { next(error) } }) module.exports = router

    Давайте рассмотрим подробнее, что происходит при поступлении запросов по методу POST .

    Значения, введенные в форме для регистрации, доступны через свойство req.body и выглядят подобным образом:

    Value: { email: "[email protected]", username: "izu", password: "chinedu", confirmationPassword: "chinedu" },

    Это свойство проверяется с помощью схемы userSchema , которую вы создали ранее. Введенные пользователем значения присваиваются константе под названием result.

    В случае ошибки при проверке данных сообщение об ошибке отображается пользователю и происходит перенаправление пользователя на страницу регистрации.

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

    В ином случае следующим этапом является хеширование пароля. В этот момент вы и вызываете метод hashPassword , который определили в вашем файле user.js. Новый захешированный пароль присваивается константе hash.

    Нет необходимости в сохранении confirmationPassword в базу данных. Поэтому это свойство удаляется. Доступный в переменной пароль по прежнему незашифрован. Поскольку не следует сохранять незашифрованный пароль в вашей базе данных, важно заново присвоить в качестве значения пароля созданный ранее хеш. Это осуществляется при помощи следующей строки кода:

    Result.value.password = hash

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

    Запустите ваш сервер, выполнив в консоли команду:

    Node app.js

    Перейдите в вашем браузере на страницу http://localhost:5000 , и вы должны будете увидеть ваше новое приложение.

    Заключение

    Теперь вы знаете, как реализовать возможность регистрации в Node-приложениях. Вы поняли важность проверки пользовательских данных и процесс ее осуществления при помощи Joi. Также вы использовали модуль bcryptjs для подсаливания и хеширования вашего пароля.