小编典典

使用Passport进行社交身份验证后,如何重定向到正确的客户端路由(反应,反应路由器,快速,护照)

reactjs

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

我想发生的事情:

  1. 未经身份验证的用户尝试访问受保护的客户端路由(类似/posts/:postid,然后重定向到/login。(React Router正在处理此部分)

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

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

相反,发生了什么:

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

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

如果我尝试将其作为API调用而不是链接进行处理,则会始终收到错误消息(此问题在此处进行了详细说明:使用Passport + Facebook +Express + create-react-app + React-Router+代理

因此,用户单击链接,该链接将打入Express
API,并成功通过Passport进行身份验证,然后Passport重定向到回调路由(http://localhost:8080/auth/facebook/callback)。

在回调函数中,我需要(1)将用户对象和令牌返回给客户端,以及(2)重定向到客户端路由-
他们在重定向到之前尝试访问的受保护路由/login,或某些默认路由,例如//dashboard

但是由于Express中没有办法完成这两项操作(我不能res.sendAND
res.redirect,我必须选择其中一个),所以我一直以一种笨拙的方式来处理它: res.redirect(${CLIENT_URL}/user/${userId})

这会将/user路由加载到客户端上,然后将userId从路由参数中拉出,将其保存到Redux,然后对服务器进行ANOTHER调用以返回令牌以将令牌保存到localStorage。

尽管感觉很笨拙,但所有操作都可以正常进行,但是 在提示用户登录之前 ,我无法弄清楚 如何重定向到用户尝试访问的受保护路由。

我首先尝试在用户尝试访问Redux时将尝试的路由保存到Redux,以为一旦他们在身份验证后登陆到个人资料页面,便可以使用该路由进行重定向。但是由于Passport身份验证流程将用户带离现场进行3d方身份验证,然后将SPA重新加载到res.redirect,因此存储被销毁,并且重定向路径丢失了。

我最后要解决的是将尝试的路由保存到localStorage,检查组件安装在前端redirectUrl时在localStorage中是否存在密钥/user,使用重定向,this.props.history.push(redirectUrl)然后redirectUrl从localStorage
清除密钥。这似乎是一个很脏的解决方法,必须有一种更好的方法来执行此操作。还有其他人想出如何使这项工作吗?


阅读 372

收藏
2020-07-22

共1个答案

小编典典

万一其他人为此感到挣扎,这就是我最终要解决的问题:

1.当用户尝试访问受保护的路由时,/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.在/logincomponent中,将以前的路由保存到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和令牌从路由参数中拉出,立即使用删除它们
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);
          }
        }
      }
    });
  }

这篇博客文章有助于解决问题。链接文章中的#4(推荐)解决方案要简单得多,并且可能在生产中可以正常工作,但是我无法在服务器和客户端具有不同基本URL的开发环境中使用它,因为将value设置为localStorage
by在服务器URL呈现的页面在客户端URL的本地存储中将不存在

2020-07-22