Supabase database returned no results.
I recently implemented "Sign in with Apple" in my Swift iOS app.
A few days ago I started implemented storing and retrieving some data in Supabase database.
Back then I was able to successfully retrieve rows.
Today everything changed:
The same code which used to retrieve proper rows for a user, started retrieving NO rows at all.
On supabase.com/dashboard/project/XXX/logs/auth-logs i found this:
"Invalid Refresh Token: Refresh Token Not Found"
What the hell? How is it not found? I did not in any way remove it manually myself!
Then i signed out and signed it (which caused
try await supabaseClient.auth.session
to be called) and only after I did it, I started getting rows as I used to before.
I was thinking that it could be due to session token expiration, but this didn't happen to be the case.
I found this post on Reddit: https://www.reddit.com/r/Supabase/comments/1jr5jof/400_invalid_refresh_token_refresh_token_not_found/.
But not 100% sure how to handle it in my app if there is even no error thrown locally when a refresh token isn't found for whatever reason. So sending 2 requests each is not an option for me (1: try await supabaseClient.auth.session
to do whatever it does under the hood; 2: Fetch some rows i need with a SELECT requests). And I can't even be sure that try await supabaseClient.auth.session
is a fix until i know how to reproduce this bug)
So I'd like to know:
1. Why the hell did this happen
2. (Most importantly) how to reproduce it
3. Ideally a clear statement from anyone from Supabase company that "Supabase Auth is not reliable".
I'm so frustrated. Primarily because I don't know how to reproduce this crap :(
I'm considering moving off Supabase in favor of my own backend in Python for one simple reason: if something does not work, I can know the EXACT reason why, hence I can reproduce it and fix it.31 Replies
This is a user helping user forum so you won't get 3 here.
Refresh token not found could be from a security violation of it being used twice in time. Or it could be a signout from any device as that removes the refresh token for all devices (sessions).
Also in logs i found a Login event with a user ID on September 18 (today), whose last sign at field is Sunday 27 July.
How is it even possible?
you won't get 3 here.Sad :(
Last signin event is the last time an actual signin call was made versus the auto refresh of the existing user session that occurs as long as refresh token is valid. A user can come and go from the app "forever" as long as they don't signout from any session or there is an error with token, or the JWT signing key is changed. There might be other reasons but those I encounter here most often.
@garyaustin
being used twice in time.I personally haven't manually used it twice. I don't know what Supabase does under the hood – whether or not it used it twice is a mystery to me. I just tried logging in on another device, but that did not cause the refresh token to become removed or anything like this which would cause no records being retrieved from database on my primary device...
It would not be removed by signing into another device. It would be removed if you used default signout which signs out all sessions.
Well unless you are Pro and have 1 session limit on.
@garyaustin i used default signout as well, but this did not cause the refresh token to become lost as well
@garyaustin i'm not Pro
If you call signOut with default of Global it will remove all refresh tokens of any session for that user:
But that will not log the user out of the other device until their JWT expires and a refresh is attempted. I actually don't know for sure the error that would occur, but my guess it is refresh token not found.

@garyaustin is this the JWT token expiration?
supabaseClient.auth.currentSession?.expiresAt
I believe so.
@garyaustin so in order to reproduce this bug, i must:
1. Log in on primary device
2. Log in on secondary device
3. Sign out gobally (default) on secondary device
4. Wait until i reach
supabaseClient.auth.currentSession?.expiresAt
time
5. Try to fetch some rows
Correct?You can decrease the JWT life time in JWT settings of the dashboard to something like 300 seconds to speed up the process (takes affect after the current session times out).
But that sounds like it would cause an error and maybe signout the user.
Not sure what bug you are chasing exactly though.
I want to reproduce the bug where i get
Invalid Refresh Token: Refresh Token Not Found
in Supabase website logs
^ Which likely leads to no rows being retrieved from my databaseThat is not a bug if you get signed out or the refresh token gets invalidated.
Your existing client should error and get signed out.
Your existing client should error and get signed out.When shall I see this error? Users are not signed out in my app unless I explicitly sign them out. They might be signed out in Supabase, not my app, in which case I have no idea how to see the error in my Swift app for it. I signed out on my secondary device, but i don't see any errors on my primary iOS device Errors usually come on iOS after some actions, not on its own
When you try an operation or the other client tries to refresh their token.
@garyaustin i mentioned in this post that i haven't got any errors thrown when i was trying to fetch rows
A second device is not in constant communication with the server and the server can't inform the device. Until you use it OR it tries to refresh the session there will be no error. If it finds out there is no refresh token then it will signout.
You have to signin again to be able to do operations. You would become anon users if signed out and your RLS would likely return no rows.
@garyaustin
You would become anon usersHow to know that I became an anon user?
When you signin or your app auto signs you in again (depending on method) you might need to do await auth.getSession() or whatever that is in Swift. As it takes some time to get the new session.
There should have been a signout event if it was from refresh failing in a running app.
Swift: Listen to auth events | Supabase Docs
Supabase API reference for Swift: Listen to auth events
I don't see any changes coming to my primary device when i sign out from the secondary device.
Only current-device messages (which is useless)

The secondary device would not signout until it tries to refresh. It would not be immediately on your other device signing out.
Also I assume the Swift uses Global signout by default, but I don't know that and the docs are no help...

@garyaustin
The secondary device would not signout until it tries to refreshThank you very much :) Does this info exist in docs though? @garyaustin
Swift uses Global signout by defaulttrue
From JS
So signout event is supposed to occur when session is terminated including signout from other device AND it can't know until the JWT needs to be refreshed and goes to the Supabase server.


Thank you very much :)
@garyaustin I just
1. Signed out globally from secondary device
2. Waited for an hour
2. Made a SELECT request to get my rows
The result was expectedly empty. Which is good.
But there was no sign out even received... In fact no auth event was received when i sent the SELECT request to my db... Is this expected?
Was your primary device app in "foreground" the entire time or closed/background and then you reopened it?
But I don't know Swift at all.
You might skim thru this and other Swift "signout" issues to see if anything relevant. https://github.com/supabase/supabase-swift/issues/486
I would expect a signout only if the app were in a condition that the event can trigger.
@garyaustin i intentionally kept both devices in foreground
I don't know then.
@garyaustin thank you anyways. At least thanks to you I know the reason. And I know how to fix it (use local sign out scopes)
If createClient has to run again before you do your select then it will notice that the JWT is expired and attempt to refresh it. If you are making a call directly after createClient you have to do an await.auth.getSession as it takes time to get the new JWT. There would not be a signout in this case.
@garyaustin i did not get the auth state updated in both cases:
1. client initializer wasn't called -> no SELECT request was made
2. client initializer was called -> a SELECT request was made