Next-Auth集成AWS Cognito无法获取JWT access_token求助
我在Next.js 13项目里用Next-Auth集成了AWS Cognito做认证,登录已经成功,也能通过useSession()拿到会话信息,但现在需要调用同一个Cognito保护的REST API,得把用户的Bearer令牌传给API才能获取专属数据,目前拿不到用于API认证的JWT令牌。
我的app/api/auth/[...nextauth]/route.js代码如下:
import NextAuth from "next-auth"; import CognitoProvider from "next-auth/providers/cognito"; export const authOptions = { providers: [ CognitoProvider({ clientId: process.env.COGNITO_CLIENT_ID, clientSecret: process.env.COGNITO_CLIENT_SECRET, issuer: process.env.COGNITO_ISSUER, authorization: { params: { scope: "openid", }, }, }), ], session: { jwt: true }, secret: process.env.NEXTAUTH_SECRET, jwt: { encryption: true, }, callbacks: { async jwt({ token, user }) { return { ...token, ...user }; }, async session({ session, token, user }) { session.user = token; return session; }, }, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
调用useSession()打印会话信息到控制台时,完全看不到JWT令牌相关内容。
解决方案
1. 调整Cognito授权Scope
首先修改CognitoProvider的scope,确保能获取到Cognito颁发的id_token和access_token,把scope扩展为:
authorization: { params: { scope: "openid email profile", // 按需添加,至少保留openid,建议补充email、profile }, },
如果需要调用Cognito管理类API,还可以追加aws.cognito.signin.user.admin。
2. 修改JWT回调,保存Cognito令牌
原来的jwt回调只合并了token和user,但Cognito返回的核心令牌(id_token、access_token、refresh_token)实际在account参数里,需要把这些令牌存入token对象:
async jwt({ token, user, account }) { // 用户首次登录时,把Cognito令牌存入token if (user && account) { token.idToken = account.id_token; token.accessToken = account.access_token; token.refreshToken = account.refresh_token; token.expiresAt = account.expires_at * 1000; // 转成毫秒时间戳,方便后续判断过期 } // 令牌未过期则返回现有token,过期可在此添加刷新逻辑 if (Date.now() < token.expiresAt) { return token; } // 临时返回原token,可自行补充令牌刷新逻辑 return token; },
3. 修改Session回调,暴露令牌到前端
要让前端通过useSession()拿到令牌,需要在session回调里把令牌字段挂载到session对象上:
async session({ session, token }) { session.user.idToken = token.idToken; session.user.accessToken = token.accessToken; session.user.refreshToken = token.refreshToken; return session; },
4. 处理JWT加密的影响
如果开启了jwt: { encryption: true },Next-Auth会对JWT加密,前端拿到的是加密后的内容,无法直接用作API的Bearer令牌。如果需要在前端直接使用Cognito令牌,建议关闭加密:
jwt: { encryption: false, // 关闭加密,前端才能拿到原始的Cognito令牌 },
如果必须保留加密,只能在Next.js的API路由中获取令牌,再转发给后端API(服务器端不受加密影响)。
修改后的完整代码
import NextAuth from "next-auth"; import CognitoProvider from "next-auth/providers/cognito"; export const authOptions = { providers: [ CognitoProvider({ clientId: process.env.COGNITO_CLIENT_ID, clientSecret: process.env.COGNITO_CLIENT_SECRET, issuer: process.env.COGNITO_ISSUER, authorization: { params: { scope: "openid email profile", // 扩展scope获取令牌和用户信息 }, }, }), ], session: { jwt: true, }, secret: process.env.NEXTAUTH_SECRET, jwt: { encryption: false, // 关闭加密以在前端获取原始令牌 }, callbacks: { async jwt({ token, user, account }) { if (user && account) { token.idToken = account.id_token; token.accessToken = account.access_token; token.refreshToken = account.refresh_token; token.expiresAt = account.expires_at * 1000; } if (Date.now() < token.expiresAt) { return token; } // 可选:添加令牌刷新逻辑 return token; }, async session({ session, token }) { session.user.idToken = token.idToken; session.user.accessToken = token.accessToken; session.user.refreshToken = token.refreshToken; return session; }, }, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
前端使用示例
现在在组件里调用useSession(),就能拿到session.user.accessToken,直接作为Bearer令牌传给API:
import { useSession } from "next-auth/react"; async function fetchUserInfo() { const { data: session } = useSession(); if (!session) return; const res = await fetch("https://your-api-endpoint.com/user", { headers: { Authorization: `Bearer ${session.user.accessToken}`, }, }); const data = await res.json(); console.log(data); }
内容的提问来源于stack exchange,提问作者Badhusha

