Sessions
Once a user signs in, you get a session — proof they’re signed in, made of an
access token and a refresh token. The SessionManager looks after that session for
you: it holds the current session, refreshes the access token before it
expires, and tells your UI whenever the login state changes. So you never have
to juggle tokens or expiry timers by hand.
Why the refresh matters: the access token expires quickly (minutes), which limits the damage if it ever leaks. The longer-lived refresh token is used to get a new access token when the old one runs out — quietly, in the background, so the user stays signed in.
Observing session state
sessionState is a flow you can collect to react to login changes — show the home
screen when authenticated, show the sign-in screen when not. The four cases below
are the complete set of states.
sessionManager.sessionState.collect { state ->
when (state) {
is SessionState.Authenticated -> println("User: ${state.session.user.id}")
is SessionState.Expired -> println("Session expired, refreshing...")
SessionState.NotAuthenticated -> println("Signed out")
SessionState.Loading -> println("Loading...")
}
}A failed refresh does not automatically mean Expired. If the refresh fails
for a transient reason — no connectivity, a 5xx, or a 429 rate limit — the
refresh token is still valid, so the session stays Authenticated and the manager
retries shortly. It only flips to Expired on a genuine invalidation (an expired
or already-used refresh token). So Expired means “sign in again,” not “the
network hiccupped” — you don’t need to bounce users to the login screen on a flaky
connection.
Persisting across restarts
By default the session lives only in memory (InMemorySessionStorage), so it’s
gone when the app closes — the user would have to sign in again on every launch.
Persistence fixes that: write the session somewhere durable so it survives a
restart.
That “somewhere” is your choice. The SDK deliberately bundles no secure-storage
dependency, so it doesn’t force a particular one on you — instead you plug in the
right store for each platform: Keychain (Apple), EncryptedSharedPreferences or
DataStore (Android), localStorage (Web), a file (desktop). You implement three
simple string operations — get, set, remove — and the SDK handles turning the
session into and out of a string for you.
class KeychainStore : KeyValueStore {
override suspend fun get(key: String): String? = /* read */
override suspend fun set(key: String, value: String) { /* write */ }
override suspend fun remove(key: String) { /* delete */ }
}
val sessionManager = createSessionManager(
authClient = auth,
supabaseClient = client,
config = SessionConfig(storage = KeyValueSessionStorage(KeychainStore())),
)
// On launch: load the stored session and refresh it in one call.
// Note: restoreSession() always performs a token refresh (rotating the refresh
// token), so it spends one network round-trip rather than reusing a still-valid
// stored access token as-is.
sessionManager.restoreSession()Storage is intentionally pluggable — the SDK bundles no Keychain or EncryptedSharedPreferences dependency, so you choose exactly how sessions are stored.