Auth soft-gate
Goal
Let people browse and orient themselves before asking for an identity. Require a verified WorkOS identity only when Clubhouse League needs to create or mutate membership: creating a league, requesting to join, or redeeming an invite code.
Locked product contract
| Decision | Locked behavior |
|---|---|
| Provider | WorkOS AuthKit |
| Sign-in methods | Apple + Google + Magic Auth (email OTP) |
| Passwords | None. Clubhouse League does not offer password creation, sign-in, reset, or recovery. |
| Native flow | Open AuthKit in the system browser sheet and use PKCE for the authorization-code exchange. |
| Callback | clubhouseleague://auth/callback |
| Android package | com.pocketlabs.clubhouseleague |
| Soft gate | Browse and onboarding are allowed signed-out. Create league, request to join, and redeem invite code require authentication. |
| Profile before a gated action | workos_user_id, email, and display_name are required. avatar is optional. |
| API verification | The API verifies WorkOS JWTs against WorkOS JWKS before accepting authenticated requests. |
Actors
Guest → authenticated user → commissioner or manager.
Happy path
- A signed-out visitor opens the app, completes any onboarding orientation, and browses discoverable leagues without an auth prompt.
- The visitor selects Create league, Request to join, or Redeem invite code.
- The app preserves the pending action and its context, then presents WorkOS AuthKit in the system browser sheet.
- The visitor signs in with Apple, Google, or Magic Auth (email OTP). No password path is shown or accepted.
- AuthKit returns to
clubhouseleague://auth/callback. The app validates the PKCE state and code, completes the exchange, and establishes the session. - The API verifies the resulting WorkOS JWT using JWKS. The app upserts the profile with
workos_user_idandemail, then collects the requireddisplay_nameif it is missing.avatarmay be left unset. - Once the required profile is complete, the app resumes the original action. The server performs the create or membership write for the authenticated user only.
- The visitor remains signed in for later authenticated screens and mutations until sign-out or session expiry.
Edge cases and failure behavior
- Cancel or back out: Close the auth sheet, keep the visitor signed-out, discard no browse state, and do not write a league or membership. The user may retry the same action.
- Interrupted callback: If the app is killed, the callback is malformed, the PKCE state does not match, or the authorization code is expired/replayed, reject the exchange and offer a clean retry. Never treat a failed callback as authenticated.
- Email OTP problems: Show an expired/invalid-code error and allow resend or restart. Do not fall back to a password flow.
- Missing profile data: Do not create a league or write membership until
workos_user_id,email, anddisplay_nameare present. Prompt only for the missing display name; avatar remains optional. - Deep-link or browser unavailable: Report that sign-in could not start and leave the pending action unwritten. The callback must be registered exactly as
clubhouseleague://auth/callback; platform host details remain tracked in OD-9. - Network, token, or JWKS failure: Fail closed for authenticated operations, retain no unverified identity, and allow retry. The API must reject invalid or unverifiable JWTs.
- Expired/revoked session: An authenticated API call receives
401 Unauthorized. The client clears the invalid local session, returns the user to the soft gate, and asks them to authenticate again before retrying the pending action. Public browse and onboarding remain available without auth. - Duplicate taps or retries: Gated writes must be server-authorized and idempotent enough to avoid duplicate leagues or duplicate membership records.
- Provider account mismatch: Use the verified WorkOS subject as
workos_user_id; never trust an email or client-provided user ID as the identity key. - Sign-out: Clear local session material and require a new verified session for the next gated action; browsing remains available.
Acceptance criteria
- A signed-out user can open onboarding and browse discoverable leagues without authentication.
- Create league, request to join, and redeem invite code each trigger the soft gate when no valid session exists.
- AuthKit offers Apple, Google, and Magic Auth (email OTP), with no password UI or password API.
- Native auth uses the system browser sheet and PKCE, returning through
clubhouseleague://auth/callbackfor packagecom.pocketlabs.clubhouseleague. - The pending action and its league/code context survive a successful auth flow and resume only after profile completion.
- The profile has
workos_user_id,email, and requireddisplay_namebefore a create or join write;avataris optional. - The API verifies every authenticated WorkOS JWT using JWKS and rejects missing, invalid, expired, or unverifiable tokens.
- A
401 Unauthorizedclears the stale client session and returns the user to re-authentication without silently retrying a write. - Cancelled, failed, or incomplete auth never creates a league or writes membership.
Delivery ownership and tickets
| Area | Owner / link |
|---|---|
| Product | Morgan |
| Engineering | Remy disburses implementation to Kit / Dex |
| Auth contract | Reed |
| Runbook design | Mira (runbook only; app UI is on hold) |
| iOS ticket | clubhouse-league-ios#3 |
| Android ticket | clubhouse-league-android#2 |
Open decisions
- Auth deep-link hosts and the staging scheme
clubhouseleague-stagingare tracked as OD-9. - See also Open decisions / parking lot.