NextAuth.js中JWT回调无法更新accessTokenExpiry导致重复刷新令牌
NextAuth.JS刷新令牌轮换逻辑异常:每次触发JWT回调都执行刷新操作
我在NextAuth.JS的options.ts中实现刷新令牌轮换逻辑,参考官方文档方案:在JWT回调里检查access token是否过期,过期则调用refreshToken函数从服务器获取新令牌,服务器返回{ accessToken, refreshToken, accessTokenExpiry }。
但现在的问题是,哪怕已经按照文档更新了accessTokenExpiry字段,每次触发JWT回调时都会执行令牌刷新操作。
我加了console.log排查,发现:
- 服务器返回的
data.accessTokenExpiry是正确的(我把access token有效期设为5秒,当前时间53:41时,过期时间显示为53:46) - 但JWT回调里的
token.accessTokenExpiry时间始终错误

其中:
- 第96行日志来自:
console.log(new Date(Date.now()), new Date(token.accessTokenExpiry) - 第18行日志来自:
console.log(new Date(Date.now()), new Date(data.accessTokenExpiry)
相关代码
[...nextauth]/options.ts
import { UUID } from 'crypto'; import { NextAuthOptions } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const refreshToken = async (token: any) => { try { const response = await fetch(`${process.env.SERVER_URL}/admins/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token.refreshToken}`, }, }); const data = await response.json(); console.log('row 18:', new Date(Date.now()), new Date(data.accessTokenExpiry)); if (!response.ok) { throw data; } return { accessToken: data.accessToken, accessTokenExpiry: data.accessTokenExpiry, refreshToken: data.refreshToken ?? token.refreshToken, }; } catch (error) { console.error(error); return { ...token, error: 'RefreshAccessTokenError', }; } }; export const options: NextAuthOptions = { session: { maxAge: 60 * 24 * 60 * 60, }, secret: process.env.JWT_SECRET, providers: [ CredentialsProvider({ name: 'Credentials', credentials: { username: { label: 'Username', type: 'text' }, password: { label: 'Password', type: 'password' }, }, authorize: async credentials => { const response = await fetch(`${process.env.SERVER_URL}/admins/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials), }); if (response.status === 404) { throw new Error('404'); } if (response.status === 403) { throw new Error('403'); } const data = await response.json(); const user = await fetch(`${process.env.SERVER_URL}/admins/admin`, { headers: { Authorization: `Bearer ${data.accessToken}`, }, }).then(res => res.json()); return { ...user, accessToken: data.accessToken, refreshToken: data.refreshToken, accessTokenExpiry: data.accessTokenExpiry, }; }, }), ], callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id as UUID; token.email = user.email; token.name = user.name; token.surname = user.surname; token.role = user.role; token.accessToken = user.accessToken!; token.refreshToken = user.refreshToken!; token.accessTokenExpiry = user.accessTokenExpiry; } console.log('row 96:', new Date(Date.now()), new Date(token.accessTokenExpiry)); // \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ // Checking if the access token is expired if (Date.now() < token.accessTokenExpiry) { return token; } return await refreshToken(token); }, async session({ session, token }) { session.user.email = token.email; session.user.role = token.role; session.user.surname = token.surname; session.user.name = token.name; session.user.id = token.id; session.accessToken = token.accessToken; session.refreshToken = token.refreshToken; session.accessTokenExpiry = token.accessTokenExpiry; return session; }, }, };
[...nextauth]/route.ts
import NextAuth from 'next-auth/next'; import { options } from './options'; const handler = NextAuth(options); export { handler as GET, handler as POST };
routes/admins/auth/auth-admin.service.ts
async refreshToken(req: Request) { const token = req.headers.authorization.split(' ')[1]; const payload = this.jwtService.decode(token); const admin = await this.adminsRepository.findOne({ where: { id: payload.id }, }); if (token !== admin.refreshToken) { throw new UnauthorizedException('Invalid refresh token'); } console.log('refresh'); // const accessTokenExpiry = Date.now() + 12 * 60 * 60 * 1000; const accessTokenExpiry = Date.now() + 5 * 1000; const accessTokenPayload = { id: payload.id, exp: accessTokenExpiry }; const newAccessToken = this.jwtService.sign(accessTokenPayload); return { accessToken: newAccessToken, refreshToken: payload.refreshToken, accessTokenExpiry, }; }
内容的提问来源于stack exchange,提问作者Lvasche
相关产品推荐
相关产品推荐

