WorkOS Development Setup
This is an operational runbook. A trusted operator executes it by hand, once, to bring the development WorkOS application into the state Phase 5 of the migration plan requires. Nothing here is a Terraform apply: WorkOS applications, identity providers, redirect allowlists, and webhook subscriptions are configured through the WorkOS Dashboard, and the two WorkOS secret payloads are loaded directly into Secret Manager with gcloud, never through terraform apply. See ADR 0008 for why WorkOS owns authentication while FPC keeps application identity and authorization.
Prerequisites
- A WorkOS account with access to create and configure environments for the FPC organization.
gcloudauthenticated against thefeelproclub-devproject with permission to add Secret Manager secret versions (roles/secretmanager.secretVersionManageror broader).- The two Secret Manager containers
fpc-dev-workos-api-keyandfpc-dev-workos-webhook-secretalready exist. Terraform creates these containers (infra/modules/fpc-environment/secrets.tf); this runbook only adds their payloads, and only after that Terraform apply has run.
1. Select the WorkOS development environment
- In the WorkOS Dashboard, open the FPC project and select its Development environment. A WorkOS project is created with a sandbox environment and a Production environment; a separate development sandbox is added alongside them. Do not reuse Production credentials for development. WorkOS exposes no way to delete an environment through its API or CLI, so an unused sandbox is left in place rather than removed.
- Open the environment's API Keys page and note the Client ID (
client_...) and the Secret API Key (sk_...). The client ID is not a secret — it becomes theGCP_DEV_WORKOS_CLIENT_IDGitHub repository variable (infra/README.md), consumed asTF_VAR_workos_client_idby Terraform and delivered to Cloud Run as the plainWORKOS_CLIENT_IDenvironment variable. The secret API key is a genuine secret; keep it out of any file this repository tracks and load it directly into Secret Manager in step 5.
2. Reduce the sign-in methods to Google alone
A new WorkOS environment does not start with every provider disabled. Apple, GitHub, Microsoft, and Google OAuth, email-and-password authentication, and SSO are all enabled by default. Leaving them on would let AuthKit's hosted sign-in page create accounts through paths the app has no interface for, and the API would provision an FPC user for any of them, because it trusts any validly signed WorkOS token regardless of which method produced it. Reducing this set is the substance of this step; enabling Google is the trivial part.
- In the environment's Authentication settings, confirm Google OAuth is enabled and configure its Google Cloud OAuth client (a separate, standard Google Cloud OAuth 2.0 client, not an FPC backend service account).
- Disable Apple, GitHub, Microsoft, email-and-password authentication, SSO, and IdP-initiated SSO. Verify afterwards rather than trusting the form: every provider flag except Google must read false.
- Apple stays disabled deliberately, not by oversight. Apple sign-in is deferred until shortly before an App Store submission. Android launches first, and Google Play imposes no equivalent of App Store Review Guideline 4.8 (which requires apps offering third-party sign-in to also offer Sign in with Apple). See the "Scope Decisions" subsection of Phase 5.
- Leave every remaining identity provider disabled. WorkOS Organizations and WorkOS RBAC stay out of scope for the MVP (ADR 0008).
3. Configure the redirect URI allowlist
The web client's authorization-code flow returns to /auth/callback, registered in apps/app/src/App.tsx and defaulted by apps/app/src/auth/workos/workos-auth-client.ts. In the environment's Redirects settings, allowlist only:
http://localhost:5173/auth/callback— local development against Vite's default port.https://[DEV_WEB_ORIGIN]/auth/callback— the deployed development Vercel origin. Replace[DEV_WEB_ORIGIN]with the actual development web deployment's hostname; do not add a wildcard or a production origin here.
If a deployment overrides the callback with VITE_WORKOS_REDIRECT_URI, allowlist that exact value instead. WorkOS rejects any redirect URI not listed here, so a mismatch surfaces as a failed sign-in rather than a silent fallback.
Do not add the Capacitor scheme (capacitor://localhost) in this phase: the Capacitor client does not cut over to WorkOS until Phase 7.
4. Register the webhook endpoint
In the environment's Webhooks settings, add an endpoint pointing at the deployed development API's WorkOS event route,
POST /webhooks/workos(apps/api/src/modules/webhooks/). Read the API's base URL fromterraform -chdir=infra/environments/dev output -json environment'scloud_run_service_urifield, so the full endpoint ishttps://<cloud-run-service-uri>/webhooks/workos.This route authenticates by signature, not by bearer token — it is deliberately reachable without a WorkOS access token, because WorkOS has none to present. A request carrying an invalid or absent signature is rejected and writes nothing.
Subscribe the endpoint to exactly these event types, and no others:
user.createduser.updateduser.deleted
These are the only events the durable inbox in Phase 5's commit sequence consumes. Subscribing to additional event types would deliver events the API has no handler for.
Copy the endpoint's Signing Secret (
whsec_...). This is the second and last secret payload this runbook loads, in step 5.
5. Load the secret payloads into Secret Manager
Run each command once per environment that needs a payload (development only, in this phase). Terraform must have created the containers first; this step adds versions to containers that already exist.
Each command reads the payload from an interactive standard input. Do not pass the secret as a command-line argument to printf, echo, or any other command in a pipeline: an argument is recorded in shell history and is visible in a process listing for as long as the command runs. Typing it into stdin avoids both.
Run the command, paste the value, then press Ctrl-D without pressing Enter first. A trailing newline becomes part of the secret: an API key with one fails WorkOS authentication, and a signing secret with one makes every webhook signature mismatch. Both failures are confusing to diagnose and neither is obviously a whitespace problem.
gcloud secrets versions add fpc-dev-workos-api-key --project=feelproclub-dev --data-file=-gcloud secrets versions add fpc-dev-workos-webhook-secret --project=feelproclub-dev --data-file=-To confirm a payload carries no trailing newline, compare its stored byte length against the length of the value you pasted:
gcloud secrets versions access latest --secret=fpc-dev-workos-api-key --project=feelproclub-dev | wc -cVerify both versions exist before relying on a deployment that reads them:
gcloud secrets versions describe latest --secret=fpc-dev-workos-api-key --project=feelproclub-dev
gcloud secrets versions describe latest --secret=fpc-dev-workos-webhook-secret --project=feelproclub-devinfrastructure-delivery.yml's development deploy job runs this same describe check before building an image, and fails closed if either version is missing.
6. Does fpc-bootstrap need re-applying?
No. None of the steps above change a delivery identity's role list or a trusted-operator policy resource — the two conditions that require re-running fpc-bootstrap (infra/README.md, "Re-running bootstrap"). This runbook only configures the WorkOS Dashboard and adds Secret Manager secret versions to containers Terraform already created through the ordinary environment apply. Do not re-run fpc-bootstrap for this runbook; doing so would apply no meaningful change and would needlessly require reauthenticating with a human credential.
Verification
- A real Google login through the development web client reaches the deployed Cloud Run API and returns an authenticated session.
- The webhook endpoint shows successful recent deliveries for
user.created,user.updated, anduser.deletedin the WorkOS Dashboard after exercising sign-up, profile update, and account deletion in development. gcloud secrets versions describe latestsucceeds for bothfpc-dev-workos-api-keyandfpc-dev-workos-webhook-secret.
Two checks that only a real WorkOS tenant can settle
Both of these pass every automated test, because the test suites verify against fake adapters by design. Neither can be confirmed before this runbook is executed.
Session survives a page reload. Sign in, then reload the page. apps/app/src/auth/workos/workos-auth-client.ts forces devMode: false so the refresh token is never written to localStorage, which ADR 0008 forbids. The cost is that the refresh token lives only in memory, so restoring a session after a reload depends on WorkOS's workos-has-session cookie and a cross-origin refresh against api.workos.com. Browsers that block third-party cookies may break that path. If the session does not survive a reload, do not weaken the deployed configuration: the ordered options are to enable devMode for the local Vite dev server only while every built artifact stays false, to move refresh behind a first-party endpoint on the API, or to accept re-login on reload in development.
The token signing algorithm is RS256. apps/api/src/common/auth/workos-token-verifier.ts pins algorithms: ['RS256'], which prevents algorithm-confusion attacks but rejects every token if WorkOS signs with something else. A mismatch fails closed and loudly — every authenticated request returns 401 with an auth_token_invalid_signature outcome in the api_authentication_outcome metric — so check that metric, not just whether login appears to work.