Next.js SSR项目中GraphQL认证请求头传递失败求助
问题
在SSR的Next.js项目中为GraphQL添加认证机制,需要仅允许已认证用户发起请求,但尝试在Authorization请求头中传递用户ID时失败。试过通过Firebase getAuth().currentUser 获取UID,也试过从Redux Store的getState()获取,但都无法成功传递信息。相关代码如下:
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; import { getAuth } from 'firebase/auth'; const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql', }); const authLink = setContext((_, { headers }) => { const user = getAuth().currentUser; return { headers: { ...headers, authorization: user?.uid ?? "null" } } }) const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache() }); export default client;
解决方案
Firebase Auth在SSR里拿不到用户的原因及解决办法
在SSR环境下,getAuth().currentUser 是客户端异步加载的,你初始化Apollo Client的时候,Firebase还没完成用户会话的加载,所以肯定拿不到值。得换个方式:
- 用Next.js的
getServerSideProps或者getInitialProps在页面渲染前提前获取用户的ID Token(别直接传UID,不安全),再把token传给Apollo Client。
比如在页面或者_app.js里写:
然后把Apollo Client改成工厂函数,接收token参数:export async function getServerSideProps(context) { const auth = getAuth(); const user = auth.currentUser; let token = null; if (user) { token = await user.getIdToken(); } return { props: { token }, // 把token传给页面组件 }; }
最后在_app.js里用这个函数创建客户端:export function createApolloClient(token) { const httpLink = createHttpLink({ uri: 'http://localhost:4000/graphql', }); const authLink = setContext((_, { headers }) => ({ headers: { ...headers, authorization: token ? `Bearer ${token}` : "null", }, })); return new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(), }); }import { ApolloProvider } from '@apollo/client'; import { createApolloClient } from '../lib/apollo-client'; function MyApp({ Component, pageProps, token }) { const client = createApolloClient(token); return ( <ApolloProvider client={client}> <Component {...pageProps} /> </ApolloProvider> ); } export async function getServerSideProps(context) { const auth = getAuth(); const user = auth.currentUser; let token = null; if (user) { token = await user.getIdToken(); } return { props: { token }, }; } export default MyApp;
Redux Store拿不到用户ID的问题
你从Redux Store的getState()拿不到用户ID,大概率是Apollo Client初始化的时机比Redux Store早,或者SSR里Redux状态还没完成hydrate。解决办法和上面类似:在_app.js里先拿到Redux的用户ID,再传给Apollo Client的创建函数,确保获取到的是最新的状态。
额外提醒
- 服务器端别直接信任Authorization头里的UID,得验证token的合法性(比如用Firebase Admin SDK验证ID Token),防止伪造请求。
- 如果是静态生成(SSG)的页面,没法在服务器端拿用户会话,就得在客户端等Firebase Auth加载完成后再设置授权头:
const authLink = setContext(async (_, { headers }) => { const user = await new Promise((resolve) => { const unsubscribe = getAuth().onAuthStateChanged((user) => { unsubscribe(); resolve(user); }); }); const token = user ? await user.getIdToken() : null; return { headers: { ...headers, authorization: token ? `Bearer ${token}` : "null", }, }; });
内容的提问来源于stack exchange,提问作者Nahuel Luna Garcia Soaje
相关产品推荐
相关产品推荐

