Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-refreshing of tokens can fail and never restart again #630

Open
2 tasks done
vojtabohm opened this issue Dec 30, 2024 · 6 comments
Open
2 tasks done

Auto-refreshing of tokens can fail and never restart again #630

vojtabohm opened this issue Dec 30, 2024 · 6 comments
Labels
bug Something isn't working

Comments

@vojtabohm
Copy link

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

When the refresh of a token fails (e.g. the app went into background or the refresh API call fails for plethora of reasons) the library silently fails and never refreshes the token again.

_ = try? await refreshSession(refreshedSession.refreshToken) (in the scheduleNextTokenRefresh function).

To Reproduce

Set your phone into airplane mode, wait for refresh to fail. It will never refresh again since scheduleNextTokenRefresh is called recursively from refreshSession that can fail, it will be stopped.

If you follow the docs recommendation

It's best practice and highly recommended to extract the access token (JWT) and store it in memory for further use in your application.

basically something like this:

func authToken() async throws -> String {
        if let token = _authToken {
            return token
        }
        
        return try await supabase.auth.session.accessToken
    }
... inside a task ...

for await (event, session) in supabase.auth.authStateChanges {
                print("Auth change:", event, session)
                switch event {
                case .initialSession, .signedIn, .tokenRefreshed:
                    // Store _authToken in memory, as per Supabase's docs recommendation
                    _authToken = session?.accessToken

... some other code ...

and you rely on authStateChanges to always give you a token, then you can run into issue of token failing to refresh and you're forever stuck with the old one.

Expected behavior

It would be great to incorporate some sort of retry mechanism. I worked around this with storing the entire session, not just the token and manually refreshing if it's expired.

func authToken() async throws -> String {
        if let session = _authSession, !session.isExpired {
            return session.accessToken
        } else {
            let newSession = try await supabase.auth.session
            _authSession = newSession
            
            return newSession.accessToken
        }
    }

I think this is a good work-around so if a retry mechanism is too complicated (which is understandable), perhaps update of the docs could be in order.

@vojtabohm vojtabohm added the bug Something isn't working label Dec 30, 2024
@AlanDuong07
Copy link

We are also experiencing a ridiculous amount of people that log into our app, use it every day, but on the third day or so, their session is completely gone. This is breaking their ability to even utilize our app.

We rely on this code that initializes on app start. We then utilize the authJWT to authenticate and identify our users on every backend API call. But the session becomes nil, and thus everything is set to nil.

for await (event, session) in await supabase.auth.authStateChanges {
self.authJWT = session?.accessToken
self.supabaseUserId = session?.user.id.uuidString
self.email = session?.user.email ?? ""
}

Please, can we have eyes on this issue? This is a high severity issue.

@grdsdev
Copy link
Collaborator

grdsdev commented Jan 3, 2025

Hi, thanks for reporting this issue, I'll be working on it ASAP.

In the meanwhile, if anyone has a fix for it, feel free to PR it and I'll gladly review it.

@grdsdev
Copy link
Collaborator

grdsdev commented Jan 6, 2025

Hi @vojtabohm and @AlanDuong07 could you confirm which version of the library are you using?

@grdsdev
Copy link
Collaborator

grdsdev commented Jan 6, 2025

@vojtabohm I wasn't able to reproduce by using airplane mode and using latest version of the library, please note that there are no scheduleNextTokenRefresh in the last version, so I assume you're using an outdated version.

@AlanDuong07
Copy link

AlanDuong07 commented Jan 7, 2025

Hi @vojtabohm and @AlanDuong07 could you confirm which version of the library are you using?

Hi, we were on version 2.20.4 I believe. I have since upgraded to the latest version. Though looking through the update notes, I don't see many changes regarding dropped sessions. I will let you know if the problem magically goes away with the latest update.

Could I please get help with whether my use of Supabase Swift is correct @grdsdev?

Only with the usual initialization code for Supabase Swift SDK, I initialize on app launch, this code that listens to auth state:

 for await (event, session) in await supabase.auth.authStateChanges {
      if let session = session {
          self.authJWT = session.accessToken // I save the authJWT here.
          self.supabaseUserId = session.user.id.uuidString
          self.email = session.user.email ?? ""
       }
}

On user sign in with apple / google, I use
try await supabase.auth.signInWithIdToken()

That should trigger the authStateChanges listener to have a session, so I can save the authJWT in memory as the docs recommend. I reassign the authJWT when new session data comes in.

Yet after a few days or so, users have an auth JWT of nil! The entire session, in fact, becomes undefined, because both "self.authJWT and self.supabaseUserId" become nil and stay nil. How could this be happening?

This is the only code I use to handle Supabase Auth. Isn't it that simple, or am I missing something?

@grdsdev
Copy link
Collaborator

grdsdev commented Jan 7, 2025

Your usage of the SDK seems fine, could you attach a logger on initialization and send logs over?

You can use the example:

          struct AppLogger: SupabaseLogger {
            func log(message: SupabaseLogMessage) {
              print(message.description)
            }
          }

          let client = SupabaseClient(
            supabaseURL: URL(string: "https://xyzcompany.supabase.co")!,
            supabaseKey: "public-anon-key",
            options: SupabaseClientOptions(
              global: SupabaseClientOptions.GlobalOptions(
                logger: AppLogger()
              )
            )
          )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants