Authentication
The Android app uses Firebase Authentication for identity, then passes the Firebase ID token as a Bearer header to every backend request. The backend identifies the user from the verified token — there is no separate Scryon account system.
Auth gate
Key points:
AuthRepository.stateis aStateFlow<AuthState>; the gate is purely reactive.- The
LaunchedEffectis keyed onuidso it fires once per signed-in user — never on plain recompositions. requiresEmailVerificationistrueonly for thepasswordprovider. Google / Phone sign-in skips that step.
Sign-in flows
Email + password
- Sign-up sends the Firebase verification email once as part of
signUp(default template first → falls back toActionCodeSettingsif that fails). The outcome is captured inSignUpResultand read once byEmailVerificationViewModel— the screen does not fire a second send on first open (which used to hit Firebase rate limits). Subsequent resends are gated by a 60-second cooldown. - The user is held on
EmailVerificationScreenuntiluser.reload()reportsisEmailVerified. - Forgot password uses
FirebaseAuth.sendPasswordResetEmail.
Google
- Uses the Credential Manager API (the modern replacement for
GoogleSignInClient). - Requires
FIREBASE_WEB_CLIENT_IDinlocal.propertiesand proper SHA-1 fingerprints in Firebase Console.
Phone
- Supports SMS auto-retrieval. If Firebase calls
onVerificationCompletedon-device we skip the manual code step.
ID-token caching
FirebaseIdTokenProvider (singleton):
- Caches the latest Bearer token in memory for ~50 minutes.
- Serialises fetches behind a lock so parallel OkHttp calls never race on
getIdToken(true). - Listens for Firebase uid changes and clears the cache.
- Primes the cache right after a successful
signIn/signInWithGoogle. - Clears explicitly on
signOut().
The provider uses Tasks.await(...) rather than runBlocking so OkHttp's request threads can't deadlock.
401 retry
FirebaseAuthAuthenticator is attached to OkHttp via .authenticator(...). On a 401 it force-refreshes the token (getIdToken(true)) and retries the request once — covers the case where the cached token expired between fetch and server validation.
Settings → Auth diagnostics
A signed-in user can see:
- Their Firebase uid.
- The cached Bearer token (preview by default; Reveal full + Copy).
- A Refresh and Force refresh button.
This is the fastest path to diagnose 401s — the same token can be tested against the backend with curl:
curl https://api.scryon.app/api/users/me \
-H "X-API-Key: $SCRYON_API_KEY" \
-H "Authorization: Bearer $TOKEN"
Sign-out is destructive
AuthRepository.signOut() wipes every per-uid local store and clears the cached ID token before calling FirebaseAuth.signOut() so the lookups still resolve to the user's namespace:
InFlightUploadStoreIdempotencyKeyStoreUploadQueueStoreDismissedCallStoreCallRecordingPrefsCallContentCache(clearForUid(uid))FirebaseIdTokenProvidercache
After sign-out, the app effectively starts over with no per-user data on disk.
Account deletion
DELETE /api/users/me returns 204. The client then calls FirebaseAuth.user.delete() and signs out. If Firebase rejects the delete with RECENT_LOGIN_REQUIRED, the UI asks the user to sign out and back in, then retry.