在带有 React.js 前端的 Node.js 上使用 Passport.js 进行身份验证

Posted

技术标签:

【中文标题】在带有 React.js 前端的 Node.js 上使用 Passport.js 进行身份验证【英文标题】:Authentication using Passport.js on Node.js with React.js frontend 【发布时间】:2018-03-08 17:42:55 【问题描述】:

在身份验证方面,我完全是新手。我正在尝试将 Colt Steele 使用的方法作为他的web developer's bootcamp course 的一部分来适应我的项目。

Colt 使用 Node.js + Express.js + MongoDB,在 ejs 中进行服务器端渲染。对于用户身份验证,他使用 Passport.js。

我正在使用 Node.js + Express.js + MongoDB 和 React.js 进行客户端渲染。我也使用反应路由器。我也在尝试使用 Passport 对用户进行身份验证。

我的问题是,虽然我可以成功登录,但我仍然无法访问登录墙后面的任何内容。当我向服务器发出请求以检查用户是否经过身份验证的某个路由时,答案总是错误的。

这是我的验证码。

const express = require("express"),
  server = express(),
  path = require("path"),
  mongoose = require("mongoose"),
  User = require("./models/User"),
  bodyParser = require("body-parser"),
  passport = require("passport"),
  localStrategy = require("passport-local"),
  passportLocalMongoose = require("passport-local-mongoose"),
  expressSession = require("express-session");

mongoose.connect("mongodb://localhost/diary");

server.use(
  bodyParser.urlencoded(
    extended: true
  )
);

//Auth
server.use(
  expressSession(
    secret: "This is pretty damn weird.",
    resave: false,
    saveUninitialized: false
  )
);

server.use(passport.initialize());
server.use(passport.session());

passport.use(new localStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());    

// Auth login
server.post("/login", function(request, response) 
  passport.authenticate("local", function(err, user, info) 
    if (err) 
      return console.log(err);
    
    if (!user) 
      return response.send(false);
    
    request.logIn(user, function(err) 
      if (err) 
        return console.log(err);
      
      checkLogIn(request, response);
      return response.send(true);
    );
  )(request, response);
);

//Auth checkLogIn
function checkLogIn(request, response) 
  if (request.isAuthenticated()) 
    return console.log("authenticated");
   else 
    return console.log("not authenticated");
  

由于我使用的是客户端渲染,我无法使用 response.redirect 将用户重定向到任何地方。这就是我使用 response.send(true)(或 false)的原因:我使用它们作为标志来告诉 React 应用程序该做什么。

上面的代码与 Colt 的代码几乎相同:他将 expressSession 的配置保持在最低限度,并且似乎没有定义自己的本地策略或(反)序列化函数。在我发现的几个教程中,每个人似乎都这样做,但 Colt 没有,而且他的应用程序运行得很好。他也不使用任何 MongoStore,很多其他人似乎都这样做。

如您所见,我在 request.logIn 之后调用了一个 checkLogIn 函数,它只是 console.logs 用户是否登录。登录后,它在屏幕上打印 true,即登录确实成功.

此时我应该已经登录,但是如果我尝试从浏览器访问受保护的部分,我会被告知我没有登录。例如,我以假用户 Elmo 成功登录,然后尝试访问/elmo/diary。这会将我带到一个 React 组件,该组件在特定路由上查询服务器,这个:

//Find all entries of a given user.
router.get("/entries/:userURL", isLoggedIn, (request, response) => 
  console.log("get entries");
  User.findOne(
     customURL: request.params.userURL ,
     entryIDs: 1 ,
    (err, data) => 
      //Get the actual entries.
      Entry.find( _id:  $in: data.entryIDs  , (err, entries) => 
        if (err) 
          console.log(err);
         else 
          response.send(entries);
        
      );
    
  );
);

如您所见,我使用了一个 isLoggedIn 中间件,如下所示:

//Auth isLoggedIn
function isLoggedIn(request, response, next) 
  console.log(request);
  if (request.isAuthenticated()) 
    console.log("logged in");
    return next();
  
  console.log("not logged in");
  return (function() 
    response.send(false);
  )();

所以如果我以 Elmo 身份登录,上面的路由应该会返回数据。但是,无论我是否登录,中间件都会返回 false。

我检查了我的 localhost 的 cookie,应用程序在其中运行,只有一个具有令牌外观的值 (s%3Ak0SZJ97eJkPjqjE6jC-azn7y3ztK2UMw.fYOfM9324JUIeUDGvh26swEWwMf3j3HDUmh9WubNF%2B8),所以我猜 Passport 正在创建会话并存储它在cookie中成功。

说实话,我也不确定这个系统对 Colt 是如何工作的,因为他的前端似乎没有发回任何令牌/数据供后端检查,而我(诚然有限)的理解是不是应该是这样的。

知道我做错了什么吗?

【问题讨论】:

客户端路由,你用的是react-router还是express? @Dream_Cap 反应路由器。 【参考方案1】:

当您说“登录墙”时,我假设您想要访问受保护的路线/组件。我相信您需要像这样的带有 react-router 的受保护路由:

 //Placed near your other react router routes, pass the logged in state to this route
  <PrivateRoute path="/dashboard" component=Dashboard loggedIn=loggedIn />

然后私有路由将如下所示:

 //If auth'd, redirect to the component you want, if not then redirect to login
const PrivateRoute = ( component: Component, ...rest, loggedIn ) => (
  <Route
    ...rest
    render=props => (
      loggedIn ? (
        <Component ...props />
      ) : (
        <Redirect to=
          pathname: '/login',
          state:  from: props.location ,
        
        />
      )
    )
  />
);

至于让你的路由知道你是否已登录,我认为你需要将 express 的身份验证响应存储在本地 React 组件状态或 Redux 中(我最近在我的博客项目 here 中这样做了。它将位于您的 redux 存储(状态)中,因为这样您就可以在需要时从多个视图/组件中访问它。例如,可以在多个视图中检索登录状态。

以下是路线的示例:

const AllRoutes = ( loggedIn ) => (
  <BrowserRouter history=history>
    <div>
      <PrivateRoute path="/dashboard" component=Dashboard loggedIn=loggedIn />
       //other routes
    </div>
  </BrowserRouter>
);

这是 Redux 部分将loggedIn 传递给allRoutes 的样子:

   //import connect for connecting the component into redux's store(state)
import  connect  from 'react-redux';
//map redux's store to props so the component can access it (this is how loggedIn can get passed in)
const mapStateToProps = state => (
  loggedIn: state.signup.loggedIn,
);
 //wraps the component in Redux's store(state)
export default connect(mapStateToProps, null)(AllRoutes);

【讨论】:

登录时loggedIn的值如何传递给AllRoutes? 我更新了答案以包含更多信息 这个答案有帮助吗? 我仍在努力解决。我现在正在学习 Udemy 课程,说根本不需要涉及 react,因为浏览器会自动将身份验证 cookie 附加到每个 http 请求。事实上,我真的不需要受保护的路由,而只需要身份验证来决定是否可以向用户显示某些数据。对不起,如果我的措辞令人困惑。无论如何,感谢您的帮助!

以上是关于在带有 React.js 前端的 Node.js 上使用 Passport.js 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何为 api 部署 node js express js,为前端部署 react js,为管理员部署 angular

基于 React.js 和 Node.js 的SSR实现方案

谁说外包公司没有高手,看我们的大前端技术栈 React/js Native, Vue, Node.js

React-Router vs Node.js

Node.js 之react.js组件-JSX简介

三剑合璧:Node.js+React.js+vue.js开发3大最前沿项目!升职加薪统统收入麾下!| 饥人谷