supabase: Refresh token errors in @supabase/ssr
Bug report
- I confirm this is a bug with Supabase, not with my own application.
- I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
If you use the serverClient on multiple layouts in the nextjs app, this will trigger a race condition on who gets to refresh the token first. The request that refreshes the token first will get the correct token, but if another refresh request is initiated from another route, this will cause the app to error with AuthApiError: Invalid Refresh Token: Already Used
To Reproduce
This happens randomly, I have the new @supbase/ssr
in the next 14 app router, use it in the middleware for auth and also on various layout components to display user specific content
Expected behavior
App doesn’t crash 😃
Screenshots
If applicable, add screenshots to help explain your problem.
System information
- OS: macOS
- Browser (if applies) [e.g. chrome, safari]
- Version of @supabase/ssr: 0.0.10
- Version of Node.js: v18.17.1
- Next 14.0.1
Additional context
Add any other context about the problem here.
About this issue
- Original URL
- State: closed
- Created 8 months ago
- Comments: 22 (3 by maintainers)
You can take a look at our most recently updated Next.js Auth guide that describes how to set up middleware: https://supabase.com/docs/guides/auth/server-side/nextjs
The key points are:
supabase.auth.getUser
)request.cookies.set
response.cookies.set
Hey @eposha, the code you posted still doesn’t work for me. Even after setting those cookies on the next reload, it still seems to read the session as null.
Also, the code is not handling the case where the cookie size is split in multiple chunks, which might cause weird errors down the line.
The set cookie in the middleware never gets called. And neither is remove. If you enable debug: true in the middelware client you can see a lot of crazy stuff happening so I’m actually not sure who actually sets that original login cookie in your browser https://github.com/vercel/next.js/blob/778fb871314e840390496f4147483ba18d974d83/examples/with-supabase/utils/supabase/middleware.ts#L20
This is rather frustrating and I’m really surprised that they provided migration docs for a non-working library
Okay awesome, glad it’s fixed 👍
I need to revisit creating a supabase client in middleware docs! Thanks for letting us know!
Quick one to call out from your above example,
autoRefreshToken
should not be set totrue
if creating a client server-side. This sets up a timer to refresh the session, which should only be run in the browser 👍So this code in middleware resolve problem for me
You should set session in the cookies every time when middleware was called
I am using NextJS: 14.0.3 and @supabase/ssr: 0.0.10. Recently I noticed several
AuthApiError: Rate limit exceeded
errors. Adding theresponse.cookies.set
snippet as suggested above in my middleware resolved the issue.@cipriancaba I have a question for you, can you share with me your solution with redirection? I have a similar issue as you reported. And more a less same desire behaviour. In middleware, if the user doesn’t have a session I redirect him to login. But everytime after login in supabase first redirects to (‘/’) it has session null. Whenever I manually refresh the page it redirects user correctly to the ‘/’ with session. Thanks for a help!
That’s an interesting idea @dijonmusters which would probably make sense for a public app, but I only have a dashboard where ALL the routes should be protected. I want to deny access in the middleware if the user is not logged in and for that I definitely need to check the user has a valid session, otherwise redirect to the /login route.
From a workflow perspective, i would want the following to happen:
This seems it should work out of the box but for some reason the invalid refresh token randomly gets triggered and I am not sure where. I’ve disabled autorefreshtoken in every server component so only middleware would be responsible for refreshing the token, but that also doesn’t seem to do the trick
All that being said, I did make a change yesterday which seems to have fixed the issue up until this point. In the migration docs, there is this snippet for the middleware code:
https://supabase.com/docs/guides/auth/server-side/creating-a-client?environment=middleware
I’ve updated that to the following:
Basically removed the request.setCookie which seems that it was failing and then the response.setCookie never got called
I am getting:
AuthApiError: missing destination name refreshed_at in *models.Session
causing a re-login.@cipriancaba I found the source of the problem
The thing is that when getSession updates the session because JWT expired, the cookies remain the same and are not overwritten with the new data from the updated session.
After session revalidation we get a new refresh_token but the cookies remain the same
@silentworks FYI