Question 💬
Hi, I am attempting to implement the refresh flow as shown in the docs. I have successfully implemented this when the user is on a single tab, but when using multiple tabs in the same browser, or when spamming the refresh button, the auth flow breaks and signs the user out.
EDIT: It appears I was wrong, it is not multiple tabs causing the problem but the second refresh.
- Sign in grants access token / refresh token
- AT expires -> refresh is called issues new AT/RT and that works fine
- Attempt to refresh the browser again and the JWT callback is using the initial token where the RT is no longer accepted.
Please can someone take a look and make any suggestions to help solve this issue? Thanks
EDIT 2: Looks like it is related to this and there is not yet a suitable workaround #6642
How to reproduce ☕️
next-auth: 4.23.1
next: 13.4.9
react: 18.2.0
node: 18.17.0
pnpm: 8
route.ts:
import NextAuth from 'next-auth';
import { options } from './options';
const handler = NextAuth(options);
export { handler as GET, handler as POST };
options.ts
async function refreshAccessToken(token: JWT) {
try {
var res = await fetch("https://myapi/api/v1/refresh", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token.accessToken}`
},
body: JSON.stringify({
refreshToken: token.refreshToken,
username: token.username
})
});
const refreshedTokens = await res.json();
if (!res.ok) throw refreshedTokens;
return {
...token,
accessToken: refreshedTokens.token,
expires: Date.now() + (refreshedTokens.expires - 10) * 1000,
refreshToken: refreshedTokens.refreshToken
}
} catch (err) {
console.log(err)
await signOut()
return {
token,
error: "RefreshTokenAccessError"
}
}
}
export const options: NextAuthOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {},
async authorize(
credentials: Record<string, string> | undefined
): Promise<any> {
var res = await fetch('https://myapi/api/v1/login', {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: credentials?.username,
password: credentials?.password
})
})
if (res.ok) {
const user = await res.json();
return {
username: user.username,
password: '',
accessToken: user.accessToken.token,
refreshToken: user.accessToken.refreshToken,
expires: Date.now() + (user.accessToken.expiresIn - 10) * 1000
};
}
return null
},
}),
],
pages: {
signIn: '/login',
},
session: {
strategy: 'jwt',
maxAge: 24 * 60 * 60,
},
callbacks: {
async redirect() {
return '/protected';
},
async jwt({ token, user, account }) {
if (account && user) {
return {...token, ...user}
}
// @ts-ignore
if (Date.now() < token.expires) {
return token
}
return await refreshAccessToken(token)
},
async session({ session, token }) {
session.user = token as {
username: string;
password: string;
expires: ISODateString;
accessToken: string;
refreshToken: string;
};
return session;
},
},
secret: process.env.NEXTAUTH_SECRET,
};
As stated, this works perfectly fine in a single tab, I've set the accesstoken to expire at 60 seconds, so predictably at >50 seconds when refreshing the page or navigating protected routes it refreshes the token and I get new accessToken and refreshToken. If I have two tabs open at the same time as requiring a refresh then the tab that has a browser refresh second seems to have stale JWT token data, which then causes a 404 on the /refresh api and signs the user out.
Any help would be appreciated.
Contributing 🙌🏽
Yes, I am willing to help answer this question in a PR
Question 💬
Hi, I am attempting to implement the refresh flow as shown in the docs. I have successfully implemented this when the user is on a single tab, but when using multiple tabs in the same browser, or when spamming the refresh button, the auth flow breaks and signs the user out.
EDIT: It appears I was wrong, it is not multiple tabs causing the problem but the second refresh.
Please can someone take a look and make any suggestions to help solve this issue? Thanks
EDIT 2: Looks like it is related to this and there is not yet a suitable workaround #6642
How to reproduce ☕️
next-auth: 4.23.1
next: 13.4.9
react: 18.2.0
node: 18.17.0
pnpm: 8
route.ts:
options.ts
As stated, this works perfectly fine in a single tab, I've set the accesstoken to expire at 60 seconds, so predictably at >50 seconds when refreshing the page or navigating protected routes it refreshes the token and I get new accessToken and refreshToken. If I have two tabs open at the same time as requiring a refresh then the tab that has a browser refresh second seems to have stale JWT token data, which then causes a 404 on the /refresh api and signs the user out.
Any help would be appreciated.
Contributing 🙌🏽
Yes, I am willing to help answer this question in a PR