我有一个React / Redux / React Router前端,Node / Express后端。我正在使用Passport(包括Facebook,Google和Github在内的各种策略)进行身份验证。
我想发生的事情:
未经身份验证的用户尝试访问受保护的客户端路由(类似/posts/:postid,然后重定向到/login。(React Router正在处理此部分)
/posts/:postid
/login
用户单击“使用Facebook登录”按钮(或其他社交身份验证服务)
身份验证之后,用户将自动重定向回他们在步骤1中尝试访问的路由。
相反,发生了什么:
我发现使用React前端成功处理Passport社交身份验证的唯一方法是将“使用Facebook登录”按钮包装在<a>标签中:
<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)。
http://localhost:8080/auth/facebook/callback
在回调函数中,我需要(1)将用户对象和令牌返回给客户端,以及(2)重定向到客户端路由- 他们在重定向到之前尝试访问的受保护路由/login,或某些默认路由,例如/或/dashboard。
/
/dashboard
但是由于Express中没有办法完成这两项操作(我不能res.sendAND res.redirect,我必须选择其中一个),所以我一直以一种笨拙的方式来处理它: res.redirect(${CLIENT_URL}/user/${userId})
res.send
res.redirect
res.redirect(
)
这会将/user路由加载到客户端上,然后将userId从路由参数中拉出,将其保存到Redux,然后对服务器进行ANOTHER调用以返回令牌以将令牌保存到localStorage。
/user
尽管感觉很笨拙,但所有操作都可以正常进行,但是 在提示用户登录之前 ,我无法弄清楚 如何重定向到用户尝试访问的受保护路由。
我首先尝试在用户尝试访问Redux时将尝试的路由保存到Redux,以为一旦他们在身份验证后登陆到个人资料页面,便可以使用该路由进行重定向。但是由于Passport身份验证流程将用户带离现场进行3d方身份验证,然后将SPA重新加载到res.redirect,因此存储被销毁,并且重定向路径丢失了。
我最后要解决的是将尝试的路由保存到localStorage,检查组件安装在前端redirectUrl时在localStorage中是否存在密钥/user,使用重定向,this.props.history.push(redirectUrl)然后redirectUrl从localStorage 清除密钥。这似乎是一个很脏的解决方法,必须有一种更好的方法来执行此操作。还有其他人想出如何使这项工作吗?
redirectUrl
this.props.history.push(redirectUrl)
万一其他人为此感到挣扎,这就是我最终要解决的问题:
1.当用户尝试访问受保护的路由时,/login使用React-Router 重定向到。
首先定义一个<PrivateRoute>组件:
<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属性传递给路线:
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
window.location.replaceState
// 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的本地存储中将不存在