Next.js+Amplify getServerSideProps调REST API报No current user
在基于AWS Amplify + Next.js搭建的项目中,在getServerSideProps方法内发起受AWS Cognito认证保护的REST API请求时,触发No current user报错,但同一段API调用代码在客户端侧(比如useEffect钩子中)可以正常运行。
报错逻辑的实现代码如下:
export async function getServerSideProps({req}) { const { Auth, API } = withSSRContext({req}); try { const apiName = "productApi-dev"; const path = `/products`; products = await API.get(apiName, path); // 该调用在useEffect钩子中可正常运行 } catch (e) { console.log(e); } return { props: { products, }, }; }
在同一段getServerSideProps逻辑中加入测试代码验证用户认证状态:
const user = await Auth.currentAuthenticatedUser(); console.log(user)
测试结果显示可以正常打印预期的用户信息,无认证相关报错。
当前项目的AWS Amplify配置如下:
Amplify.configure({ Auth: { mandatorySignIn: true, region: "us-east-1", userPoolId: *****, userPoolWebClientId: *****, identityPoolId: i*****, signupAttributes: "EMAIL", secure: true, }, API: { endpoints: [ { name: "*******", endpoint: "https://*******.execute-api.us-east-1.amazonaws.com", region: "us-east-1", custom_header: async () => { return { Authorization: `Bearer ${(await Auth.currentSession()) .getIdToken() .getJwtToken()}`, }; }, }, ], }, ssr: true, });
根据AWS Amplify官方文档说明,withSSRContext工具可在服务端渲染路由中向GraphQL、REST后端发起认证请求,但官方示例仅给出了GraphQL请求指定authMode的用法:
movieData = await API.graphql({ query: listMovies, authMode: "AMAZON_COGNITO_USER_POOLS" });
无REST API对应的服务端认证请求使用说明。
报错核心原因是:全局Amplify.configure中定义的custom_header函数,内部引用的是全局导入的Auth实例,而非withSSRContext({req})生成的、绑定了当前请求上下文的服务端Auth实例。
服务端渲染时每个请求都是独立上下文,全局Auth实例没有绑定当前请求携带的Cookie认证信息,调用currentSession()时自然拿不到当前用户,才会抛出No current user错误。
在getServerSideProps里直接用withSSRContext返回的Auth调用currentAuthenticatedUser()能成功,就是因为这个返回的Auth是绑定了当前请求上下文的实例。
不要在全局配置里写死custom_header,在服务端调用REST API时,手动从当前SSR上下文的Auth实例取Token,加到请求头里即可,有两种可落地的实现方式:
方式1:调用API时直接传入请求头(改动最小)
修改getServerSideProps中的API调用逻辑,手动生成Authorization头传入:
export async function getServerSideProps({req}) { const { Auth, API } = withSSRContext({req}); try { // 从当前SSR上下文的Auth实例取会话Token const session = await Auth.currentSession(); const token = session.getIdToken().getJwtToken(); const apiName = "productApi-dev"; const path = `/products`; // 发起请求时传入自定义头 products = await API.get(apiName, path, { headers: { Authorization: `Bearer ${token}` } }); } catch (e) { console.log(e); } return { props: { products, }, }; }
注意:此时可以把全局配置里API端点的custom_header逻辑删掉,避免客户端侧和服务端侧逻辑冲突。
方式2:为SSR上下文单独配置API实例(适配多接口复用场景)
如果服务端有多个REST接口需要调用,不想每次重复写取Token的逻辑,可以在getServerSideProps内用当前SSR上下文单独配置一次API,把custom_header绑定到当前上下文的Auth实例:
export async function getServerSideProps({req}) { const { Auth, API } = withSSRContext({req}); // 用当前SSR上下文配置API API.configure({ endpoints: [ { name: "productApi-dev", endpoint: "https://*******.execute-api.us-east-1.amazonaws.com", region: "us-east-1", // 这里的Auth是当前请求上下文的实例,能正确拿到用户会话 custom_header: async () => { return { Authorization: `Bearer ${(await Auth.currentSession()) .getIdToken() .getJwtToken()}`, }; }, }, ] }) try { const apiName = "productApi-dev"; const path = `/products`; products = await API.get(apiName, path); } catch (e) { console.log(e); } return { props: { products, }, }; }
两种方式都可以解决问题,单接口场景优先选方式1即可。
内容的提问来源于stack exchange,提问作者Ben Jonson

