Authentication & Secrets
Owner: Ivy, Founder, Koda Digital · Effective: 2026-09-03 · Updated: 2026-09-27
1. Transport security
All traffic is HTTPS. The backend is served on *.cloudfunctions.net / *.run.app with
Google-managed certificates (TLS 1.2+, automatic renewal). Plaid endpoints use Plaid's
certificates. The Android app does not permit cleartext traffic in release builds.
2. User authentication
Every request from the app to the backend is made as a signed-in user. Nothing about the app build itself grants access.
- Sign-in uses Firebase Authentication with Identity Platform: an email and password, plus a time-based code from an authenticator app (TOTP). Once the authenticator is enrolled, a password alone cannot open a session.
- Every API request carries a Firebase ID token (
Authorization: Bearer). The backend middleware (functions/src/firebaseAuth.ts) rejects the request unless the token:- verifies and has not been revoked (checked on every request),
- belongs to a verified email address,
- was issued to a session that completed the authenticator step.
- Accounts and isolation. People who receive the app create their own account in it
(email, password, then email verification and an authenticator). The Operator can also add
people in the Firebase console. There is no list of permitted users to maintain: the uid
in the verified token is the only key the backend uses to reach data, so every account
reaches only its own tree (
users/{uid}) and can never read another person's. Sign-up can be closed at any time with the Firebase console's Enable create (sign-up) switch. - On the device, the session is stored in the iOS Keychain / Android Keystore via
expo-secure-store, marked this-device-only so it is excluded from backups and cannot be restored onto another phone. A device biometric lock additionally guards an open session. - Revocation: "Sign out of every device" (
POST /revoke-sessions) revokes all sessions immediately; the next request from any device is refused. Without a signed-in device, the Operator revokes through the Admin SDK (revokeRefreshTokens), or disables the user in Firebase Authentication, which refuses every request for as long as it stays disabled.
3. Tokens and credentials in use
| Credential | Purpose | Where it lives | Rotation / expiry |
|---|---|---|---|
| Each user's Koda Finance account (email, password, TOTP secret) | App sign-in | Password chosen by the user; TOTP secret in that user's authenticator app | Password on suspicion of compromise; TOTP re-enrolled if the authenticator device is lost |
| Firebase ID token | App → backend, per request | App memory | Expires after 1 hour; refreshed automatically |
| Firebase refresh token | Keeps the device signed in | Device Keychain / Keystore (this device only) | Revoked by sign-out, "sign out of every device", password change, or disabling the user |
Plaid client_id / secret |
Backend → Plaid | Google Secret Manager; injected into the function at runtime; never in git or the app | On suspicion of leak, or annually |
| Plaid access tokens (per Item) | Backend → Plaid data | Firestore users/{uid}/plaid_tokens/{itemId}, under the account that linked it; readable only by the backend service account |
Revoked via /item/remove on unlink |
| Plaid link tokens | One-time, short-lived, created server-side to open Link | Passed to the app and consumed immediately; expire in ≤4 h per Plaid | n/a |
| Google service-account credentials | Backend → Firestore/Secret Manager | Google-managed, short-lived, no user-managed keys | Automatic |
| Operator Google account | Console/CLI access | Password manager + 2SV passkey/security key | Per Google policy |
The Firebase web configuration compiled into the app (API key, project ID, app ID) identifies the project and is not a credential: it grants nothing without a signed-in account that has completed the authenticator step, and that account sees only its own data.
Bank credentials are never handled by Koda Finance code — they are entered only within Plaid Link's hosted UI.
4. Rules
- No secret may be committed to source control (
.env,.env.local, keystores are gitignored;functions/.envholds only non-secret configuration). - Secrets are accessed by name from Secret Manager at deploy time (
defineSecret), never copied into environment files in production. - Superseded secret versions are destroyed at the next access review.
- Release APKs are signed with a dedicated keystore kept outside the repository.
5. History and planned improvement
Until 2026-09-12 the app authenticated to the backend with a single shared key compiled into the app bundle. Anyone holding a copy of the app could extract it and call every endpoint — including unlinking institutions — with no revocation short of rotating the key and rebuilding every install. It was replaced by the per-user authentication in §2, and the shared key secret is to be destroyed.
Planned improvement: Firebase App Check (Play Integrity / App Attest), so that requests must also come from a genuine, unmodified install. This adds to user authentication; it does not replace it.