如何在nodejs中使用passportjs进行身份验证
#javascript #初学者 #node #authentication

nb:

  1. node.js的基本知识与本文一起遵循
  2. 从命令行shippet键入命令时,您应该在项目的根路径上

多年来,我从事的大多数代码库一直都喜欢使用JSON Web-Tokens(JWT)或身份验证-AS-AS-Service平台(Auth0Okta等)来获得身份验证逻辑。
这些确实是绝佳的选择!但是,在较小的项目上,我发现这些总是过分的。最近,我开始使用Twitter Oauth API进行社交登录的Chrome扩展,并决定使用Passport.js将设置身份验证的一些繁重的工作外包。在下面,我浏览了Passport.js的身份验证

开始的福利和分步指南

为什么

身份验证是创建软件的一个非常微妙的过程,并拥有适当的应用程序可以是一件繁琐的过程。为了不在 perfect 身份验证流的困境中陷入困境,可以使用数百万用户,我想坚持使用更简单的工具,这些工具可以有效地服务于我的最终用户,并且仅在规模上扩展需要出现(而不是以前的时刻)。对于像我正在处理的Chrome扩展名这样的前端重型应用程序,我发现使用Passport.js设置身份验证是我用例更有效的选择,因为我将很少使用后端。

什么是Passportjs

根据官方文档

护照是node.js的身份验证中间件。 Passport非常灵活且模块化,可以不显眼地放入任何基于Express的Web应用程序中。一组全面的策略支持使用用户名和密码,Facebook,Twitter等身份验证。

让我们分解一下,以便我们以更基本的层面理解它。

护照是node.js的身份验证中间件:中间件只是在请求/响应周期和node.js之间运行的函数,而node.js是一个运行时环境,使我们能够在浏览器外运行JavaScript 。

可以毫不客气地放入任何基于明确的应用程序中:这意味着我们可以在任何node.js应用程序中使用护照,以使我们的代码保持精益,清洁且易于维护。<<<<<<<<<<<<<< /p>

使用用户名和密码,Facebook,Twitter支持身份验证: Passport.js使您可以灵活地使用用户名/密码,Google,Twitter等对用户进行身份验证。它通过使用与NPM目录的单独软件包安装的strategies来执行此操作,并且除了500 strategies外,还可以使用Passport!

最重要的是,这个包装是81kB!对于上下文,是实施Google Oauth的官方库google-auth-library的大小为496kB。很容易理解为什么我在此项目上使用Passport.js

项目范围和设置

不要浪费太多时间,让我们跳入项目的范围。
今天,我们将制作一个简单的应用程序
允许用户登录,查看当前时间并注销

首先是第一件事,让我们设置服务器。
ps :您可能需要确保首先安装了nodejs

创建文件夹

mkdir timely # create folder for project
cd timely # navigate into folder
touch index.js # create main entry point into your server
npm init -y # initialize npm

安装依赖项

npm install passport express express-session passport-local ejs # install dependencies
npm install nodemon --save-dev #install peer dependencies

设置用户界面

我们将使用EJS作为模板引擎,因此我们可以在网站上动态显示用户内容。如果您不太确定什么是模板引擎,则可以在这个惊人的article上了解一个想法。

mkdir views # create views folder
cd views # navigate into views folder
touch authenticate.ejs && touch index.ejs # create out home and authentication page

现在,我们将添加public文件夹,该文件夹包含我们的静态资产

mkdir public # create public folder 
cd public # navigate into public folder 
touch main.js && touch styles.css

我包含了main.jsstyles.css的内容,您可以通过here

复制它们

找到index.ejs文件,并将其内容替换为以下内容

<!-- navigate into timely/views/index.ejs and insert the following -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/styles.css" />
    <script defer src="/main.js"></script>
    <title>Timely | Who needs a rolex anyway 🤷‍♂️</title>
  </head>
  <body>
    <div class="header">
      <h1>Timely 🕰</h1>
    </div>
      <div class="container">
        <h1>Welcome User</h1>
        <div class="content"></div>
      </div>
  </body>
</html>

创建服务器

// Navigate into timely/index.js
const express = require('express');
const path = require('node:path');
const app = express();


app.use(express.static(path.join(__dirname, 'public'))); // Require static assets from public folder
app.set('views', path.join(__dirname, 'views')); // Set 'views' directory for any views being rendered res.render()
app.engine('html', require('ejs').renderFile); // Set view engine as EJS
app.set('view engine', 'ejs');

app.get('/', (req, res) => res.render('index'));

app.listen(3000, () => console.log('server is running on port 3000'));

package.json中的设置脚本

{
  "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "node index.js",
   "start:dev": "nodemon index.js"
 }
}

运行脚本

npm run start:dev

在这一点

Home page of timely passportjs app

看起来很棒!但是我们只希望登录用户可以访问时间和日期。因此,让我们为未登录的用户创建一个身份验证页面。
在您的views文件夹中找到authentication.ejs文件,然后用以下内容替换其内容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/styles.css" />
    <title>Timely | Who needs a rolex anyway 🤷‍♂️</title>
  </head>
  <body>
    <div class="header">
      <h1>Timely 🕰</h1>
    </div>
    <form action="/log-in" method="POST" class="container">
      <h1>please log in</h1>
      <div>
        <label for="username">Username</label>
        <input name="username" placeholder="username" type="text" />
      </div>
      <div>
        <label for="password">Password</label>
        <input name="password" type="password" />
      </div>
      <button>Log In</button>
    </form>
  </body>
</html>

在我们编写更多代码之前,让我们了解我们安装的护照软件包以及它们如何合并

护照:这是主要护照模块,可帮助我们将用户记录的当前登录的信息附加到req.user对象

Passport-Local: Passport.js与策略软件包合作,以执行应用程序所需的身份验证流。我们将使用passport-local为此,因为我们将仅使用用户名和密码对用户进行身份验证

明确说明:这将使我们能够存储并管理服务器上登录的用户的会话

现在,我们对护照的工作原理有所了解,让我们创建护照中间件策略。这将是将用户信息添加到我们的req.user对象的逻辑。在您的根文件夹中创建以下目录

mkdir strategy && cd strategy # create and move into strategy folder
touch local.js # create strategy file 

nb:在现实世界项目上,最好在存储它们之前使用hash用户密码,以提高安全性,还可以使用实际数据库来存储用户信息。在这里,我将将用户信息存储在系统上的JSON文件中,以避免网络调用,并将文章的唯一重点保留在Passport.js。

上。

local.js内部用以下内容替换其内容

const path = require('node:path'); // path module to find absolute paths on the system 
const fs = require('node:fs'); // file system module to manipulate files
const passport = require('passport'); // the main star of the show
const LocalStrategy = require('passport-local'); // the co-protagonist in this sequel 

const dbpath = path.join(__dirname, 'db.json'); // path to our custom in-house json database

passport.serializeUser((user, done) => { 
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  if (!fs.existsSync(dbpath)) { // if db does not exist, create db
    fs.writeFileSync(dbpath, JSON.stringify({ users: [] }));
  }

  const db = JSON.parse(fs.readFileSync(dbpath, { encoding: 'utf-8' }));

  let user = db.users.find((item) => item.id === id);

  if (!user) {
    done(new Error('Failed to deserialize'));
  }

  done(null, user);
});

passport.use(
  new LocalStrategy(async (username, password, done) => {
    if (!fs.existsSync(dbpath)) { // if db.json does not exist yet, we create it
      fs.writeFileSync(dbpath, JSON.stringify({ users: [] }));
    }

    const db = JSON.parse(fs.readFileSync(dbpath, { encoding: 'utf-8' }));

    let user = db.users.find((item) => {
      return item.username === username && item.password === password;
    });

    if (!user) {
      user = {
        id: Math.floor(Math.random() * 1000), // generate random id between numbers 1 - 999
        username,
        password,
      };

      db.users.push(user);
      fs.writeFileSync(dbpath, JSON.stringify(db));
    }

    done(null, user);
  })
);

每次向我们的服务器提出请求时,将运行此文件。让我们分解每种方法完全做什么

Passport.USE :这是第一个联系点,负责创建我们的用户。它是一次又一次地成功地验证我们的用户后运行的,它将此信息传递给下一个中间件,即passport.serializeUser

Passport.Serializeuser :这是在Passport中创建的,我们的密钥是user.id。对于我们的sessionIDs来说,避免冲突也很重要

也很重要。

passport.deserializeuser :在每个请求上,此方法接收我们的sessionID并扫描我们的数据库以获取当前登录的用户的用户信息,然后将该信息附加到req.user对象

现在,我们知道护照的工作原理,让它最终将其包括在项目中,这将使我们能够识别登录用户并相应地重新调整他们的请求

const express = require('express');
const passport = require('passport');
const session = require('express-session');
const path = require('node:path');
const app = express();
require('./strategy/local'); // passport strategy will listen to every request

app.use(
  session({
    secret: 'SOME SECRET', // secret key to sign our session
    resave: false,
    saveUninitialized: false,
    cookie: {
      maxAge: 1000 * 60 * 60 * 24, // TTL for the session
    },
  })
);
// initialize passport package
app.use(passport.initialize());
// initialize a session with passport that authenticates the sessions from express-session
app.use(passport.session());

app.use(express.urlencoded({ extended: false }));

app.use(express.static(path.join(__dirname, 'public'))); // Require static assets from public folder
app.set('views', path.join(__dirname, 'views')); // Set 'views' directory for any views being rendered res.render()
app.engine('html', require('ejs').renderFile); // Set view engine as EJS
app.set('view engine', 'ejs');

app.get('/', (req, res) => {
  if (!req.user) {
    // if passport does not have record of this user, we redirect them to the authentication page
    res.render('authenticate');
  } else {
    res.render('index', { user: req.user });
  }
});

app.post(
  '/log-in',
  passport.authenticate('local', {
    // on Initial login, passport will redirect to either of these routes
    successRedirect: '/',
    failureRedirect: '/',
  })
);

app.listen(3000, () => console.log('server is running on port 3000'));

如果我们再次在浏览器上访问http://localhost:3000/,我们将立即路由到身份验证页面。

Authentication page using passportjs

注册后,我们将分配一个会话cookie,您可以在我们的浏览器devtools控制台中查看该会话(右键单击页面 - > Inspect-> Application-> Application-> Cookie-> http://localhost:3000)。
每次将请求发送到服务器时,此cookie都会与该饼干一起发送,并且从此cookie中发送,Passport可以正确识别我们是否已登录。
为了进一步证明这一点,您可以继续从DevTools控制台中删除Cookie,然后刷新页面 - 瞬间,您立即将其重新列入身份验证页面。

我们的主页看起来不错,但让我们通过使用ejs显示用户用户名以及用户注销的能力来增加一些个性。

index.ejs中进行以下更改

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/styles.css" />
    <script defer src="/main.js"></script>
    <title>Timely | Who needs a rolex anyway 🤷‍♂️</title>
  </head>
  <body>
    <div class="header">
      <h1>Timely 🕰</h1>
    </div>
    <form class="container" action="/log-out" method="post">
      <h1>Welcome back, <%= user.username %></h1>
      <div class="content"></div>
      <button>Log out</button>
    </form>
  </body>
</html>

现在要处理服务器上的注销路由,我们可以在app.listen()方法上方添加以下代码

app.post('/log-out', (req, res) => {
  req.logOut((err) => { // clear out information in passport-session and redirects user
    if (err) {
      res.send('something went wrong');
    }

    res.redirect('/');
  });
});

app.use('*', (req, res) => res.redirect('/')); // to catch and redirect all other routes 

Completed home page of timely passportjs app

nb:您可能已经注意到,每次更改服务器文件时,都会重新列入身份验证页面。这是因为sessionIDs存储在由express-session创建的缓存中,一旦nodemon重新启动了服务器,express-session缓存被擦拭干净,因此,护照无法使用以前的sessionID识别我们。

就是这样!现在,我们可以使用Passport.js充分登录和退出应用程序。
我确实建议阅读更多有关他们的documentations的信息,以发现您可以在未来项目中使用的新策略。直到下次,Ciao!