Handle refresh token in NextAuth.js
Unanswered
Bighead carp posted this in #help-forum
Bighead carpOP
I'm currently encountering an issue with Next.js while in strict mode. This problem arises from double renders. Specifically, when using useSession, if the token expires, the system attempts to refresh the same token twice.
This situation becomes problematic because, when the initial token is refreshed in response to a simultaneous request, it inadvertently triggers a user sign-out due to the RefreshTokenError that I've implemented.
Although the first request successfully passes and revokes the token—rendering it unusable—resulting in an "OK" status, the second request encounters failure. This happens because the new token isn't acquired in time, causing the same token to be sent to the backend again which leads to sign-out event.
Sample code: (jwt callback)
Anonye have any suggestions on how I can appraoch solving this issue?
This situation becomes problematic because, when the initial token is refreshed in response to a simultaneous request, it inadvertently triggers a user sign-out due to the RefreshTokenError that I've implemented.
Although the first request successfully passes and revokes the token—rendering it unusable—resulting in an "OK" status, the second request encounters failure. This happens because the new token isn't acquired in time, causing the same token to be sent to the backend again which leads to sign-out event.
Sample code: (jwt callback)
const jwt = jose.decodeJwt(token.user.accessToken);
const expirationTime = jwt.exp;
const currentTime = Math.floor(Date.now() / 1000);
if (expirationTime && currentTime > expirationTime) {
const result = await refreshToken(token.user.refreshToken)
return {
...token,
accessToken: result.accessToken,
refreshToken: result.refreshToken
}
}
return token;Anonye have any suggestions on how I can appraoch solving this issue?
26 Replies
Bighead carpOP
up
im not sure exactly what ur problem but if you want to have a critical section, would this lib work? I haven't tried it though.
https://www.npmjs.com/package/async-lock
https://www.npmjs.com/package/async-lock
@tafutada777 im not sure exactly what ur problem but if you want to have a critical section, would this lib work? I haven't tried it though.
https://www.npmjs.com/package/async-lock
Bighead carpOP
Thank you for response. I tried locking, but issue is that I probably need some queue system with it
if you for example do
It will create same issue for me
lock.acquire()
// work here
lock.free()It will create same issue for me
since it will just wait to complete
and right after it will still send same request
issue which happens is that react makes 2 session requests at almost same interval, and both get same JWT token which means they both have same refresh token
Even if I lock it will not solve issue
I probably need callback system:
-> refresh token request exists? -> put into callback queue
-> insert refresh token into queue
-> refresh token
-> resolve all call-backs
-> clean queue
-> return original request
-> refresh token request exists? -> put into callback queue
-> insert refresh token into queue
-> refresh token
-> resolve all call-backs
-> clean queue
-> return original request
how about having a global hashmap variable with a refresh token as a key? Node.js is single thread so no need to mutex on the global val.
Bighead carpOP
Yeah, and value could be just array of waiting callback
contains?
return new promise that pushes to queue and returns resolve once callback is called
not contains
- add
- start calling API
- re-check if there is anything in queue, and resolve all callbacks
- return
contains?
return new promise that pushes to queue and returns resolve once callback is called
not contains
- add
- start calling API
- re-check if there is anything in queue, and resolve all callbacks
- return
That make sense?
i just thought that we can use a hash code of the refresh token as a key of the global hash map, with a dirty flag like
global_map[hash(refresh_token)] = false
if ( !global_map[hash(refresh_token)] {
// invoke Google OAuth
global_map[hash(refresh_token)] = true
something like that
global_map[hash(refresh_token)] = false
if ( !global_map[hash(refresh_token)] {
// invoke Google OAuth
global_map[hash(refresh_token)] = true
something like that
Bighead carpOP
what would else be 🤔 ?
do nothing
btw double rendering mean the famous double rendering in dev mode, which won't happen in prod?
Bighead carpOP
I have this component:
If I do nothing, what would result be I guess I can just return nothing, and clear this hasmap every 30 sec
export function RequireAuth({ children }: { children: React.ReactNode }) {
const router = useRouter();
const { data: session, status } = useSession();
useEffect(() => {
console.log(session, status);
const performSessionCheck = async () => {
if (status === "unauthenticated" || (status !== "loading" && session!.error === "RefreshAccessTokenError")) {
await signOut({ redirect: false });
console.log("redirecting to signin fault refresh token?");
router.push("/auth/signin");
}
};
performSessionCheck();
}, [router, status, session]);
return <>{children}</>;
}If I do nothing, what would result be I guess I can just return nothing, and clear this hasmap every 30 sec
@tafutada777 btw double rendering mean the famous double rendering in dev mode, which won't happen in prod?
Bighead carpOP
Yea I'm pretty sure it's due to dev mode
But I just want to prevent something like this even if it renders twice
oh. does that matter?
@tafutada777 oh. does that matter?
Bighead carpOP
What exactly?
its dev mode. it sound overengineering.
Bighead carpOP
This looks like error that might happen if I somewhere call it twice
And it will probably:
A) Very hard to debug
B) Very hard to even think about it in future
A) Very hard to debug
B) Very hard to even think about it in future
I rather eliminate this possibility fully even it is overengineering
Bighead carpOP
do nothing
But which one will get picked 🤔 maybe store result..
Since if react decides to pick one which returns nothing then how will I know that accessToken is valid and I can make request with it