Updating & refreshing user metadata causes Error "Invalid Refresh Token: Already Used"
Bug report
- [x] I confirm this is a bug with Supabase, not with my own application.
- [x] I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
I'm trying to use the Supabase user metadata as a means of storing user metadata (like preferences eg. is_subscribed_to_newsletter), since it seems way more intuitive than having a clone of it in Postgres, and updating those... This is my code:
await supabaseClient.auth.updateUser({
data: {
...metadata,
...updatedMetadata
}
})
const { error } = await supabaseClient.auth.refreshSession()
Basically, the first time I update any user metadata, it works perfectly, but the second time and all times after that I get the error "Invalid Refresh Token: Already Used"
As seen here and here, other people are also facing this issue.
To Reproduce
- Create a Next app with an authenticated user
- call updateUser, then refreshSession
- Try to updateUser, then refreshSession again, and you will get error "Invalid Refresh Token: Already Used"
Expected behavior
I expect the refreshSession to also refetch a new refresh-token, while refetching updated account information for the useUser hook.
Being able to use the user_metadata field for small personal user preferences is super good for DX. I always cringed at the idea of creating some Postgres triggers (that last I checked weren't even production ready) only to create a matching public.users table that then contained the users data, when we could just use the user_metadata itself.
Screenshots
If applicable, add screenshots to help explain your problem.
System information
- OS: macOS
- Browser (if applies) Chrome
- Version of supabase-js: 2.26.0
- Version of Node.js: v18.15.0
+1 also hitting this in our production app. Not a blocker, but hugely painful to have to log out and log back in everytime to manually reset the token.
For our use case, we use user_metadata to track the current org view we are switched into from an admin view. Here's the code snippet:
// Updates currently active user org in app as well as
// cached org in Supabase user metadata.
const setOrgForUser = async (newOrg: Organization) => {
setCurrentOrg(newOrg)
await supabase.auth.updateUser({
data: {
org: newOrg.id,
},
})
await supabase.auth.refreshSession()
const getUserRes = await supabase.auth.getUser()
console.log('getUserRes', getUserRes)
// window.location.reload()
}
Normally we call window.location.reload() to ensure all components are refreshed using this new orgID, but I commented it out here to trace this more carefully. What I'm seeing is that the getUserRes log outputs the expected result when I switched from org A to B. I confirmed by running a query in the SQL editor on the auth.users table as well to read out the value of raw_user_meta_data.org on that row.
But after refreshing the browser tab it loads org A again and I'm trying to debug this. My understanding is that the access token and refresh tokens come from the 3rd party identity provider (in our case Google) so I'm not sure why failing to use the refresh token would lead to the issue that I'm observing.
@skoshx i can't seem to reproduce the issue on my end - this is the code i'm using to test: https://gist.github.com/kangmingtay/c3559556033ba599f51182cf5956ac6c
@douglasqian
My understanding is that the access token and refresh tokens come from the 3rd party identity provider (in our case Google)
The access and refresh tokens are created by gotrue as gotrue acts as the intermediary. If you need the tokens returned by google, you need to retrieve the provider_access_token or provider_refresh_token from the session.
Based on your code, you don't need to call refreshSession followed by getUser. refreshSession already returns an AuthResponse which contains the user's information in it.
@kangmingtay , the problem is that useUser hook doesn't return refreshed user metadata without calling refreshSession.
This feels like an issue for another repo. gotrue-js does not define a useUser hook. @silentworks where is this defined?
This was fixed recently in the auth-helpers by a PR from a user https://github.com/supabase/auth-helpers/pull/617
I see that now the session is updated which is good. I'm wondering though that does the refreshed session also make it so that the refresh token rotates?
Just wondering that does the auth-helper fix really close out this issue.