Repository navigation
architecture
Four decisions shape this library. Each exists because of a specific failure.
The library installs Auth and ComposeAuth onto the client your app already has, through the
SupabaseExtrasProvider seam.
Build a second client to get Auth and your generated API bindings hold a different instance
carrying no session — so every RLS-gated call resolves no auth.uid(). It compiles cleanly,
passes static checks, and fails only at runtime against real policies. Removing the opportunity
is more reliable than documenting the hazard.
On Android the native-Google onResult(Success) callback frequently never fires even though the
id_token exchange succeeded and the session landed. This was observed on-device: an app sat on
"Signing in…" while Supabase logged Authenticated.
So KmpSupabaseAuthViewModel derives isSignedIn from KmpSupabaseAuthRepository.isSignedIn, and uses
onResult only for Error / NetworkError / ClosedByUser, which do fire reliably.
KmpSupabaseAuthViewModelTest.reachesSignedInFromTheSessionAloneWithNoSuccessCallback pins this: it
reaches signed-in with no callback invoked at all. Rewire the ViewModel to trust the callback and
that test fails.
| Owns | Who |
|---|---|
| Supabase session — tokens, provider identity, status |
library (KmpSupabaseAuthSessionStore) |
| User profile data, app preferences | your app, untouched |
| Attaching the JWT to API calls |
your app's existing AuthHeaderBridge
|
| Logout fan-out |
your app's UserLogoutManager / StoreRegistry
|
KmpSupabaseAuthUser carries identity only — id, email, display name, avatar, provider, anonymous flag. The
library never writes to your preferences store and defines no profile type, so there is never a
second owner of state you already own.
An earlier design backed it with Store5. That was wrong, for three reasons:
- Memory only. GoTrue already persists and refreshes. Caching again would show a signed-in user after the real token had expired.
- Not in the logout purge. This store is what tells the app a logout happened; purging it would clear the stream the app reads to notice the purge.
- Push, not fetch. GoTrue emits session changes. There is no fetcher to write, and inventing one creates a second read path for one piece of state.
Dropping Store5 also lifted the headless module from 8 targets to 17.
No consumer writes a platform-conditional import to make sign-in work.
Android needs a real OAuth-redirect receiver, so the library ships KmpSupabaseAuthCallbackActivity
and registers it by manifest merge — but the class is internal, armed from commonMain through
an internal expect fun registerAuthCallbackClient. Every non-Android target shares a single
no-op actual.
This matters beyond tidiness. An app in this workspace shipped the Apple button enabled on Android, where there is no native Apple provider, so the flow fell back to a browser redirect — with no intent-filter anywhere to return to. The flow was unfinishable and the build was silent.
cmp-supabase-auth 17 targets config · client · session store · repository · Koin DI
cmp-supabase-auth-compose 6 targets remember* wrappers · buttons · login screen · ViewModel
sample-app — runnable proof, not published
The split is forced, not stylistic: the Compose compiler plugin applies to every compilation in a
module and fails on any target lacking the runtime, so a Compose-bearing module can never match
its headless sibling's matrix. Keeping compose-auth out of the headless module is what buys the
17.
This library does not reimplement native sign-in. It builds on:
-
ComposeAuth—rememberSignInWithGoogle/rememberSignInWithApple,NativeSignInResult,LINK_IDENTITY_CALLBACK -
ComposeAuthUI—ProviderButtonContent, form fields, validators (flagged experimental upstream; the opt-in is contained to one file here)
What this library adds is the part those do not cover: DI, a provider-neutral domain surface, the session-truth ViewModel, the token bridge, and the Android callback receiver.
-
Getting started
-
Provider setup
-
Modules
-
Reference
-
Project