How to handle session storage with CMP
Hi there!
We started to use supabase-kt library in our Kotlin Multiplatform Project.
I am trying to find a bit more information about how the auth works behind the scenes.
Here is our useCase:
- When user opens the app, they are either redirected to Auth page or Login. This is based on the session status check obtained from the supabase sdk.
We are listening and collecting auth.sessionStatus in ViewModel which updates our state (simple flag if the user is logged in or not).
My questions are:
- How is the supabase sdk handling the session storage and its persistance, can/should this be configured?
If the application is longer than few minutes offline (no internet), the session status is is set to SessionStatus.RefreshFailure. This results in user log-out even though they never logged out manually. User is logged back when they are back online.
- How often is the sessionStatus flow emitted?
It seems like when the SessionStatus.Refresh failure is returned, it will try to refresh every 10s. Can this be configured somewhere?
- Do I need to do any token validation manually? - Such as saving token and refreshing it.
Thank you!
I would also appreciate some instructive resources related deeper understanding of the supabase-kt library.
18 Replies
Session management in general is managed in the supabase auth schema session table and i don't think the use of any one library will affect that. It is advised to not play around with such tables because it is managed by supabase and you might introduce changes that may cause problems over time if supabase updated the functionality or schemas in anyway.
i think at lease web app applications manage sessions simply by the frequency which the users interacts with supabase and it can be configured for pro users vis authentication -> sessions
however i do know that this can be different for other platforms and i think someone on desktop was saying the other day that things get logged out automatically if the user closes their app or something along those lines
How is the supabase sdk handling the session storage and its persistance, can/should this be configured?The session is stored in the shared preferences on Android via multiplatform settings. You can configure this in the Auth settings when installing the plugin. Whether you should depends on what you are doing and if you know what you are doing
How often is the sessionStatus flow emitted?Whenever there is an update (session refresh, sign out, expired session, user update, ...). Its a state flow, so there is an underlying property.
If the application is longer than few minutes offline (no internet), the session status is is set to SessionStatus.RefreshFailure. This results in user log-out even though they never logged out manually. User is logged back when they are back online.This has been changed in the newest versions, the session will still be active until it expires, only then the status gets changed to refresh failure.
Do I need to do any token validation manually? - Such as saving token and refreshing it.No.
@Jan It seems like the frequency of the SessionStatus.RefreshFailure can be changed in the auth config via the "retryDelay" property as well. Thanks for pointing this option out.
SessionStatus.RefreshFailure is returned if the session expired. Based on my tests, the session is considered as expired after 60 min. I guess that's the default value of the access token lifespan. I wonder, based on the documentation, the session starts when user sign in, and by default, last indefinetely. Why should I care about the SessionStatus.RefreshFailure then? Or do I mix apples with pears?
Also, when I set the Auth settings "alwaysAutoRefresh" property to false, the flow always emmits SessionStatus.Authenticated, doesn't matter if 60 minutes or a few days passed. In this case, even if the user is deleted from the DB, the client's session will stay as "SessionStatus.Authenticated". If I manually call logout function, then the session will be changed to SessionStatus.NotAuthenticated.
What we are trying to achieve is that the user log in via the client (mobile app) and stay log in until manually call logout. User can CRUD tables accordingly.
It seems like the frequency of the SessionStatus.RefreshFailure can be changed in the auth config via the "retryDelay" property as well. Thanks for pointing this option out.You could interpret it that way; its just that if the session expired and a refresh attempt failed, a new status is emitted each time
Based on my tests, the session is considered as expired after 60 min.The session is not considered expired, it is expired. You cannot use it for requests after that without refreshing it. You can configure this duration in your Supabase dashboard.
I wonder, based on the documentation, the session starts when user sign in, and by default, last indefinetely.I don't know where you read that it lasts indefinitely. You could say after signing in you as an app don't have to do anything auth related anymore, but the library takes care of refreshing the session in the background
What we are trying to achieve is that the user log in via the client (mobile app) and stay log in until manually call logout.RefreshFailure does not mean the user is logged out, this just means the session expired and the client could not refresh it yet. Any request with that expired session will fail, thats why there is a separate status. After a successful refresh, the Authenticated status will come back. It will never be deleted or anything like that.
The session is not considered expired, it is expired. You cannot use it for requests after that without refreshing it. You can configure this duration in your Supabase dashboard.How so? If I let the client run in the offline mode and set the "alwaysAutoRefresh" to false, the client will stay logged in and the library returns SessionStatus.Authenticated. Even if the local session is long time after the expiration (ex. set time to the future). If I delete the user from the DB while the client is offline and put the client back online, the flow still returns the SessionStatus.Authenticated even though the user doesn't exists in the db anymore. If I call auth.signOut(SignOutScope.Local), then the flow will trigger with SessionStatus.NotAuthenticated.
I don't know where you read that it lasts indefinitely...https://supabase.com/docs/guides/auth/sessions#what-is-a-session
You could say after signing in you as an app don't have to do anything auth related anymore, but the library takes care of refreshing the session in the backgroundThat sounds really nice. I would be glad if I can read bit deeper about what is going on behind the scenes. Could you provide some references?
RefreshFailure does not mean the user is logged out, this just means the session expired and the client could not refresh it yet. Any request with that expired session will fail, thats why there is a separate status.Sorry for being cheeky, but when and how can this happen? You mentioned "library takes care of refreshing the session in the background". Could you give me some real world example when session is expired while request was made?
https://supabase.com/docs/guides/auth/sessions#what-is-a-sessionYea though the session may last indefinitely, the access token does not. And I don't know what you want to do with a session with an invalid access token, thats why I basically said the "session expires". Of course you can always refresh the session and get a new access token
How so? If I let the client run in the offline mode and set the "alwaysAutoRefresh" to false, the client will stay logged in and the library returns SessionStatus.Authenticated. Even if the local session is long time after the expiration (ex. set time to the future). If I delete the user from the DB while the client is offline and put the client back online, the flow still returns the SessionStatus.Authenticated even though the user doesn't exists in the db anymore.I don't know if I just understand this wrong, but what do you mean? If you sign in, Supabase sends over the session data and the time after which the session (or more correctly the access token) expires. After this time, you basically have an useless access token, so you are basically unauthanticated and can no longer make authenticated requests. Just because you disabled all that and maybe deleted the user, it doesn't change the fact that the stored session is still useless. If you try to make any request with that session/access token, it will fail.
If I call auth.signOut(SignOutScope.Local), then the flow will trigger with SessionStatus.NotAuthenticated.Yea that is expected? Even though you might see an error in the debug logs because it will try to invalidate the session first.
That sounds really nice. I would be glad if I can read bit deeper about what is going on behind the scenes. Could you provide some references?There are no docs for the internals, you can read through the code.
Sorry for being cheeky, but when and how can this happen? You mentioned "library takes care of refreshing the session in the background". Could you give me some real world example when session is expired while request was made?If your device has no internet and the session expired, the client will try to refresh it and it fails obviously. -> RefreshFailure (maybe I understand the question wrong?) And as I said any request with an expired access token will fail, thats why this status exists
There are no docs for the internals, you can read through the code.The short version is that, once you sign-in, the client imports the session, stores it on the disk (except for Native Linux), starts a coroutine to refresh the session 20% before the session expires, and repeats. This is started automatically once e.g. the app opens and the client is initialized This also handles updating access tokens for realtime channels
@TheRealJan Good insights!
We are trying to implement the supabase-kt sdk in our project and we try to understand the documentation and how to handle things most reliably.
SessionStatus.RefreshFailure -> Session is expired. The user cannot do anything. This will stay periodical and try to refresh the session. The frequency of the tries can be adjusted via auth "retryDelay" property. Is there any limit when trying to refresh stops? Ex. after 10 minutes or number of unsuccessful tries?
SessionStatus.NotAuthenticated -> Session is expired or the user has logged out. The user cannot do anything.
SessionStatus.Authenticated -> Session is valid and the library will try to refresh the session (token) 20% before the session expires. What is the frequency for that? Is it the same value as assigned with "retryDelay" when configuring the auth?
SessionStatus.Initializing -> Just data loading.
Could you elaborate and possibly extend assumptions above? Thanks !
@civomt Also remember the kt client is an open source community project versus a Supabase owned repository. You can skim issues and source code there.
This is a good summary of how access_tokens, refresh_tokens and the auth server sessions work. https://supabase.com/docs/guides/auth/sessions
What is the frequency for that? Is it the same value as assigned with "retryDelay" when configuring the auth?Yea that is why this property exists
Is there any limit when trying to refresh stops?No
NotAuthenticated -> Session is expired or the user has logged out.This status will not appear if the session expired
Okay thanks.
I let the sdk do their job.
Next thing. I am accessing the api abstraction in my example ViewModel in the init block. ViewModel is initialized via the Koin DI. If the app is offline and meanwhile the session expired, user opens the app from the BG and the api periodically returns status failure (as expected). However, now if the user puts app back into BG, the periodical update is still ongoing is this intended?
I also noticed that with every openning of the app from the BG while the session is expired and app still offline will trigger another concurrent periodical emission. This means - if you open the app 3 times from the BG you will get 3 different periodical emissions. How to prevent this?
And these are the logs what you will get when you put the app into offline mode and switch device time ex +1h - to be sure that the session was expired. Put the app 3 times into BG...You will get 3 different flows...if I would put the app 10 times into BG and coming back to foreground (while still offline), then there will be 10 different flows...
Now I switch the app back online and leave it in the BG. Emissions are still ongoing until some certain point when they stopped. They will stop only if the device is back online.
What I would expect is that if the app is offline and in the BG, there shouldn't be any periodical updates whatsoever as we can't reach the network anyway...
1. Can you give more details about your device and app
2. Can you enable debug logs in the supabase client builder and check them
3. It should not be possible that multiple session jobs start unless you somehow have multiple clients
4. The session job gets cancelled on android devices when going into BG
Hard to say anything with these logs
Here are the logs from the supabase itself:
Current behavior:
- If the session expire while the app is offline, the sdk will try to refresh the token as long as the app is alive (doesn't matter if in the foreground or in the background). If the app is open multiple times from the BG, multiple concurrent flows will launch.
Expected behavior(from my perspective):
- If the session is expired while the app is offline don't try to refresh the token as we cannot reach the cloud anyway. If the app is in the BG, don't run the refresh flow either. If the app comes from the BG to the FG - don't start new concurrent flow.
Possible issue: Auto-refresh is not cancelled when app is offline or in the BG.
How to replicate:
- Login
- Put device offline
- Wait 1h or your expiration time or change time of the device into future (app can be in the foreground or background doesn't matter) - the refreshing process is ongoing all the time
- Open the app - refreshing process starts
- Put app into BG
- Open the app - another refreshing process starts
Replicated on:
- Samsung Galaxy S22 Ultra Android 15 (Real device)
- Pixel Android 16 (Emulator)
Example implementation:
Any ideas?
@TheRealJan Supabase logs just confirmed the same behavior as I observed
Alright thanks, maybe its a bug happening when the client already tries to retry refresh the session before going into BG and fails, maybe something breaks the cancelling there, because it normally cancels the refresh job as you can see in the logs.
Will look at it.
Okay, so are contributing to the supbase-kt library?
You could say that I'm the creator/maintainer
Can you create an issue with this bug?
Nice! Let me know if you were able to replicate the issue or if you need more logs / testing.
I can do it. Do you want me to do it via github or is there any special procedure which needs to be followed here or on the supabase page?
Yea just on the supabase kt github so I don't forget it
issue created