Authentication
How sign-in, session persistence, token refresh, and sign-out flow through the auth system.
Overview
Section titled “Overview”Step-by-Step Details
Section titled “Step-by-Step Details”1. Sign-In Flow
Section titled “1. Sign-In Flow”Code: crates/infrastructure/supabase/src/auth/supabase_auth.rs
Supabase Auth supports multiple sign-in methods: OAuth (GitHub, Google), magic link (email), and email+password. The
Tauri command orchestrates the flow based on the method. After a successful auth exchange, the command receives
access_token and refresh_token from Supabase.
store_session is called with both tokens:
- Keychain persistence (best-effort):
keychain_setattempts to store each token in the OS keychain under servicecom.inklings.desktop. Keychain failures are logged aserror!(notwarn!) to make session persistence failures visible, but the operation continues — tokens are still cached in memory. - In-memory cache:
session.write()updates theRwLock<SessionStore>withArc<String>handles to both tokens. Subsequentget_sessioncalls read from this lock-protected in-memory store without touching the keychain.
After the session is stored, IdentityStore::store_identity persists the AuthUser (id, email, display_name) as a JSON
string under a separate keychain key. The identity survives across sessions independently of the token lifecycle.
2. Session Restore on Startup
Section titled “2. Session Restore on Startup”Code: SupabaseAuthRepository::new
On construction (called during AppState initialization), the repository immediately attempts to restore tokens from
the keychain:
let access_token = keychain_get(KEYCHAIN_ACCESS_TOKEN).map(Arc::new);let refresh_token = keychain_get(KEYCHAIN_REFRESH_TOKEN).map(Arc::new);If tokens are found, they are pre-loaded into the in-memory SessionStore. This means the first call to
ensure_valid_token after startup does not need to prompt the user to sign in, as long as the stored token has not
expired (or can be refreshed).
3. Token Expiry and Refresh
Section titled “3. Token Expiry and Refresh”Code: supabase_auth.rs — ensure_valid_token, refresh_session, is_jwt_expired
ensure_valid_token is called by any command that needs to make an authenticated request to Supabase.
Expiry check: is_jwt_expired decodes the JWT payload (base64url) and reads the exp claim. It does NOT verify the
signature — the Supabase API validates the signature server-side. A 30-second clock skew buffer is applied: tokens
expiring within 30 seconds are treated as already expired.
Refresh serialization: A dedicated tokio::sync::Mutex<()> (refresh_lock) serializes concurrent refresh attempts.
If two async tasks both see an expired token and call refresh_session concurrently:
- The first acquires the lock and performs the refresh
- The second blocks on
lock().awaituntil the first completes - When the second proceeds, it typically finds a now-valid token (though the current implementation still performs a refresh for the second caller — a future optimization could re-check expiry inside the lock)
Exponential backoff: refresh_failure_count (atomic) and last_refresh_failure track repeated refresh failures.
The backoff formula is 2^min(failure_count, 5) seconds, capping at 32 seconds. check_refresh_backoff is called
before acquiring refresh_lock and returns an AuthFailed error if within the backoff window. This prevents thundering
herd from many concurrent callers hammering a broken refresh endpoint.
After a successful refresh, reset_refresh_backoff clears both the counter and the last failure timestamp.
4. Sign-Out Flow
Section titled “4. Sign-Out Flow”Code: supabase_auth.rs — sign_out
Sign-out is intentionally tolerant of failures:
- Remote logout (best-effort):
POST /auth/v1/logoutis attempted with the current access token. The response is silently discarded — if the request fails (network error, token already invalid), sign-out still proceeds. - Session clear:
clear_sessiondeletes both keychain entries and clears the in-memorySessionStore. This is synchronous and always succeeds regardless of remote logout outcome. - Identity clear:
clear_identityremoves the user identity from the keychain.
After sign-out, any subsequent call to ensure_valid_token returns AuthError::NotAuthenticated.
5. Identity vs. Auth Separation
Section titled “5. Identity vs. Auth Separation”The system enforces a conceptual separation between two concerns:
| Concern | Type | Lifetime | Storage |
|---|---|---|---|
| Who is the user | IdentityStore / AuthUser | Permanent (survives sign-out option) | OS keychain, separate key |
| Do we have a valid token | AuthRepository / session | Session (cleared on sign-out) | OS keychain + in-memory cache |
This separation means the app can display the user’s name and identity locally without making any network calls, even when the auth session is expired. The identity is re-stored on each successful sign-in but cleared only when the user explicitly clears it.
6. In-Memory Repository (Tests / HTTP Bridge)
Section titled “6. In-Memory Repository (Tests / HTTP Bridge)”Code: SupabaseAuthRepository::new_in_memory
A non-keychain variant is used in tests and the HTTP bridge to avoid blocking on OS keychain authorization dialogs.
Identity is stored in RwLock<Option<AuthUser>> instead of the keychain. Token management works identically except
keychain calls are skipped.
Error Handling
Section titled “Error Handling”| Failure | Behavior |
|---|---|
| Keychain write fails on sign-in | error! logged; tokens still cached in memory; session will not survive restart |
| Keychain read fails on startup | warn! logged; session starts empty (user must sign in again) |
| JWT expired, refresh fails | AuthError::AuthFailed returned; exponential backoff activated |
| Refresh rate-limited (backoff active) | AuthError::AuthFailed("Token refresh rate-limited...") returned immediately |
| Remote logout fails (sign-out) | Silently discarded; local session cleared regardless |
| No session (unauthenticated command) | AuthError::NotAuthenticated returned to command layer |
| Lock poisoned (RwLock) | AuthError::Storage("Lock poisoned: ...") returned |
| Network error (refresh) | AuthError::Network { message, details } returned; failure recorded for backoff |
Related
Section titled “Related”- Auth System — Full auth system reference including OAuth flows, device management, and Supabase Auth configuration
- Sync System — Sync engine calls
ensure_valid_tokenbefore every push/pull cycle - MCP System — MCP bearer token is separate from auth session; managed independently
Was this page helpful?
Thanks for your feedback!