supabase-swift icon indicating copy to clipboard operation
supabase-swift copied to clipboard

"Refresh Token Not Found" = automatic logout after 2-3 days after login

Open EduardMe opened this issue 1 year ago β€’ 29 comments

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.

EduardMe avatar Aug 01 '24 18:08 EduardMe

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

hf avatar Aug 02 '24 11:08 hf

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.

hf avatar Aug 02 '24 11:08 hf

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.

EduardMe avatar Aug 02 '24 14:08 EduardMe

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?

EduardMe avatar Aug 05 '24 16:08 EduardMe

Hi @EduardMe

do you have any extension also using Supabase? Widgets...

grdsdev avatar Aug 05 '24 17:08 grdsdev

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?

EduardMe avatar Aug 06 '24 17:08 EduardMe

Thanks @EduardMe

I'm investigating it, and will post any updates I find on this issue.

grdsdev avatar Aug 07 '24 09:08 grdsdev

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 avatar Aug 07 '24 13:08 LucasAbijmil

@LucasAbijmil which version were you using before, which you wasn't having this issue?

grdsdev avatar Aug 07 '24 13:08 grdsdev

@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

LucasAbijmil avatar Aug 07 '24 15:08 LucasAbijmil

@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 avatar Aug 09 '24 14:08 EduardMe

@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...

grdsdev avatar Aug 09 '24 20:08 grdsdev

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.

dpelletier2017 avatar Aug 19 '24 20:08 dpelletier2017

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.

EduardMe avatar Aug 22 '24 15:08 EduardMe

@grdsdev Could you find anything? We are a bit struggling with this, or is there anything we can do temporarily to reduce the logouts?

EduardMe avatar Aug 28 '24 19:08 EduardMe

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

iBenjamin avatar Sep 10 '24 09:09 iBenjamin

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.

pedrommcarrasco avatar Sep 13 '24 22:09 pedrommcarrasco

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
  }

rebryk avatar Sep 13 '24 22:09 rebryk

@EduardMe Have you managed to codesign the app with SDK 2.14+? Have you granted Keychain Sharing capabilities to the app?

rebryk avatar Sep 13 '24 23:09 rebryk

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

rebryk avatar Sep 14 '24 01:09 rebryk

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.

dpelletier2017 avatar Sep 14 '24 09:09 dpelletier2017

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.

dshukertjr avatar Sep 14 '24 12:09 dshukertjr

@EduardMe Have you managed to codesign the app with SDK 2.14+? Have you granted Keychain Sharing capabilities 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.

EduardMe avatar Sep 14 '24 15:09 EduardMe

@dshukertjr i can add supabase logger and wait for a few hours when it actually will happen. will it help you?

rebryk avatar Sep 15 '24 02:09 rebryk

@rebryk That might help us out πŸ‘

dshukertjr avatar Sep 20 '24 02:09 dshukertjr

@dshukertjr @grdsdev I have logs. What email should I use to send them?

rebryk avatar Sep 21 '24 03:09 rebryk

@rebryk Can you just hide what you need to hide and paste it here?

dshukertjr avatar Sep 22 '24 11:09 dshukertjr

@dshukertjr i don't know how to do that with .pulse log

rebryk avatar Sep 24 '24 02:09 rebryk

@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"
}

rebryk avatar Sep 24 '24 02:09 rebryk

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

AlanDuong07 avatar Oct 08 '24 21:10 AlanDuong07