我有一个 React/Redux/React Router 前端,Node/Express 后端。我正在使用 Passport(各种策略,包括 Facebook、Google 和 Github)进行身份验证。
我想要发生的事情:
未经身份验证的用户尝试访问 protected 客户端路由 (类似于
/posts/:postid
,并被重定向到/login
。 (React Router 正在处理这部分)用户点击“使用 Facebook 登录”按钮(或其他社交身份验证服务)
- 身份验证后,用户会自动重定向回他们在第 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.send
和 res.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/