我正在使用 Spotify Web API 为使用 Node Express 作为服务器的简单 React 应用程序实现授权代码流,并且无法弄清楚如何将身份验证凭据从服务器传递到客户端。
我正在使用 React 的 useContext 挂钩来存储授权凭据。
import React, { createContext, useState, useEffect } from "react";
// the shape of the default value must match
// the shape that consumers expect
// auth is an object, setAuthData is a function
export const AuthContext = createContext({
auth: {},
setAuthData: () => {},
});
const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({ loading: true, data: null });
const setAuthData = (data) => {
setAuth({ data: data });
};
// on component mount, set the authorization data to
// what is found in local storage
useEffect(() => {
setAuth({
loading: false,
data: JSON.parse(window.localStorage.getItem("authData")),
});
return () => console.log("AuthProvider...");
}, []);
// when authorization data changes, update the local storage
useEffect(() => {
window.localStorage.setItem("authData", JSON.stringify(auth.data));
}, [auth.data]);
return (
<AuthContext.Provider value={{ auth, setAuthData: setAuthData }}>
{children}
</AuthContext.Provider>
);
};
export default AuthProvider;
在 index.js 中,我将我的应用程序包装在 AuthProvider 中
import ReactDOM from "react-dom";
import AuthProvider from "./contexts/AuthContext";
import App from "./Components/App/AppRouter";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>,
document.getElementById("root")
);
在应用程序中,我使用 react-router-dom 来管理路由和受保护的路由。
// External libraries
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";
import { AuthContext } from "../../contexts/AuthContext";
// Components
import { PrivateRoute } from "../PrivateRoute/PrivateRoute";
function AppRouter(props) {
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/" component={AppLite} />
</Switch>
</Router>
);
}
在 PrivateRoute 内部,我允许根据身份验证上下文中的内容访问路由。
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "../../contexts/AuthContext";
import Layout from "../App/Layout";
export const PrivateRoute = ({ component: Component, ...rest }) => {
const { auth, setAuthData } = useContext(AuthContext);
// if loading is set to true, render loading text
if (auth.loading) {
return (
<Route
render={() => {
return (
<Layout>
<h2>Loading...</h2>
</Layout>
);
}}
/>
);
}
// if the user has authorization render the component,
// otherwise redirect to the login screen
return auth.data ? <Component /> : <Redirect to="/login" />;
};
当用户进入主页时,他们会被重定向到登录屏幕。在那里,一个链接将用户重定向到 /api/login。/api 路由被代理到节点服务器,并且 /api/login 启动 spotify 授权调用。用户被定向到 Spotify 登录,输入他们的信息,我最终得到一个访问令牌和刷新令牌。这一切都有效。
使用访问令牌和刷新令牌,我可以将用户重定向到带有这些参数(例如/#/user/${access_token}/${refresh_token}
)的 URL,但我不知道如何将这些参数放入我的授权上下文中。请注意,从 URL 获取令牌不是问题。
我试图做的是向我的 PrivateRoute 添加一个 useEffect ,它从 URL 获取参数,然后在找到它们时更新授权上下文。
const { auth, setAuthData } = useContext(AuthContext);
const location = useLocation();
// on mounting the component, check the URL for
// authentication data, if it is present, set
// it on the authorization context
useEffect(() => {
let authData = getHashParams(location);
authData && setAuthData(authData);
return () => {
authData = null;
};
}, [location, setAuthData]);
然而,这会使事情陷入无限循环。我似乎只能在由 onClick 事件触发时成功使用 setAuthData 。我应该如何拦截来自我的 api 路由器的重定向,以便我可以更新授权上下文中的数据,然后转到 PrivateRoute?
或者,有没有一种方法可以将我的所有 api 路由器逻辑封装在 onClick 事件中并从中获取最终响应(例如fetch("/api/login")....user gets redirected, fills in info, exchange code for tokens, send tokens back as response...then((response) => setAuthData(response)...
)?