如何在使用 Passport(react、react-router、express、passport)进行社交身份验证后重定向到正确的客户端路由 [英] How to redirect to correct client route after social auth with Passport (react, react-router, express, passport)

查看:38
本文介绍了如何在使用 Passport(react、react-router、express、passport)进行社交身份验证后重定向到正确的客户端路由的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a React/Redux/React Router front end, Node/Express back end. I’m using Passport (various strategies including Facebook, Google and Github) for authentication.

我想要发生的事情:

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

  1. Unauthenticated user attempts to access protected client route (something like /posts/:postid, and is redirected to /login. (React Router is handling this part)

用户点击使用 Facebook 登录"按钮(或其他社交认证服务)

User clicks the ‘Log in with Facebook’ button (or other Social auth service)

发生了什么:

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

The only way I’ve found to successfully handle Passport social authentication with a React front end is to wrap the ‘Log in with Facebook’ button in an <a> tag:

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

如果我尝试将其作为 API 调用而不是链接来执行,我总是收到一条错误消息(这里有更详细的解释:使用 Passport + Facebook + Express + create-react-app + React-Router + 代理进行身份验证)

If I try to do it as an API call instead of a link I always get an error message (this issue is explained in a lot more detail here: Authentication with Passport + Facebook + Express + create-react-app + React-Router + proxy)

于是用户点击链接,命中Express API,成功通过Passport认证,然后Passport重定向到回调路由(http://localhost:8080/auth/facebook/callback).

So the user clicks the link, which hits the Express API, successfully authenticates with Passport, and then Passport redirects to the callback route (http://localhost:8080/auth/facebook/callback).

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

In the callback function I need to (1) return the user object and token to the client, and (2) redirect to a client route — either the protected route they were trying to access before they got redirected to /login, or some default route like / or /dashboard.

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

But since there isn’t a way to do both of these things in Express (I can’t res.send AND res.redirect, I have to choose one), I’ve been handling it in what feels like kind of a clunky way: res.redirect(`${CLIENT_URL}/user/${userId}`)

这会在客户端加载 /user 路由,然后我从路由参数中提取 userId,将其保存到 Redux,然后向服务器发出另一个调用以返回令牌将令牌保存到 localStorage.

This loads the /user route on the client, and then I’m pulling the userId out of the route params, saving it to Redux, then making ANOTHER call to the server to return the token to save token to localStorage.

这一切正常,虽然感觉很笨重,但我不知道如何在提示登录之前重定向到用户尝试访问的受保护路由.

This is all working, although it feels clunky, but I can’t figure out how to redirect to the protected route the user was trying to access before being prompted to log in.

当用户尝试访问它时,我首先尝试将尝试的路由保存到 Redux,我想我可以使用它在身份验证后登陆个人资料页面后进行重定向.但由于 Passport 身份验证流程将用户带到异地进行 3d 方身份验证,然后在 res.redirect 上重新加载 SPA,因此存储被破坏并且重定向路径丢失.

I first tried saving the attempted route to Redux when the user tries to access it, thinking I could use that to redirect once they land on the profile page after authentication. But since the Passport auth flow takes the user off-site for 3d-party authentication and then reloads the SPA on res.redirect, the store is destroyed and the redirect path is lost.

我最终解决的是将尝试的路由保存到 localStorage,当 /user 组件安装在前端,使用 this.props.history.push(redirectUrl) 重定向,然后从 localStorage 中清除 redirectUrl 键.这似乎是一个非常肮脏的解决方法,必须有更好的方法来做到这一点.有没有其他人想出如何使这项工作?

What I ended up settling on is saving the attempted route to localStorage, checking to see if there is a redirectUrl key in localStorage when the /user component mounts on the front end, redirecting with this.props.history.push(redirectUrl) and then clearing the redirectUrl key from localStorage. This seems like a really dirty workaround and there has got to be a better way to do this. Has anybody else figuree out how to make this work?

推荐答案

万一其他人为此而苦苦挣扎,这就是我最终采用的方法:

In case anybody else is struggling with this, this is what I ended up going with:

1.当用户尝试访问受保护的路由时,使用 React-Router 重定向到 /login.

首先定义一个组件:

// 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 属性传递给路由:

Then pass the loggedIn property to the route:

// App.jsx

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

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

2. In /login component, save previous route to localStorage so I can later redirect back there after authentication:

// Login.jsx

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

3.在 SocialAuth 回调中,重定向到客户端的个人资料页面,添加用户 ID 和令牌作为路由参数

// 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.如果存在,重定向然后清除值

4. In the Profile component on the client, pull the userId and token out of the route params, immediately remove them using window.location.replaceState, and save them to localStorage. Then check for a redirectUrl in localStorage. If it exists, redirect and then clear the value

// 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 的开发中使用它,因为一个值设置为 localStorage在客户端 URL 的本地存储中将不存在在服务器 URL 处呈现的页面

This blog post was helpful in figuring things out. The #4 (recommended) solution in the linked post is much simpler and would probably work fine in production, but I couldn't get it to work in development where the server and client have different base URLs, because a value set to localStorage by a page rendered at the server URL will not exist in local Storage for the client URL

这篇关于如何在使用 Passport(react、react-router、express、passport)进行社交身份验证后重定向到正确的客户端路由的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆