Создайте (минимальное) приложение с полным стеком с аутентификацией пользователя через паспорт и JWT.

Мы создадим минимальное приложение для входа / авторизации с полным стеком, используя стек MERN (MongoDB для нашей базы данных, Express и Node для нашего внутреннего интерфейса и React для нашего внешнего интерфейса). Мы также интегрируем Redux для управления состоянием наших React компонентов.

Полную кодовую базу можно посмотреть здесь:

Наше приложение позволит пользователям

  • регистр
  • Авторизоваться
  • Доступ к защищенным страницам, доступным только зарегистрированным пользователям
  • Оставайтесь в системе, когда они закрывают приложение или обновляют страницу
  • Выйти

Это должно стать прочной основой для создания более функционального полнофункционального приложения MERN. Ниже приведены примеры проектов, которые я создал с использованием этой базы mern-auth.

Эта серия статей также должна позволить вам более эффективно создавать приложения с полным стеком, используя любой бэкэнд / интерфейс. Например, этот проект научил меня тому, что мне нужно было знать для создания IB Vine, который я построил с использованием Gatsby (фреймворк React.js), Redux и Firebase для бэкэнда / базы данных.

В этой части (часть 1) мы

  • Инициализируем наш бэкэнд с помощью npm и устанавливаем необходимые пакеты
  • Настройка базы данных MongoDB с помощью mLab
  • Настройте сервер с Node.js и Express
  • Создайте схему базы данных для определения User для целей регистрации и входа в систему.
  • Настройте два маршрута API, register и login, используя passport + jsonwebtokens для аутентификации и validator для проверки ввода
  • Протестируйте наши маршруты API с помощью Postman

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

Опубликовано в блоге Bit

Bit помогает вам делиться и синхронизировать компоненты Javascript и UI в разных проектах и ​​приложениях, чтобы быстрее создавать вместе с вашей командой. Это OSS, попробуйте.



Прежде чем мы начнем

Предварительные требования

У вас должно быть хотя бы базовое понимание фундаментальных концепций программирования и некоторый опыт работы с вводным _18 _ / _ 19 _ / _ 20_. Если у вас нет опыта работы с Javascript, но вы работали с Python, Ruby или другим подобным серверным языком, вы все равно сможете продолжить.

Этот пост не предназначен для объяснения MERN стека или используемых в нем технологий, но является хорошим введением в создание с его помощью приложения с полным стеком. Однако вы можете (и должны) узнать больше о технологиях, включенных в стек, прежде чем приступить к работе (Mongo, Express, React, Node).

Вот два хороших стартовых ресурса.



Стеки MEAN и MERN
Веб-приложения и нативные приложения созданы с использованием« стека
различных технологий. Будучи студентом Flatiron, у меня есть… blog.cloudboost.io »



Установить

Наконец, убедитесь, что у вас установлено следующее.

  • Текстовый редактор (Atom) (или VS code / Sublime Text)
  • Последняя версия Node.js (мы будем использовать npm, или Диспетчер пакетов узлов, для установки зависимостей - очень похоже на pip для Python или gems для Ruby)
  • MongoDB (быстрая установка: установите Homebrew и запустите brew update && brew install mongodb)
  • Почтальон (для тестирования API)
  • Prettier (для простого форматирования нашего Javascript; в Atom, Пакеты → Prettier → Toggle Format on Save для автоматического форматирования при сохранении)

Давайте начнем.

Часть 1: Создание нашего бэкэнда

я. Инициализация нашего проекта

Установите текущий каталог там, где вы хотите, чтобы ваш проект жил, и инициализируйте проект с помощью npm.

➜  ~ mkdir mern-auth
➜  ~ cd mern-auth
➜  mern-auth npm init

После выполнения команды утилита проведет вас через создание файла package.json.

Вы можете enter пройти через большинство из них безопасно, но продолжайте и установите entry point на server.js вместо значения по умолчанию index.js при появлении запроса (это можно сделать позже в нашем package.json).

ii. Настройка нашего package.json

1. Установите “main” точку входа на “server.js” вместо значения по умолчанию “index.js”, если вы еще этого не сделали (для обычных целей)

2. Установите следующие зависимости, используя npm

mern-auth npm i bcryptjs body-parser concurrently express is-empty jsonwebtoken mongoose passport passport-jwt validator

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

  • bcryptjs: используется для хеширования паролей перед сохранением их в нашей базе данных
  • body-parser: используется для анализа тел входящих запросов в промежуточном программном обеспечении
  • concurrently: позволяет нам запускать наш бэкэнд и интерфейс одновременно и на разных портах
  • express: находится поверх Node, чтобы упростить написание маршрутизации, обработки запросов и ответов.
  • is-empty: глобальная функция, которая пригодится, когда мы будем использовать validator
  • jsonwebtoken: используется для авторизации
  • mongoose: используется для взаимодействия с MongoDB
  • passport: используется для аутентификации запросов с помощью расширяемого набора подключаемых модулей, известных как strategies.
  • passport-jwt: passport стратегия аутентификации с помощью JSON Web Token (JWT); позволяет аутентифицировать конечные точки с помощью JWT
  • validator: используется для проверки ввода (например, проверка правильности формата электронной почты, подтверждение совпадения паролей)

3. Установите следующую devDependency (-D), используя npm

mern-auth npm i -D nodemon

Nodemon - это утилита, которая будет отслеживать любые изменения в вашем коде и автоматически перезагружать ваш сервер, что идеально подходит для разработки. Альтернативой может быть отключение вашего сервера (Ctrl+C) и его резервное копирование каждый раз, когда вы вносите изменения. Не идеально.

Обязательно используйте nodemon вместо node при запуске кода в целях разработки.

4. Измените объект “scripts” на следующий

"scripts": {
    "start": "node server.js",
    "server": "nodemon server.js",
},

Позже мы будем использовать nodemon run server для запуска нашего сервера разработки.

На этом этапе ваш package.json файл должен выглядеть следующим образом.

{
  "name": "mern-auth",
  "version": "1.0.0",
  "description": "Mern Auth Example",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "server": "nodemon server.js"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "^1.18.3",
    "concurrently": "^4.0.1",
    "express": "^4.16.4",
    "is-empty": "^1.2.0",
    "jsonwebtoken": "^8.3.0",
    "mongoose": "^5.3.11",
    "passport": "^0.4.0",
    "passport-jwt": "^4.0.0",
    "validator": "^10.9.0"
  }
}

iii. Настройка нашей базы данных

1. Перейдите в mLab и создайте аккаунт, если у вас его еще нет



2. Создайте новое развертывание MongoDB

Выберите AWS в качестве поставщика облачных услуг и Песочница в качестве типа тарифного плана. Затем установите свой регион AWS в зависимости от того, где вы живете. Наконец, назовите свою базу данных и отправьте заказ (не волнуйтесь, это бесплатно).

3. Перейдите в панель управления и нажмите на созданную вами базу данных.

Перейдите на вкладку Users, щелкните Add Database User и создайте пользователя базы данных. Вашей базе данных нужен как минимум один пользователь, чтобы использовать ее.

Найдите свой MongoDB URI; мы будем использовать это для подключения к нашей базе данных.

mongodb://<dbuser>:<dbpassword>@ds159993.mlab.com:59993/mern-auth

Замените <dbuser> и <dbpassword> учетными данными пользователя базы данных, которые вы только что создали.

4. Создайте config каталог, а в нем keys.js файл

mern-auth mkdir config && cd config && touch keys.js

Внутри вашего keys.js файла разместите следующее для облегчения доступа за пределами этого файла.

module.exports = {
  mongoURI: "YOUR_MONGOURI_HERE" 
};

На этом пока все.

iv. Настройка нашего сервера с помощью Node.js и Express

Базовый процесс настройки нашего сервера выглядит следующим образом.

  • Вставьте наши необходимые зависимости (а именно express, mongoose и bodyParser)
  • Инициализируем наше приложение, используя express()
  • Примените функцию промежуточного программного обеспечения для bodyparser, чтобы мы могли ее использовать
  • Вытяните наш MongoURI из нашего keys.js файла и подключитесь к нашей базе данных MongoDB.
  • Установите port для работы нашего сервера и пусть наше приложение будет прослушивать этот порт.

Поместим следующее в наш server.js файл.

const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const app = express();
// Bodyparser middleware
app.use(
  bodyParser.urlencoded({
    extended: false
  })
);
app.use(bodyParser.json());
// DB Config
const db = require("./config/keys").mongoURI;
// Connect to MongoDB
mongoose
  .connect(
    db,
    { useNewUrlParser: true }
  )
  .then(() => console.log("MongoDB successfully connected"))
  .catch(err => console.log(err));
const port = process.env.PORT || 5000; // process.env.port is Heroku's port if you choose to deploy the app there
app.listen(port, () => console.log(`Server up and running on port ${port} !`));

Запустите nodemon run server, и должно получиться следующее.

mern-auth nodemon run server
[nodemon] 1.18.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node run server server.js`
Server up and running on port 5000 !
MongoDB successfully connected

Попробуйте изменить сообщение "Server up and running..." в вашем файле, нажмите «Сохранить», и вы увидите, что ваш сервер автоматически перезагружается.

Поздравляю! Вы настроили сервер с использованием NodeJS и Express и успешно подключились к своей базе данных MongoDB.

v. Настройка нашей схемы базы данных

Давайте создадим папку models для определения нашей пользовательской схемы. В models создайте файл User.js.

mern-auth mkdir models && cd models && touch User.js

В течение User.js мы будем

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

Поместим следующее в наш User.js файл.

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const UserSchema = new Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  date: {
    type: Date,
    default: Date.now
  }
});
module.exports = User = mongoose.model("users", UserSchema);

Довольно стандартная настройка для того, что вы ожидаете от пользователя.

vi. Настройка проверки формы

Прежде чем настраивать маршруты, давайте создадим каталог для проверки ввода и создадим файлы register.js и login.js для проверки каждого маршрута.

mern-auth mkdir validation && cd validation && touch register.js login.js

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

  • Вставьте зависимости validator и is-empty
  • Экспортируйте функцию validateRegisterInput, которая принимает data в качестве параметра (отправляется из нашей формы регистрации во внешнем интерфейсе, которую мы создадим во второй части).
  • Создайте экземпляр нашего объекта errors
  • Преобразуйте все пустые поля в пустую строку перед запуском проверок (validator работает только со строками)
  • Проверьте наличие пустых полей, допустимые форматы электронной почты, требования к паролю и подтвердите равенство паролей с помощью validator функций.
  • Вернуть наш объект errors с любыми и всеми ошибками, а также логическим isValid, которое проверяет, есть ли у нас какие-либо ошибки.

Поместим следующее в register.js.

const Validator = require("validator");
const isEmpty = require("is-empty");
module.exports = function validateRegisterInput(data) {
  let errors = {};
// Convert empty fields to an empty string so we can use validator functions
  data.name = !isEmpty(data.name) ? data.name : "";
  data.email = !isEmpty(data.email) ? data.email : "";
  data.password = !isEmpty(data.password) ? data.password : "";
  data.password2 = !isEmpty(data.password2) ? data.password2 : "";
// Name checks
  if (Validator.isEmpty(data.name)) {
    errors.name = "Name field is required";
  }
// Email checks
  if (Validator.isEmpty(data.email)) {
    errors.email = "Email field is required";
  } else if (!Validator.isEmail(data.email)) {
    errors.email = "Email is invalid";
  }
// Password checks
  if (Validator.isEmpty(data.password)) {
    errors.password = "Password field is required";
  }
if (Validator.isEmpty(data.password2)) {
    errors.password2 = "Confirm password field is required";
  }
if (!Validator.isLength(data.password, { min: 6, max: 30 })) {
    errors.password = "Password must be at least 6 characters";
  }
if (!Validator.equals(data.password, data.password2)) {
    errors.password2 = "Passwords must match";
  }
return {
    errors,
    isValid: isEmpty(errors)
  };
};

Наша проверка для нашего login.js осуществляется в том же порядке, что и выше, хотя и с другими полями.

const Validator = require("validator");
const isEmpty = require("is-empty");
module.exports = function validateLoginInput(data) {
  let errors = {};
// Convert empty fields to an empty string so we can use validator functions
  data.email = !isEmpty(data.email) ? data.email : "";
  data.password = !isEmpty(data.password) ? data.password : "";
// Email checks
  if (Validator.isEmpty(data.email)) {
    errors.email = "Email field is required";
  } else if (!Validator.isEmail(data.email)) {
    errors.email = "Email is invalid";
  }
// Password checks
  if (Validator.isEmpty(data.password)) {
    errors.password = "Password field is required";
  }
return {
    errors,
    isValid: isEmpty(errors)
  };
};

vii. Настройка наших маршрутов API

Теперь, когда мы прошли проверку, давайте создадим новую папку для наших маршрутов api и создадим users.js файл для регистрации и входа в систему.

mern-auth mkdir routes && cd routes && mkdir api && cd api && touch users.js

Вверху users.js давайте извлечем наши необходимые зависимости и загрузим наши проверки ввода и модель пользователя.

const express = require("express");
const router = express.Router();
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const keys = require("../../config/keys");
// Load input validation
const validateRegisterInput = require("../../validation/register");
const validateLoginInput = require("../../validation/login");
// Load User model
const User = require("../../models/User");

Создайте конечную точку регистрации

Для нашей конечной точки регистрации мы будем

  • Вытяните переменные errors и isValid из нашей функции validateRegisterInput(req.body) и проверьте правильность ввода
  • Если введенные данные действительны, используйте User.findOne() MongoDB, чтобы узнать, существует ли уже пользователь.
  • Если пользователь является новым пользователем, заполните поля (name, email, password) данными, отправленными в теле запроса.
  • Используйте bcryptjs для хеширования пароля перед сохранением его в базе данных

Поместим следующее в наш users.js файл для нашего маршрута регистрации.

// @route POST api/users/register
// @desc Register user
// @access Public
router.post("/register", (req, res) => {
  // Form validation
const { errors, isValid } = validateRegisterInput(req.body);
// Check validation
  if (!isValid) {
    return res.status(400).json(errors);
  }
User.findOne({ email: req.body.email }).then(user => {
    if (user) {
      return res.status(400).json({ email: "Email already exists" });
    } else {
      const newUser = new User({
        name: req.body.name,
        email: req.body.email,
        password: req.body.password
      });
// Hash password before saving in database
      bcrypt.genSalt(10, (err, salt) => {
        bcrypt.hash(newUser.password, salt, (err, hash) => {
          if (err) throw err;
          newUser.password = hash;
          newUser
            .save()
            .then(user => res.json(user))
            .catch(err => console.log(err));
        });
      });
    }
  });
});

Настроить паспорт

В каталоге config создайте файл passport.js.

mern-auth cd config && touch passport.js

Прежде чем настраивать паспорт, давайте добавим следующее в наш keys.js файл.

module.exports = {
  mongoURI: "YOUR_MONGOURI_HERE",
  secretOrKey: "secret"
};

Вернуться к passport.js. Вы можете узнать больше о стратегии passport-jwt по ссылке ниже. Он отлично разбирается в том, как построена стратегия аутентификации JWT, объясняя необходимые параметры, переменные и функции, такие как options, secretOrKey, jwtFromRequest, verify и jwt_payload.



Поместим следующее в наш passport.js файл.

const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const mongoose = require("mongoose");
const User = mongoose.model("users");
const keys = require("../config/keys");
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;
module.exports = passport => {
  passport.use(
    new JwtStrategy(opts, (jwt_payload, done) => {
      User.findById(jwt_payload.id)
        .then(user => {
          if (user) {
            return done(null, user);
          }
          return done(null, false);
        })
        .catch(err => console.log(err));
    })
  );
};

Также обратите внимание, что jwt_payload будет отправлен через нашу конечную точку входа ниже.

Создайте конечную точку входа

Для нашей конечной точки входа мы будем

  • Вытяните переменные errors и isValid из нашей функции validateLoginInput(req.body) и проверьте правильность ввода
  • Если введенные данные действительны, используйте User.findOne() MongoDB, чтобы узнать, существует ли пользователь.
  • Если пользователь существует, используйте bcryptjs для сравнения отправленного пароля с хешированным паролем в нашей базе данных.
  • Если пароли совпадают, создаем наш JWT Payload
  • Подпишите наш jwt, включая наш payload, keys.secretOrKey из keys.js, и установив expiresIn время (в секундах)
  • В случае успеха добавьте токен в Bearer string (помните, в нашем passport.js файле мы установилиopts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();)

Поместим следующее в наш users.js файл для нашего маршрута входа.

// @route POST api/users/login
// @desc Login user and return JWT token
// @access Public
router.post("/login", (req, res) => {
  // Form validation
const { errors, isValid } = validateLoginInput(req.body);
// Check validation
  if (!isValid) {
    return res.status(400).json(errors);
  }
const email = req.body.email;
  const password = req.body.password;
// Find user by email
  User.findOne({ email }).then(user => {
    // Check if user exists
    if (!user) {
      return res.status(404).json({ emailnotfound: "Email not found" });
    }
// Check password
    bcrypt.compare(password, user.password).then(isMatch => {
      if (isMatch) {
        // User matched
        // Create JWT Payload
        const payload = {
          id: user.id,
          name: user.name
        };
// Sign token
        jwt.sign(
          payload,
          keys.secretOrKey,
          {
            expiresIn: 31556926 // 1 year in seconds
          },
          (err, token) => {
            res.json({
              success: true,
              token: "Bearer " + token
            });
          }
        );
      } else {
        return res
          .status(400)
          .json({ passwordincorrect: "Password incorrect" });
      }
    });
  });
});

Не забудьте экспортировать наш маршрутизатор в конец users.js, чтобы мы могли использовать его где-нибудь еще.

module.exports = router;

Добавление наших маршрутов в наш server.js файл

Внесите в server.js следующие полужирные добавления.

const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const passport = require("passport");
const users = require("./routes/api/users");
const app = express();
// Bodyparser middleware
app.use(
  bodyParser.urlencoded({
    extended: false
  })
);
app.use(bodyParser.json());
// DB Config
const db = require("./config/keys").mongoURI;
// Connect to MongoDB
mongoose
  .connect(
    db,
    { useNewUrlParser: true }
  )
  .then(() => console.log("MongoDB successfully connected"))
  .catch(err => console.log(err));
// Passport middleware
app.use(passport.initialize());
// Passport config
require("./config/passport")(passport);
// Routes
app.use("/api/users", users);
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server up and running on port ${port} !`));

viii. Тестирование наших маршрутов API с помощью Postman

Тестирование нашей конечной точки регистрации

Откройте Почтальон и

  • Установите тип запроса POST
  • Установите URL-адрес запроса на http://localhost:5000/api/users/register
  • Перейдите на вкладку Body, выберите x-www-form-urlencoded, введите параметры регистрации и нажмите Send.

Вы должны получить ответ HTTP о состоянии 200 OK, а новый пользователь должен быть возвращен как JSON.

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

Тестирование нашей конечной точки входа

Как и в предыдущем случае, в Postman

  • Установите тип запроса POST
  • Установите URL-адрес запроса на http://localhost:5000/api/users/login
  • Перейдите на вкладку Body, выберите x-www-form-urlencoded, введите параметры входа и нажмите Send.

Вы должны получить ответ о состоянии HTTP 200 OK и получить jwt в ответе.

Ошибки тестирования

Вам следует поэкспериментировать и проверить свои validator ошибки, поигравшись с различными случаями, когда пользователь регистрируется и входит в систему (например, неверные форматы электронной почты, несоответствующие пароли). При тестировании API в Postman вы должны увидеть возвращенный объект ошибок.

В конечном итоге мы внесем эти ошибки в наш интерфейс и отобразим сообщения в самой форме.

На этом наш бэкэнд!

Мы успешно настроили и протестировали наши маршруты API (с использованием passport и jsonwebtokens для аутентификации). Похлопайте себя по плечу за то, что вы продолжили. Бросьте также несколько хлопков. 👏

В части 2 (см. Ниже) мы создадим наш интерфейс, используя React, будем использовать Redux для управления состоянием и начнем использовать axios для получения данных с нашего сервера.



Учить больше