Рубрики
Без рубрики

Методы аутентификации Nodejs (часть 2)

Методы аутентификации узла Часть2: аутентификация на основе токена

Автор оригинала: Abdul-Samii Ajala.

В первой части этой статьи мы рассмотрели аутентификацию на основе сеанса. На этот раз мы рассмотрим второй способ аутентификации, перечисленные в части 1.

II. Аутентификация на основе токена

В этом методе аутентификации мы будем использовать JWT (Web Tookens JSON) как средство аутентификации. JWT, как определено на Jwt.io является стандартным методом открытого отрасли для представления претензий (может быть утверждение о том, что вы являетесь пользователем, а также и admin) надежно между двумя сторонами. JWT может быть сгенерирован, проверен и декодирован.

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

Ниже приведен то, как аутентификация выполняется с токенами

  1. Регистры пользователей с электронной почтой/именем пользователя и пароль (пароль проходит hashed при регистрации)
  2. Пользователь регистрирует использование имени пользователя и пароля (мы зацикливаем базу данных для имени пользователя/электронной почты и используем BCRYPT, чтобы сравнить сохраненную PasswareHash с помощью прилагаемого пользователя пользователя).
  3. Если успешно, подписанный токен отправляется на клиентское приложение
  4. Клиент хранит токен в памяти, локальном хранилище и т. Д. и отправляет его вместе с каждым запросом к серверу через заголовок.
  5. Сервер проверяет токен и отвечать только с данными, если токен действителен.

Чтобы идти в ногу, получите начальную структуру кода от Эта статья Github Repo Запустите их в вашем терминале/CMD (нажмите Return/Enter после каждой строки)

Git Clone https://github.com/jalasem/nodeauthstuts.git CD Nodeauthtuts Git Checkout Part2.

Вот код App.js (хорошо прокомментировал, чтобы объяснить, как работает аутентификация на основе токена)

// define dependencies
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const shortid = require('shortid');
const jwt = require('jsonwebtoken'); //we're using 'express-session' as 'session' here
const bcrypt = require("bcrypt"); // 
const app = express();
const PORT = 3000; // you can change this if this port number is not available

//connect to database
mongoose.connect('mongodb://localhost:27017/auth_tuts', { //replace this with you
  useMongoClient: true}, (err, db) => {
    if (err) {
      console.log("Couldn't connect to database");
    } else {
      console.log(`Connected To Database`);
    }
  }
);

// define database schemas
const User = require('./models/user'); // we shall create this (model/user.js) soon 

// configure bodyParser
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
let token_secret = 'iy98hcbh489n38984y4h498'; // !!! don't put this into your code at production.  Try using saving it into environment variable or a config file.

/*
0. Unprotected route
=============
*/
app.get('/', (req, res) => {
  res.send('Welcome to the Home of our APP');
})

/*
1. User Sign up
=============
*/
// here we're expecting username, fullname, email and password in body of the request for signup. Note that we're using post http method
app.post('/signup', (req, res) => {
  let {username, fullname, email, password} = req.body; // this is called destructuring. We're extracting these variables and their values from 'req.body'
    
    let userData = {
    	username,
        password: bcrypt.hashSync(password, 5), // we are using bcrypt to hash our password before saving it to the database
        fullname,
        email
    };
    
    let newUser = new User(userData);
    newUser.save().then(error => {
    	if (!error) {
        	return res.status(201).json('signup successful')
        } else {
        	if (error.code ===  11000) { // this error gets thrown only if similar user record already exist.
            	return res.status(409).send('user already exist!')
            } else {
            	console.log(JSON.stringigy(error, null, 2)); // you might want to do this to examine and trace where the problem is emanating from
            	return res.status(500).send('error signing up user')
            }
        }
    })
})

/*
2. User Sign in
=============
We will be using username and password, but it can be improved or modified (e.g email and password or some other ways as you please)
*/
app.post('/login', (req, res) => {
  let {username, password} = req.body;
    User.findOne({username: username}, 'username email password', (err, userData) => {
    	if (!err) {
        	let passwordCheck = bcrypt.compareSync(password, userData.password);
        	if (passwordCheck) { // we are using bcrypt to check the password hash from db against the supplied password by user
                // 1. here our payload contains data we want client to hold for when next they send us any request
                const payload = {
                  email: userData.email,
                  username: userData.username,
                  id: userData._id
                }
                // 2. then we use jwt to sign our payload with our secret defined on line 23
                let token = jwt.sign(payload, token_secret);
                // 3. lastly we send the token and some other info we feel clients might need to them in form of response
                res.status(200).send({token, email: userData.email, username: userData.username})
            } else {
            	res.status(401).send('incorrect password');
            }
        } else {
        	res.status(401).send('invalid login credentials')
        }
    })
})

/*
3. Authorization
=============
A simple way of implementing authorization is creating a simple middleware (take note of next() ) for it. Any endpoint that comes after the authorization middleware won't pass if user doesn't have a valid token

Normally your server is expecting it as either an header 'x-access-token' or in the body of your request as 'token'
*/
app.use((req, res, next) => {
  // check for token in the header first, then if not provided, it checks whether it's supplied in the body of the request
  var token = req.headers['x-access-token'] || req.body.token
  if (token) {
    jwt.verify(token, token_secret, function (err, decoded) {
      if (!err) {
        req.decoded = decoded; // this add the decoded payload to the client req (request) object and make it available in the routes
        next();
      } else {
        res.status(403).send('Invalid token supplied');
      }
    })
  } else {
    res.status(403).send('Authorization failed! Please provide a valid token');
  }
})

app.get('/protected', (req, res) => {
  res.send(`You have access to this because you have supplied a valid token.
    	Your username is ${req.decoded.username}
        and email is ${req.decoded.email}.
    `)
})

/*
4. Logout
=============
*/
// Since JWT is stateless i.e server doesn't keep track of tokens/session, all you need to do to logout is just to delete the saved token at the client side. Since you can't make a request without a token, then you are logged out technically.

/*
4. Password reset
=================
We shall be using two endpoints to implement password reset functionality
*/
app.post('/forgot', (req, res) => {
  let {email} = req.body; // same as let email = req.body.email
  User.findOne({email: email}, (err, userData) => {
    if (!err) {
      userData.passResetKey = shortid.generate();
      userData.passKeyExpires = new Date().getTime() + 20 * 60 * 1000 // pass reset key only valid for 20 minutes
      userData.save().then(err => {
          if (!err) {
            // configuring smtp transport machanism for password reset email
            let transporter = nodemailer.createTransport({
              service: "gmail",
              port: 465,
              auth: {
                user: '', // your gmail address
                pass: '' // your gmail password
              }
            });
            let mailOptions = {
              subject: `NodeAuthTuts | Password reset`,
              to: email,
              from: `NodeAuthTuts `,
              html: `
                

Hi,

Here is your password reset key

${passResetKey}

Please ignore if you didn't try to reset your password on our platform

` }; try { transporter.sendMail(mailOptions, (error, response) => { if (error) { console.log("error:\n", error, "\n"); res.status(500).send("could not send reset code"); } else { console.log("email sent:\n", response); res.status(200).send("Reset Code sent"); } }); } catch (error) { console.log(error); res.status(500).send("could not sent reset code"); } } }) } else { res.status(400).send('email is incorrect'); } }) }); app.post('/resetpass', (req, res) => { let {resetKey, newPassword} = req.body User.find({passResetKey: resetKey}, (err, userData) => { if (!err) { let now = new Date().getTime(); let keyExpiration = userDate.passKeyExpires; if (keyExpiration > now) { userData.password = bcrypt.hashSync(newPassword, 5); userData.passResetKey = null; // remove passResetKey from user's records userData.keyExpiration = null; userData.save().then(err => { // save the new changes if (!err) { res.status(200).send('Password reset successful') } else { res.status(500).send('error resetting your password') } }) } else { res.status(400).send('Sorry, pass key has expired. Please initiate the request for a new one'); } } else { res.status(400).send('invalid pass key!'); } }) }) app.listen(PORT, () => { console.log(`app running port ${PORT}`) })

Плюсы аутентификации на основе токена

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

  2. Безопасность Эти методы относительно более безопасны, чем другие методы, поскольку это строго токен, токены становятся недействительными в том, что пользователи изменяют данные в нем данных, поскольку у них нет секрета токена (так что держите секрет токена!). Он также руководствуется против атак CSRF. Мы также могли бы расширить нашу безопасность, установив срок действия наших токенов.

  3. Возможность продать, арендовать аренду, предлагать API как услугу. С токеном мы можем продать доступ API к нашим приложениям через токен и данный набор пределов запросов, пользователи могут предоставить доступ к другому приложению для выполнения действий от их имени на нашей платформе через токен. E.g Вы строите заявку на напоминание/TODO. Но вы хотите дать разрешение приложения, чтобы напомнить вам обо всех ваших дни рождения друзей Facebook. Мы могли бы сделать это с токенами. Возможности безграничны.

Это всеобъемлющая аутентификация на основе токена, в следующей/окончательной части мы будем изучать аутентификацию по паролям.

Оставайтесь продуктивными и напишите свой код добра.