2

I'm using NextAuth with a custom backend in my Next.js app.

When I refresh the page, two refresh requests are sent almost simultaneously.

The first request correctly calls /auth/refresh and gets a new accessToken and refreshToken.

Immediately after that, a second request is sent, but it still uses the old refresh token.

The backend then returns 401, invalidating both tokens.

After this, no further API calls succeed until I log in again.

I'm trying to understand:

Why is the NextAuth jwt callback called twice during page reload?

How can I prevent multiple refresh calls from happening at the same time?

Here’s the relevant part of my config:

// auth.config.ts import type { NextAuthConfig } from "next-auth" import Credentials from "next-auth/providers/credentials" import validateCredential from "../server/actions/user/validateCredential" async function refreshAccessToken(token: any) { const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL! try { const res = await fetch(`${BACKEND_URL}/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: token.refreshToken }), cache: "no-store", }) if (!res.ok) throw new Error("Refresh failed") const data = await res.json() const newAccessToken = data.access_token || token.accessToken const newRefreshToken = data.refresh_token || token.refreshToken const payload = JSON.parse( Buffer.from(newAccessToken.split(".")[1], "base64").toString() ) const newExp = payload?.exp ? payload.exp * 1000 : undefined return { ...token, accessToken: newAccessToken, refreshToken: newRefreshToken, accessTokenExpires: newExp, } } catch (e) { return { ...token, error: "RefreshAccessTokenError" } } } const authConfig: NextAuthConfig = { providers: [ Credentials({ async authorize(credentials) { const user = await validateCredential(credentials as any) return user ?? null }, }), ], callbacks: { async jwt({ token, user }) { if (user) { return { ...token, accessToken: user.accessToken, refreshToken: user.refreshToken, accessTokenExpires: user.accessTokenExpires, } } const now = Date.now() if (token.accessTokenExpires && now < token.accessTokenExpires - 10000) { return token } if (token.refreshToken) { return await refreshAccessToken(token) } return { ...token, error: "RefreshAccessTokenError" } }, }, } export default authConfig 

And my credential validation:

// validateCredential.ts export default async function validateCredential(values) { const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/login/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, cache: 'no-store', body: JSON.stringify(values), }) if (!res.ok) return null const data = await res.json() return { id: String(data.user.id), accessToken: data.access_token, refreshToken: data.refresh_token, accessTokenExpires: data.access_token_exp, } } 
3
  • Can you show the code that sends the request? Commented Oct 18 at 8:35
  • refreshAccessToken() already exists in auth.config.ts file Commented Oct 18 at 8:46
  • I think you could follow this discussion: github.com/nextauthjs/next-auth/discussions/1455 Commented Oct 19 at 11:29

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.