Статьи

Аутентификация сайта в Node.js: регистрация пользователя

Так же, как аутентификация важна в API, она также важна в определенных веб-приложениях — тех, у которых есть страницы и секреты, которые должны быть доступны только зарегистрированным и аутентифицированным пользователям.

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

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

1
npm init -y

Флаг -y указывает npm использовать параметры по умолчанию.

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

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
#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»
  }
}

Сделав это, запустите команду для установки зависимостей.

1
npm install

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
#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 — инструмент моделирования MongoDB для Node.js. Настроить Mongoose легко, вот так.

1
2
3
4
#app.js
 
mongoose.Promise = global.Promise
mongoose.connect(‘mongodb://localhost:27017/site-auth’)

На данный момент, давайте настроим наше промежуточное программное обеспечение.

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
// 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!’))
  1. Экспресс инициализируется и назначается app .
  2. Промежуточное программное обеспечение для обработки представлений настроено. Для просмотра вы будете использовать handlebars .
  3. Вы устанавливаете промежуточное ПО для bodyparser , cookie , session и passport . Паспорт будет использоваться, когда пользователи хотят войти.
  4. В некоторые моменты вы будете отображать флеш-сообщения. Таким образом, вам нужно настроить промежуточное программное обеспечение для этого, а также создать тип флэш-сообщений, которые вы хотите.
  5. Промежуточное программное обеспечение маршрутов — оно обработает любой запрос к пути URL. Указанные здесь URL-пути относятся к индексу и пути пользователя.
  6. Промежуточное ПО для обработки 404 ошибок. Это промежуточное ПО запускается, когда запрос не сопоставляется ни с одним из промежуточного ПО, созданного над ним.
  7. Сервер настроен на прослушивание через порт 5000.

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

1
2
3
4
5
6
7
8
9
├── dashboard.handlebars
├── index.handlebars
├── layouts
│  └── layout.handlebars
├── login.handlebars
├── notFound.handlebars
├── partials
│  └── navbar.handlebars
└── register.handlebars

С этим, пришло время сбросить код.

1
2
3
4
5
6
#dashboard.handlebars
 
<!— Jumbotron —>
<div class=»jumbotron»>
  <h1>User DashBoard</h1>
</div>

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

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

1
2
3
4
5
6
7
#index.handlebars
 
<!— Jumbotron —>
<div class=»jumbotron»>
  <h1>Site Authentication!</h1>
  <p class=»lead»>Welcome aboard.</p>
</div>

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

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
#layout/layout.handlebars
 
<!DOCTYPE html>
<html>
  <head>
    <title>Site Authentication</title>
    <link rel=»stylesheet»
          href=»https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css»
          integrity=»sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u»
          crossorigin=»anonymous»>
    <link rel=»stylesheet»
          href=»https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css»
          integrity=»sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp»
          crossorigin=»anonymous»>
    <link rel=»stylesheet»
          href=»/css/style.css»>
  </head>
  <body>
    {{#if success_messages }}
      <div class=»alert alert-success»>{{success_messages}}</div>
    {{/if}}
    {{#if error_messages }}
      <div class=»alert alert-danger»>{{error_messages}}</div>
    {{/if}}
    <div class=»container»>
      {{> navbar}}
      {{{body}}}
    </div>
 
    <script src=»https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js»
            integrity=»sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa»
            crossorigin=»anonymous»></script>
  </body>
</html>

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
#views/login.handlebars
 
<form class=»form-signin» action=»/users/login» method=»POST»>
  <h2 class=»form-signin-heading»>Please sign in</h2>
 
  <label for=»inputEmail» class=»sr-only»>Email address</label>
  <input type=»email» id=»inputEmail» name=»email» class=»form-control» placeholder=»Email address» required autofocus>
 
  <label for=»inputPassword» class=»sr-only»>Password</label>
  <input type=»password» id=»inputPassword» name=»password» class=»form-control» placeholder=»Password» required>
 
  <br/>
   
  <button class=»btn btn-lg btn-default btn-block» type=»submit»>Sign in</button>
</form>

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

1
2
3
4
5
6
#views/notFound.handlebars
 
<!— Jumbotron —>
<div class=»jumbotron»>
  <h1>Error</h1>
</div>

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<form class=»form-signin» action=»/users/register» method=»POST»>
  <h2 class=»form-signin-heading»>Please sign up</h2>
 
  <label for=»inputEmail» class=»sr-only»>Email address</label>
  <input type=»email» id=»inputEmail» name=»email» class=»form-control» placeholder=»Email address» required autofocus>
 
  <label for=»inputUsername» class=»sr-only»>Username</label>
  <input type=»text» id=»inputUsername» name=»username» class=»form-control» placeholder=»Username» required>
 
  <label for=»inputPassword» class=»sr-only»>Password</label>
  <input type=»password» id=»inputPassword» name=»password» class=»form-control» placeholder=»Password» required>
 
  <label for=»inputConfirmPassword» class=»sr-only»>Confirm Password</label>
  <input type=»password» id=»inputConfirmPassword» name=»confirmationPassword» class=»form-control» placeholder=»Confirm Password» required>
 
  <br/>
 
  <button class=»btn btn-lg btn-default btn-block» type=»submit»>Sign up</button>
</form>

Наконец, для ваших просмотров, вот ваша панель навигации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#partials/navbar.handlebars
 
<div class=»masthead»>
  <h3 class=»text-muted»>Site Authentication</h3>
  <nav>
    <ul class=»nav nav-justified»>
      <li class=»active»><a href=»/»>Home</a></li>
      {{#if isAuthenticated}}
        <li><a href=»/users/dashboard»>Dashboard</a></li>
        <li><a href=»/users/logout»>Logout</a></li>
      {{else}}
        <li><a href=»/users/register»>Sign Up</a></li>
        <li><a href=»/users/login»>Sign In</a></li>
      {{/if}}
    </ul>
  </nav>
</div>

Сделав это, вы можете углубиться в некоторые глубокие части.

Вам понадобится модель пользователя. Из приведенного выше кода представлений вы можете сделать вывод, что для модели User необходимы свойства электронной почты, имени пользователя и пароля. Создайте каталог с именем models и файл в нем с именем user.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
#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
  1. Импортирует зависимости и сохраняет их в константах.
  2. Новая схема создана. Для каждого пользователя вы хотите сохранить email , username и password в базе данных. Схема показывает, как модель должна быть построена для каждого документа. Здесь вы хотите, чтобы адрес электронной почты, имя пользователя и пароль имели тип String.
  3. Для каждого пользователя, сохраненного в базе данных, вы также хотите создать timestamps . Вы используете Mongoose для получения createdAt и updatedAt , а затем они сохраняются в базе данных.
  4. Модель определяется и присваивается константе с именем User , которая затем экспортируется как модуль, чтобы ее можно было использовать в других частях приложения.

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

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

01
02
03
04
05
06
07
08
09
10
#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.js и users.js .

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

1
2
app.use(‘/’, require(‘./routes/index’))
app.use(‘/users’, require(‘./routes/users’))

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

01
02
03
04
05
06
07
08
09
10
#routes/index.js
 
const express = require(‘express’)
const router = express.Router()
 
router.get(‘/’, (req, res) => {
    res.render(‘index’)
})
 
module.exports = router

Теперь к маршруту пользователей. На данный момент этот файл маршрута будет делать четыре вещи.

  1. Требовать зависимости. Вам понадобится установить зависимости, которые вы установили с помощью NPM.
  2. Проверьте вводимые пользователем данные. Вы хотите убедиться, что пользователь не отправляет пустую форму. Все входы обязательны, и все должны иметь тип String. Электронная почта имеет специальную проверку, называемую .email() которая гарантирует, что введенное значение соответствует формату электронной почты, а пароль проверяется с помощью регулярного выражения. Для подтверждения пароля вы хотите, чтобы он был таким же, как введенный пароль. Эти проверки выполняются с использованием Joi .
  3. Настройте свой маршрутизатор. Запрос GET отображает страницу регистрации, а запрос POST запускается, когда пользователь нажимает кнопку, чтобы отправить форму.
  4. Маршрутизатор экспортируется как модуль.

Вот как выглядит код.

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
47
48
49
50
51
52
53
54
55
56
#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(/^[a-zA-Z0-9]{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 , и значения выглядят следующим образом.

1
2
3
4
5
value:
  { email: ‘[email protected]’,
    username: ‘izu’,
    password: ‘chinedu’,
    confirmationPassword: ‘chinedu’ },

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

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

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

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

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

1
result.value.password = hash

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

Запустите свой сервер из терминала, запустив:

1
node app.js

Укажите в браузере http: // localhost: 5000, и вы должны увидеть новое приложение.

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

Далее вы увидите, как реализовать функцию входа в систему для зарегистрированных пользователей. Я надеюсь, вам понравилось!