"Refresh Token Not Found" = automatic logout after 2-3 days after login
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
About 2-3 days after logging in, the user gets automatically logged out (we are using the OTP login and use the API on iOS and macOS). Checking the error messages, we see:
api(Auth.AuthError.APIError(msg: nil, code: nil, error: Optional("invalid_grant"), errorDescription: Optional("Invalid Refresh Token: Refresh Token Not Found"), weakPassword: nil))
To Reproduce
Using the Swift API of Supabase, login, use the app for 2-3 days and you will get logged out.
Expected behavior
User should stay logged in indefinitely or as per the settings (see additional information).
System information
- OS: macOS, iOS
- Version of supabase-swift: 2.15.2
Additional context
We have tried to increase the rate limits: Token Refreshes = 184 Token Verifications = 100 Sign ups and sign ins = 50 Time-box user sessions = 0 Inactivity timeout = 0 Access Tokens expiry time = 10800 (3 hours) Refresh token reuse interval = 30
Let me know if the settings can cause this.
This is likely due to a bug in your application that refreshes the token in parallel. Please see this doc for more information about such issues: https://supabase.com/docs/guides/auth/sessions#what-is-refresh-token-reuse-detection-and-what-does-it-protect-from
Also be aware that if the user signed out from another device or platform (web) they are signed out from everywhere. You can control this with a parameter.
Thanks for the quick reply. We have called refreshSession when an invalid_grant error was received. Can this cause the problem? I have removed that and also set the scope of signOut to global. Though I don't think this was the issue, but could have added some confusion. Will monitor this the next few days.
Ok, after about 2-3 days of testing with the changes, I'm still getting logged out automatically on macOS (2x in the last 2-3 days) with the error message:
api(Auth.AuthError.APIError(msg: nil, code: nil, error: Optional("invalid_grant"), errorDescription: Optional("Invalid Refresh Token: Already Used"), weakPassword: nil))
I haven't hit the logout button anywhere during testing.
Any ideas how to fix it or if it's a bug in the library?
Hi @EduardMe
do you have any extension also using Supabase? Widgets...
Just checked, we have a Widget, but it accesses only some userDefaults.
We have shortcuts that call the CloudKit sync, possibly Supabase, however, I'm not using them.
Today I got logged out again on macOS. I use it less on iOS, but there I didn't get logged out since testing. So it's just on macOS since the last 3 days.
On macOS I often run into some internet connection issues. It gets disconnected for a second. Might this influence it?
Thanks @EduardMe
I'm investigating it, and will post any updates I find on this issue.
I'm having the same issue since I updated my version of Supabase in my Xcode project. I don't do anything sophisticated in my app and the token settings are the default
@LucasAbijmil which version were you using before, which you wasn't having this issue?
@grdsdev Previously I was in 2.5.1, then it was when I switched to 2.13.3 that the bug seems to have been introduced
@grdsdev I have some idea, but I'm not quite sure. Is it possible that if this gets called in parallel or when the Supabase library refreshes the session in the background and we call it at the same time it creates an issue?
We are using this func in Swift to check if the user is logged in:
func session(completion: @escaping (Result<Session, Error>) -> Void) {
Task {
do {
if let currentSession = supabase.auth.currentSession, !currentSession.isExpired {
completion(.success(currentSession))
return
}
let session = try await supabase.auth.session
completion(.success(session))
} catch {
completion(.failure(error))
}
}
}
@EduardMe I don't think that is causing the issue, but your check is redundant, when you call the try await supabase.auth.session it already validates if session is expired, if it isn't, it returns it without refreshing, just like your first if.
Still looking at this issue...
This might be the bug I'm experiencing since December, the day I implemented Sign In With Apple in my SwiftUI App. It seems to happen randomly. When I develop the app, it can happen 3-4 times in an afternoon because I launch my app often, but other than that yes it seems to be happening every 2-3 days. It basically logs my users out from time to time.
Ideally, I'd like the user to just login the first time and then never again unless he stops using the app for a prolonged period, or he deletes the app and reinstalls it or upgrades to a new phone.
api(Auth.AuthError.APIError(msg: nil, code: nil, error: Optional("invalid_grant"), errorDescription: Optional("Invalid Refresh Token: Refresh Token Not Found"), weakPassword: nil))
I've indeed seen that happen quite a lot of times. That along with "Bad ID token". I can't put my finger on it, but there's something not working well with Supabase Auth in Swift.
FYI I never call the refreshSession() function, because the Docs says :
try await supabase.auth.session // Returns the session, refreshing it if necessary
I tried using the refreshSession() function and the bug was still there, so I removed it in my production code.
For us the error is usually Invalid Refresh Token: Already Used. That's the only bigger roadblock for us right now. The rest works great. We haven't use the API for that long, so I haven't seen a version where it worked for more than 2-3 days.
@grdsdev Could you find anything? We are a bit struggling with this, or is there anything we can do temporarily to reduce the logouts?
2024-09-10T17:37:30+0800 supabase: Response: Status code: 400 Content-Length: 83 Body: { "error" : "invalid_grant", "error_description" : "Invalid Refresh Token: Already Used" }
same here
Hey π Could we please bring more attention to this issue? Itβs a crucial part of the core workflow for many apps, and addressing it promptly would be greatly appreciated.
I receive an error with 2.14.0. Sometimes the app works less than a day.
2024-09-12T19:14:44-0700 error BroadcastReducer : [n/a] Failed to create realtime channel: api(Auth.AuthError.APIError(msg: nil, code: nil, error: Optional("invalid_grant"), errorDescription: Optional("Invalid Refresh Token: Already Used"), weakPassword: nil))
I have my actor that updates the current session that might be accessed from different places:
func session(from: URL? = nil) async throws -> Session {
if let task {
return try await task.value
}
task = Task {
defer { self.task = nil }
if let from {
return try await supabase.auth.session(from: from)
} else {
return try await supabase.auth.session
}
}
return try await task!.value
}
@EduardMe Have you managed to codesign the app with SDK 2.14+? Have you granted Keychain Sharing capabilities to the app?
SDK v2.8.0 has same the problem
2024-09-13T17:49:05-0700 debug BroadcastReducer : [n/a] Channel status changed: unsubscribing
2024-09-13T18:07:28-0700 warning BackendService : [n/a] Attempt 1 failed: api(Auth.AuthError.APIError(msg: nil, code: nil, error: Optional("invalid_grant"), errorDescription: Optional("Invalid Refresh Token: Refresh Token Not Found"), weakPassword: nil))
@grdsdev i used v2.0 before, if i am not mistaken, and it was fine. however, not sure that it has realtime. i need realtime that is why i decided to upgrade at all
AFAIK the bug's been there since at least December 2023 because this is when I started designing my app and it's been a day 1 bug for me.
Yes, this requires more attention, it is currently my only bug with Supabase, but it's a big one. It's really annoying my users.
I only implemented Sign In With Apple FYI, in Swift/SwiftUI.
Also, logging in with a device seems to log out other devices. I always have to login between the Simulator and my real iPhone.
These types of errors are hard to track down the cause. If anyone is able to reliably reproduce the issue, sharing the steps would greatly help us track down what might be the cause.
@EduardMe Have you managed to codesign the app with SDK 2.14+? Have you granted
Keychain Sharingcapabilities to the app?
@rebryk Yes, I have granted Keychain Sharing, but I did this long before, didn't even know I needed to do this. Haven't done anything else to set things up. Just had to bump up the minimum version for macOS and iOS builds.
@dshukertjr i can add supabase logger and wait for a few hours when it actually will happen. will it help you?
@rebryk That might help us out π
@dshukertjr @grdsdev I have logs. What email should I use to send them?
@rebryk Can you just hide what you need to hide and paste it here?
@dshukertjr i don't know how to do that with .pulse log
@dshukertjr in my case it happens with realtime.
2024-09-16T04:51:13Z [verbose] [Realtime] [Realtime/WebSocketClient.send(_:):111] Sending message: {"event":"heartbeat","payload":{},"topic":"phoenix","ref":"233"}
2024-09-16T04:51:13Z [verbose] [Realtime] [Realtime/WebSocketClient.receive():88] Received message: {"ref":"233","event":"phx_reply","payload":{"status":"ok","response":{}},"topic":"phoenix"}
2024-09-16T04:51:13Z [debug] [Realtime] [Realtime/RealtimeClientV2.onMessage(_:):328] heartbeat received
2024-09-16T04:51:16Z [verbose] [Realtime] [Realtime/WebSocketClient.receive():88] Received message: {"ref":null,"event":"system","payload":{"message":"Access token has expired: [message: \"Invalid token\", claim: \"exp\", claim_val: 1726462276]","status":"error","extension":"system","channel":"b0412dd4-fffc-4058-ab0d-2531f88fd6d9"},"topic":"realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9"}
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeClientV2.onMessage(_:):331] Received event system for channel realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeChannelV2.onMessage(_:):210] Received message without event type: RealtimeMessageV2(joinRef: nil, ref: nil, topic: "realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9", event: "system", payload: ["channel": b0412dd4-fffc-4058-ab0d-2531f88fd6d9, "message": Access token has expired: [message: "Invalid token", claim: "exp", claim_val: 1726462276], "status": error, "extension": system])
2024-09-16T04:51:16Z [verbose] [Realtime] [Realtime/WebSocketClient.receive():88] Received message: {"ref":"1","event":"phx_close","payload":{},"topic":"realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9"}
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeClientV2.onMessage(_:):331] Received event phx_close for channel realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeChannelV2.unsubscribe():116] unsubscribing from channel realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeChannelV2.onPresenceChange(_:):350] Removing presence callback with id: 1
2024-09-16T04:51:16Z [verbose] [Realtime] [Realtime/WebSocketClient.send(_:):111] Sending message: {"join_ref":"1","topic":"realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9","ref":"234","payload":{},"event":"phx_leave"}
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeChannelV2.onBroadcast(event:callback:):447] Removing broadcast callback with id: 2
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeClientV2.removeChannel(_:):229] No more subscribed channel in socket
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeClientV2.disconnect():302] Closing WebSocket connection
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeChannelV2.onBroadcast(event:callback:):447] Removing broadcast callback with id: 3
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeChannelV2.onMessage(_:):322] Unsubscribed from channel realtime:b0412dd4-fffc-4058-ab0d-2531f88fd6d9
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeClientV2.removeChannel(_:):229] No more subscribed channel in socket
2024-09-16T04:51:16Z [debug] [Realtime] [Realtime/RealtimeClientV2.disconnect():302] Closing WebSocket connection
2024-09-16T04:51:16Z [verbose] [_Helpers] [_Helpers/Request.rawFetch(_:):25] Request [2773CEC7-44CE-47DB-ADC8-B71579E7660C]: POST https://<PROJECT_ID>.supabase.co/auth/v1/token?grant_type=refresh_token
Body: {
"refresh_token" : "Cl9XpqzeHzUxSuee1Bbzdg"
}
2024-09-16T04:51:16Z [verbose] [_Helpers] [_Helpers/Request.rawFetch(_:):45] Response [2773CEC7-44CE-47DB-ADC8-B71579E7660C]: Status code: 400 Content-Length: 94
Body: {
"error" : "invalid_grant",
"error_description" : "Invalid Refresh Token: Refresh Token Not Found"
}
Also have been experiencing this for quite some time in my SwiftUI app. Completely breaking functionality for users, so would appreciate someone to look at this! This is probably the most broken part of this SDK that affects the most users of it, so would appreciate prioritization of it