0008 - Adopt WorkOS Authentication and Application-Owned Identity
Status: Accepted Date: 2026-08-07 Author: Manuel Nucci
Context
The app currently authenticates Google and Apple users through Supabase. The API validates Supabase JWTs, and the domain User identifier is coupled to the authentication provider's token subject. Team roles are stored in PostgreSQL and vary by membership: one user can have different roles in different teams.
The backend-platform migration removes Supabase, but authentication must retain the custom FPC experience, Google and Apple login, web and Capacitor support, safe identity linking, and server-authoritative team authorization. FPC also wants the option to replace the authentication provider later without rewriting domain primary keys.
Decision
Adopt WorkOS AuthKit for authentication while keeping application identity and authorization in FPC.
- Keep the existing custom Google and Apple entry controls.
- Use WorkOS authorization flows with PKCE, the system browser, and verified web or Capacitor deep-link redirects. FPC never collects provider credentials.
- Validate WorkOS access tokens in the API against WorkOS signing keys and expected issuer, audience, expiry, and session claims.
- Generate
User.idas an application-owned UUID. - Add an authentication-identity mapping keyed by issuer and subject that points to the FPC user. Provider subjects and email addresses are not domain primary keys.
- Treat email and profile claims as synchronized attributes, not identity keys.
- Make login-time identity synchronization idempotent.
- Consume signed WorkOS user events through a durable inbox that deduplicates event IDs and tolerates retries or out-of-order delivery.
- Keep team membership and role authorization in PostgreSQL. WorkOS Organizations and WorkOS RBAC are out of scope for the MVP.
- Return current per-team roles from the authenticated session-bootstrap interface. The UI uses them to render affordances, while the API rechecks current membership for every protected operation.
- Keep any future global FPC administration permission separate from team membership and WorkOS organization roles.
- Put token verification behind a small interface with one production adapter, WorkOS, and a fake adapter for tests. No Supabase verifier is built. See "Compatibility window" below.
Consequences
Positive
- Authentication-provider identifiers no longer control FPC domain identity.
- Google and Apple can be linked without creating domain records whose primary keys depend on either provider.
- Team roles remain current, contextual, and server-authoritative instead of being copied into long-lived JWTs.
- The custom FPC entry experience remains intact across web and Capacitor.
- The verifier seam keeps WorkOS replaceable and lets authentication behavior be tested through a fake adapter rather than against a live provider.
Negative
- Authentication now depends on an external WorkOS service in addition to GCP and Vercel.
- PKCE, system-browser return handling, secure token storage, refresh behavior, and deep links require coordinated web and mobile testing.
- Introducing application-owned IDs requires schema and relationship changes before the WorkOS cutover.
- Webhooks add signature verification, replay protection, retries, and event-ordering concerns.
- Team-role changes are not pushed instantly to every open client during the MVP.
Mitigations
- Put provider verification behind a small interface and test both production and fake adapters through the same seam.
- Cut the API and the web client over to WorkOS in the same phase, so no request ever needs to carry a token this API cannot verify.
- Use database uniqueness constraints and transactions for identity creation and linking.
- Store mobile tokens only through the platform's secure-storage mechanism; never persist refresh credentials in ordinary web storage.
- Refresh membership state during session bootstrap, after role-changing mutations, and on normal query refocus. Add realtime role propagation only when evidence requires it.
- Enforce authorization in the API regardless of what the UI renders.
Compatibility window
Amended 2026-08-11. As originally accepted, this ADR required a temporary dual-verifier seam carrying both a Supabase and a WorkOS adapter, so the API could ship before the app changed providers. That requirement is withdrawn: the API and the web client now cut over to WorkOS together, and no Supabase verifier is built at any point.
The original rationale no longer applies. Nothing would ever present a Supabase token to the migrated API. The development database is reset rather than backfilled, so no established sessions survive the cutover; production has no users, because its database stays stopped until MVP launch; and no Capacitor build has been distributed. A Supabase adapter would therefore be dead code carrying a metrics and testing obligation, and its deletion phase would remove code that never served a request.
The cost of the withdrawal is recorded rather than hidden: the migration's stated rollback for this work was to keep the Supabase verifier selected for existing traffic. That option no longer exists. Rollback is now redeploying the previous Cloud Run revision together with the previous web deployment, which is acceptable only because both environments hold disposable data.
Amending an accepted ADR is a deliberate exception to the immutability convention in docs/CLAUDE.md, taken here at the owner's direction because three separate statements in this document — a decision, a consequence, and a mitigation — would otherwise instruct a reader to build an adapter the project has decided not to build. Every other decision in this ADR stands unchanged, and the end state it describes is unaffected.