احرازهویت در نودجی‌اس با استفاده از Passport.js

گردآوری و تالیف : ارسطو عباسی
تاریخ انتشار : 28 مرداد 1397
دسته بندی ها : نود جی اس

در این مقاله می‌خواهیم به شما شیوه احرازهویت سرور نودجی‌اس را با استفاده از Passport.js را آموزش دهیم. در این مطلب احرازهویت فرانت‌اند را پوشش نمی‌دهیم بلکه قصد داریم به موضوع احراز‌هویت در بک‌اند بپردازیم (ایجاد توکن برای هر کاربر و محافظت از مسیرها).

در نظر داشته باشید که اگر در هر مرحله از این مطلب دچار مشکل شدید می‌توانید به این صفحه از گیت‌هاب مراجعه کنید.

شما همچنین میتوانید با دیدن دوره ساخت یک وبسایت آموزشی (فروشگاهی) با Nodejs بصورت کاملا عملی این موارد را پیاده سازی کنید

مباحثی که در این مطلب گفته می‌شود:

  • مدیریت مسیرهای محافظت شده
  • مدیریت توکن‌های JWT
  • مدیریت پاسخ‌های غیرمجاز
  • ایجاد API ساده
  • ایجاد ماژول‌ها و طرح‌ها

مقدمه

Passport.js چیست؟

Passport یک میان افزار برای نودجی‌اس است. از آنجایی که این ابزار بسیار منعطف و ماژولار است می‌تواند در اپلیکیشن‌های مبتنی بر وب Express قرار بگیرد. Passport می‌تواند احرازهویت را از طریق نام کاربری و رمز عبور، فیسبوک، توییتر و... برای شما انجام دهد. در این رابطه می‌توانید در این لینک مطالعه بیشتری داشته باشید. 

آموزش

ایجاد سرور نودجی‌اس

یک دایرکتوری جدید ایجاد کنید و یک فایل app.js را در آن بسازید. حال محتویات زیر را در آن قرار دهید:

const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const session = require('express-session');
const cors = require('cors');
const mongoose = require('mongoose');
const errorHandler = require('errorhandler');

//Configure mongoose's promise to global promise
mongoose.promise = global.Promise;

//Configure isProduction variable
const isProduction = process.env.NODE_ENV === 'production';

//Initiate our app
const app = express();

//Configure our app
app.use(cors());
app.use(require('morgan')('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({ secret: 'passport-tutorial', cookie: { maxAge: 60000 }, resave: false, saveUninitialized: false }));

if(!isProduction) {
  app.use(errorHandler());
}

//Configure Mongoose
mongoose.connect('mongodb://localhost/passport-tutorial');
mongoose.set('debug', true);

//Error handlers & middlewares
if(!isProduction) {
  app.use((err, req, res) => {
    res.status(err.status || 500);

    res.json({
      errors: {
        message: err.message,
        error: err,
      },
    });
  });
}

app.use((err, req, res) => {
  res.status(err.status || 500);

  res.json({
    errors: {
      message: err.message,
      error: {},
    },
  });
});

app.listen(8000, () => console.log('Server running on http://localhost:8000/'));

برای روند توسعه ساده‌تر nodemon را نصب می‌کنیم:

npm install -g nodemon

بعد از آن app.js را با استفاده از nodemon اجرا می‌کنیم:

nodemon app.js

ایجاد ماژول کاربری

یک دایرکتوری جدید با نام models ایجاد کرده و در آن یک فایل جاوااسکریپتی با نام Users.js را قرار دهید. در این فایل قصد داریم که طرح کلی کاربری را -UsersSchema- ایجاد کنیم. برای ایجاد hash و salt از رشته پسورد دریافت شده قصد داریم که از JWT و Crypto استفاده کنیم. از این موارد بعدا برای احرازهویت کاربران استفاده می‌شود:

const mongoose = require('mongoose');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');

const { Schema } = mongoose;

const UsersSchema = new Schema({
  email: String,
  hash: String,
  salt: String,
});

UsersSchema.methods.setPassword = function(password) {
  this.salt = crypto.randomBytes(16).toString('hex');
  this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
};

UsersSchema.methods.validatePassword = function(password) {
  const hash = crypto.pbkdf2Sync(password, this.salt, 10000, 512, 'sha512').toString('hex');
  return this.hash === hash;
};

UsersSchema.methods.generateJWT = function() {
  const today = new Date();
  const expirationDate = new Date(today);
  expirationDate.setDate(today.getDate() + 60);

  return jwt.sign({
    email: this.email,
    id: this._id,
    exp: parseInt(expirationDate.getTime() / 1000, 10),
  }, 'secret');
}

UsersSchema.methods.toAuthJSON = function() {
  return {
    _id: this._id,
    email: this.email,
    token: this.generateJWT(),
  };
};

mongoose.model('Users', UsersSchema);

حال بیایید ماژول جدید را به app.js اضافه کنیم. بعد از پیکربندی Mongoose ماژول را اضافه کنید:

require('./models/Users');

پیکربندی Passport

یک دایرکتوری جدید با نام config ایجاد کرده و یک فایل جاوااسکریپتی با نام passport.js را در آن قرار دهید. حال محتویات زیر را به آن اضافه نمایید:

const mongoose = require('mongoose');
const passport = require('passport');
const LocalStrategy = require('passport-local');

const Users = mongoose.model('Users');

passport.use(new LocalStrategy({
  usernameField: 'user[email]',
  passwordField: 'user[password]',
}, (email, password, done) => {
  Users.findOne({ email })
    .then((user) => {
      if(!user || !user.validatePassword(password)) {
        return done(null, false, { errors: { 'email or password': 'is invalid' } });
      }

      return done(null, user);
    }).catch(done);
}));

در این فایل، ما از متد validatePassword که در ماژول user ایجاد کردیم استفاده می‌کنیم. براساس نتایج، ما خروجی متفاوتی را از LocalStrategy مربوط به Passport دریافت می‌کنیم.

حال بیایید passport.js را به فایل app.js متصل کنیم. ماژول را به صورت زیر به فایل app.js اضافه نمایید:

require('./config/passport');

مسیرها و گزینه‌های احرازهویت

یک دایرکتوری جدید ایجاد کرده و در داخل آن فایل auth.js را ایجاد کنید.

در این فایل از تابع getTokenFromHeaders برای دریافت توکن JWT استفاده می‌کنیم. این توکن از طرف کلاینت به عنوان یک درخواست ارسال می‌شود. همچنین شئ auth با خصوصیات optional و required را ایجاد می‌کنی. بعدا از این مورد در قسمت مسیرها استفاده می‌کنیم.

const jwt = require('express-jwt');

const getTokenFromHeaders = (req) => {
  const { headers: { authorization } } = req;

  if(authorization && authorization.split(' ')[0] === 'Token') {
    return authorization.split(' ')[1];
  }
  return null;
};

const auth = {
  required: jwt({
    secret: 'secret',
    userProperty: 'payload',
    getToken: getTokenFromHeaders,
  }),
  optional: jwt({
    secret: 'secret',
    userProperty: 'payload',
    getToken: getTokenFromHeaders,
    credentialsRequired: false,
  }),
};

module.exports = auth;

در همان دایرکتوری مسیرها فایل index.js را ایجاد کنید:

const express = require('express');

const router = express.Router();

router.use('/api', require('./api'));

module.exports = router;

حال ما به یک دایرکتوری api در داخل دایرکتوری routes نیازمندیم که در داخل آن نیز یک فایل index.js قرار دارد:

const express = require('express');

const router = express.Router();

router.use('/users', require('./users'));

module.exports = router;

حال، بیایید فایل users.js که به آن در api/index.js نیاز داریم را ایجاد کنیم. ابتدا، قصد داریم یک optional auth به صورت ‘/’ را ایجاد کنیم. از این حالت برای ایجاد ماژول جدید استفاده می‌کنیم.

router.post('/', auth.optional, (req, res, next) ...

بعد از آن باید یک مورد دیگر را به صورت ‘/login’ ایجاد کنیم. از این مورد برای فعال‌سازی پیکربندی Passport.js و اعتبارسنجی ایمیل و پسورد دریافت شده استفاده می‌شود. 

router.post('/login', auth.optional, (req, res, next) ...

در پایان یک required auth را نیز استفاده می‌کنیم که برای برگشت دادن کاربر وارد شده به سیستم استفاده می‌شود. تنها کاربرانی که وارد سیستم شده‌اند به این مورد دسترسی خواهند داشت:

router.get('/current', auth.required, (req, res, next) ...

در نهایت فایل users.js به صورت زیر خواهد بود:

const mongoose = require('mongoose');
const passport = require('passport');
const router = require('express').Router();
const auth = require('../auth');
const Users = mongoose.model('Users');

//POST new user route (optional, everyone has access)
router.post('/', auth.optional, (req, res, next) => {
  const { body: { user } } = req;

  if(!user.email) {
    return res.status(422).json({
      errors: {
        email: 'is required',
      },
    });
  }

  if(!user.password) {
    return res.status(422).json({
      errors: {
        password: 'is required',
      },
    });
  }

  const finalUser = new Users(user);

  finalUser.setPassword(user.password);

  return finalUser.save()
    .then(() => res.json({ user: finalUser.toAuthJSON() }));
});

//POST login route (optional, everyone has access)
router.post('/login', auth.optional, (req, res, next) => {
  const { body: { user } } = req;

  if(!user.email) {
    return res.status(422).json({
      errors: {
        email: 'is required',
      },
    });
  }

  if(!user.password) {
    return res.status(422).json({
      errors: {
        password: 'is required',
      },
    });
  }

  return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
    if(err) {
      return next(err);
    }

    if(passportUser) {
      const user = passportUser;
      user.token = passportUser.generateJWT();

      return res.json({ user: user.toAuthJSON() });
    }

    return status(400).info;
  })(req, res, next);
});

//GET current route (required, only authenticated users have access)
router.get('/current', auth.required, (req, res, next) => {
  const { payload: { id } } = req;

  return Users.findById(id)
    .then((user) => {
      if(!user) {
        return res.sendStatus(400);
      }

      return res.json({ user: user.toAuthJSON() });
    });
});

module.exports = router;

بیایید دایرکتوری routes را به app.js اضافه نماییم. برای اینکار به صورت زیر عمل کنید:

app.use(require('./routes'));

تست مسیر‌ها

برای ارسال درخواست به سرور من از Postman استفاده می‌کنم. سرور ما قالب جی‌سان زیر را می‌پذیرد:

{
  "user": {
    "email": String,
    "password": String
  }
}

ایجاد یک درخواست Post برای ثبت کاربر جدید

قالب تست:

پاسخ:

{
    "user": {
        "_id": "5b0f38772c46910f16a058c5",
        "email": "erdeljac.antonio@gmail.com",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImVyZGVsamFjLmFudG9uaW9AZ21haWwuY29tIiwiaWQiOiI1YjBmMzg3NzJjNDY5MTBmMTZhMDU4YzUiLCJleHAiOjE1MzI5MDgxNTEsImlhdCI6MTUyNzcyNDE1MX0.4TWc1TzY6zToHx_O1Dl2I9Hf9krFTqPkNLHI5U9rn8c"
    }
}

حال ما از این توکن استفاده می‌کنیم و آن را به سربرگ مربوط به پیکربندی Postman اضافه می‌کنیم.

حال بیایید مسیر auth را تست کنیم

ایجاد یک درخواست Get برای دریافت کاربر وارد شده

 URL درخواستی:

GET http://localhost:8000/api/users/current

پاسخ:

{
    "user": {
        "_id": "5b0f38772c46910f16a058c5",
        "email": "erdeljac.antonio@gmail.com",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImVyZGVsamFjLmFudG9uaW9AZ21haWwuY29tIiwiaWQiOiI1YjBmMzg3NzJjNDY5MTBmMTZhMDU4YzUiLCJleHAiOjE1MzI5MDgzMTgsImlhdCI6MTUyNzcyNDMxOH0.5UnA2mpS-_puPwwxZEb4VxRGFHX6qJ_Fn3pytgGaJT0"
    }
}

بیایید اینکار را بدون توکن داخل سربرگ انجام دهیم.

پاسخ:

منبع

مقالات پیشنهادی

8 دليلي كه نبايد از يك مديريت محتوا استفاده كنيد

من تمايل دارم افرادي رو پيدا كنم كه در قدم اول ميخوان به مشتريان چنين ايده اي رو، ترويج بدن كه اون ها ميتونن سايت خودشون رو "به راحتي به كمك يه واژه پ...

بازاریابی محتوا در نمایشگاه ها

بازاریابی محتوا یکی از مهم‌ترین شاخه‌های بازاریابی است که در سالیان اخیر از سوی شرکت‌ها و کسب و کارها مورد استقبال گسترده قرار گرفته است. اگرچه اهمیت...

ساخت یک ربات تلگرام با استفاده از Laravel و Botman

امروز، چیز متفاوتی برای شما داریم، امروز رباتی برای دوست داران حیوانات خانگی برای شما خواهیم ساخت. بله، درست خواندید. یک ربات تلگرام برای دوستداران سگ...

مدیریت احراز هویت در Vue با استفاده از Vuex

به طور سنتی، افراد از حافظه‌های محلی (Local Storage) برای مدیریت نشانه‌های تولید شده از طریق احراز هویت سمت کاربر استفاده می‌کنند. روش دیگری برای مدیر...