node.js - 如何在使用 Passport(react、react-router、express、passport)进行社交身份验证后重定向到正确的客户端路由

标签 node.js reactjs express react-router passport.js

我有一个 React/Redux/React Router 前端,Node/Express 后端。我正在使用 Passport(各种策略,包括 Facebook、Google 和 Github)进行身份验证。

我想要发生的事情:

  1. 未经身份验证的用户尝试访问 protected 客户端路由 (类似于 /posts/:postid ,并被重定向到 /login 。 (React Router 正在处理这部分)

  2. 用户点击“使用 Facebook 登录”按钮(或其他社交身份验证服务)

  3. 身份验证后,用户会自动重定向回他们在第 1 步中尝试访问的路由。

发生了什么:

我发现使用 React 前端成功处理 Passport 社交身份验证的唯一方法是将“使用 Facebook 登录”按钮包装在 <a> 中。标签:

<a href="http://localhost:8080/auth/facebook">Facebook Login</a>

如果我尝试将其作为 API 调用而不是链接来执行,我总是会收到一条错误消息(此问题在此处有更详细的解释:Authentication with Passport + Facebook + Express + create-react-app + React-Router + proxy)

所以用户点击链接,点击 Express API,成功使用 Passport 进行身份验证,然后 Passport 重定向到回调路由(http://localhost:8080/auth/facebook/callback)。

在回调函数中,我需要 (1) 将用户对象和 token 返回给客户端,以及 (2) 重定向到客户端路由——他们在重定向到 /login 之前尝试访问的 protected 路由。 ,或一些默认路由,如 //dashboard .

但是由于在 Express 中没有办法同时完成这两件事(我不能 res.sendres.redirect ,我必须选择一个),我一直在以一种感觉不错的方式处理它笨拙的方式: res.redirect(`${CLIENT_URL}/user/${userId}`)

这会加载 /user在客户端上路由,然后我从路由参数中提取 userId,将其保存到 Redux,然后再次调用服务器以返回 token 以将 token 保存到 localStorage。

一切正常,虽然感觉很笨拙,但我无法弄清楚如何重定向到用户在提示登录之前尝试访问的 protected 路由。

当用户尝试访问它时,我首先尝试将尝试的路由保存到 Redux,我认为一旦他们在身份验证后登陆配置文件页面,我就可以使用它来重定向。但是由于 Passport 身份验证流程将用户带到异地进行 3d 方身份验证,然后在 res.redirect 上重新加载 SPA ,商店被破坏,重定向路径丢失。

我最终决定将尝试的路由保存到 localStorage,检查是否有 redirectUrl/user 时输入 localStorage组件安装在前端,使用 this.props.history.push(redirectUrl) 重定向然后清除 redirectUrl来自 localStorage 的 key 。这似乎是一个非常肮脏的解决方法,必须有更好的方法来做到这一点。还有其他人知道如何使这项工作成功吗?

最佳答案

万一其他人正在为此苦苦挣扎,这就是我最终要做的:

<强>1。当用户尝试访问 protected 路由时,重定向到 /login使用 React-Router。

首先定义一个<PrivateRoute>组件:

// App.jsx

const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props =>
        loggedIn === true ? (
          <Component {...rest} {...props} />
        ) : (
          <Redirect
            to={{ pathname: "/login", state: { from: props.location } }}
          />
        )
      }
    />
  );
};

然后传递loggedIn路由的属性:

// App.jsx

<PrivateRoute
  loggedIn={this.props.appState.loggedIn}
  path="/poll/:id"
  component={ViewPoll}
/>

<强>2。在 /login组件,将之前的路由保存到 localStorage,以便我稍后可以在身份验证后重定向回那里:

// Login.jsx

  componentDidMount() {
   const { from } = this.props.location.state || { from: { pathname: "/" } };
   const pathname = from.pathname;
   window.localStorage.setItem("redirectUrl", pathname);
}

<强>3。在 SocialAuth 回调中,重定向到客户端的个人资料页面,添加 userId 和 token 作为路由参数

// auth.ctrl.js

exports.socialAuthCallback = (req, res) => {
  if (req.user.err) {
    res.status(401).json({
        success: false,
        message: `social auth failed: ${req.user.err}`,
        error: req.user.err
    })
  } else {
    if (req.user) {
      const user = req.user._doc;
      const userInfo = helpers.setUserInfo(user);
      const token = helpers.generateToken(userInfo);
      return res.redirect(`${CLIENT_URL}/user/${userObj._doc._id}/${token}`);
    } else {
      return res.redirect('/login');
    }
  }
};

<强>4。在Profile客户端上的组件,拉取 userId 和 token 在路由参数之外,立即使用删除它们 window.location.replaceState ,并将它们保存到 localStorage。然后检查 localStorage 中的 redirectUrl。如果存在,则重定向,然后清除该值

// Profile.jsx

  componentWillMount() {
    let userId, token, authCallback;
    if (this.props.match.params.id) {
      userId = this.props.match.params.id;
      token = this.props.match.params.token;
      authCallback = true;

      // if logged in for first time through social auth,
      // need to save userId & token to local storage
      window.localStorage.setItem("userId", JSON.stringify(userId));
      window.localStorage.setItem("authToken", JSON.stringify(token));
      this.props.actions.setLoggedIn();
      this.props.actions.setSpinner("hide");

      // remove id & token from route params after saving to local storage
      window.history.replaceState(null, null, `${window.location.origin}/user`);
    } else {
      console.log("user id not in route params");

      // if userId is not in route params
      // look in redux store or local storage
      userId =
        this.props.profile.user._id ||
        JSON.parse(window.localStorage.getItem("userId"));
      if (window.localStorage.getItem("authToken")) {
        token = window.localStorage.getItem("authToken");
      } else {
        token = this.props.appState.authToken;
      }
    }

    // retrieve user profile & save to app state
    this.props.api.getProfile(token, userId).then(result => {
      if (result.type === "GET_PROFILE_SUCCESS") {
        this.props.actions.setLoggedIn();
        if (authCallback) {
          // if landing on profile page after social auth callback,
          // check for redirect url in local storage
          const redirect = window.localStorage.getItem("redirectUrl");
          if (redirect) {
            // redirect to originally requested page and then clear value
            // from local storage
            this.props.history.push(redirect);
            window.localStorage.setItem("redirectUrl", null);
          }
        }
      }
    });
  }

This blog post有助于解决问题。链接帖子中的#4(推荐)解决方案要简单得多,并且可能在生产中运行良好,但我无法在服务器和客户端具有不同基本 URL 的开发中使用它,因为值设置为 localStorage在服务器 URL 呈现的页面将不存在于客户端 URL 的本地存储中

关于node.js - 如何在使用 Passport(react、react-router、express、passport)进行社交身份验证后重定向到正确的客户端路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49788580/

相关文章:

javascript - 如何更新 React 组件的数组?

javascript - 尝试导入错误: 'PrivateRoute' is not exported from 'react-router-dom'

regex - 从javascript中的字符串中提取MAC地址

node.js - Node - 错误 : error:0906D06C:PEM routines:PEM_read_bio:no start line

javascript - 解决 Node 中的 "localhost unexpectedly closed connection"错误

reactjs - "Impure"组件示例

javascript - 有没有办法检查用户是否使用express.Router中间件登录?

node.js - 无法读取未定义 Mongoose 的属性 'length'

javascript - 将对象传递给护照的 require() 时出现类型错误

node.js - 更改 PouchDB 存储目录