Files
heygo/docs/auth-spike.md

7.2 KiB

Auth provider spike: Cloudflare Workers OAuth

Decision summary

Choose Better Auth as the preferred auth route implementation for Heygo, using its Cloudflare Workers/D1-compatible runtime path and configuring only Google and GitHub social providers for MVP.

For this task, the code change is intentionally small: worker/auth.ts records provider/session constants and cookie policy helpers for the next session-guard task. We are not installing Better Auth or implementing the OAuth callback flow yet, because Task 7 is the session guard and there are no production OAuth secrets or remote Cloudflare resources in this workspace.

Apple is disabled by default. Add Sign in with Apple only after confirming an active paid Apple Developer Program membership and the required web credentials exist.

Chosen auth approach for this project

  1. Primary path: Better Auth on Cloudflare Workers with D1.
    • Better Auth currently publishes as better-auth@1.6.19.
    • Its docs describe D1/SQLite support through the built-in Kysely/D1 path, programmatic migrations for Cloudflare/serverless environments, social providers, and custom table/column names through modelName/fields.
    • It supports Google and GitHub social provider configuration in code.
  2. Project integration style: mount Better Auth under an API prefix such as /api/auth/* in the Worker, backed by the existing DB D1 binding.
  3. MVP session guard: Task 7 should read and validate the existing sessions table directly using an HttpOnly cookie token, so private my.heygo.cc routes can be protected before the full OAuth login/callback route is wired.
  4. Fallback if Better Auth schema/runtime integration blocks implementation: manual OAuth authorization-code flow using Workers-native fetch, Web Crypto, and D1. Auth.js with @auth/d1-adapter remains a secondary fallback but is less attractive because its D1 adapter creates fixed core table names (accounts, sessions, users, verification_tokens) and does not fit the existing oauth_accounts table as cleanly.

Why this fits Cloudflare Workers

  • Uses standard Web APIs available in Workers (Request, Response, fetch, URL, Headers, Web Crypto) rather than Node-only fs, raw net, or Node crypto APIs.
  • Can use the existing Cloudflare D1 binding (env.DB) as the system of record for users, OAuth accounts, and sessions.
  • Better Auth docs explicitly cover database-backed auth, D1/SQLite migrations, and programmatic migration support for Cloudflare/serverless contexts.
  • No new package was installed for this spike, avoiding lockfile churn before OAuth routes are implemented.

Mapping to existing D1 tables

Existing migration tables are already close to the MVP auth model:

users

Use as the canonical application user table.

Existing column Auth meaning
id app user id
email provider email, nullable for providers that do not return/verify email
name display name
image_url avatar/profile image URL
role app authorization role (user/admin)
created_at, updated_at audit timestamps

oauth_accounts

Use as the provider account-link table.

Existing column Auth meaning
id local account-link id
user_id foreign key to users.id
provider google, github, or future apple
provider_account_id stable subject/id from the OAuth provider
created_at account-link creation timestamp

If Better Auth requires additional account token columns for refresh/access/id tokens, add nullable columns in a future migration rather than storing tokens in code or secrets files.

sessions

Use as the session table for the Worker session guard.

Existing column Auth meaning
id session row id
user_id foreign key to users.id
session_token_hash hash of the opaque cookie token; never store raw cookie token
expires_at absolute session expiry
created_at session creation timestamp

Task 7 should query this table by a hash of the cookie token, reject expired sessions, and join/load the associated users row.

Cookie/session policy

Session cookie name: heygo_session.

Production cookie attributes:

HttpOnly; Secure; SameSite=Lax; Path=/; Domain=.heygo.cc

Rationale:

  • HttpOnly prevents client-side JavaScript from reading the session token.
  • Secure restricts transmission to HTTPS.
  • SameSite=Lax supports ordinary top-level OAuth redirects while reducing CSRF exposure.
  • Path=/ makes the cookie available to the whole app.
  • Domain=.heygo.cc allows the session set on heygo.cc to be visible to my.heygo.cc.

Local development fallback: keep the same attributes but omit Domain for localhost/non-heygo hosts so browsers accept the host-only cookie.

Provider support

Enabled for MVP:

  • Google OAuth
  • GitHub OAuth

Disabled by default:

  • Apple / Sign in with Apple

worker/auth.ts now encodes this as:

AUTH_ENABLED_PROVIDERS = ['google', 'github']
AUTH_DISABLED_PROVIDERS = ['apple']

Apple checkpoint

Do not block MVP on Apple.

Sign in with Apple for web requires Apple developer credentials such as:

  • Service ID
  • Team ID
  • Key ID
  • private key (.p8)

Current research indicates an active paid Apple Developer Program membership is likely required for the resources needed to configure Sign in with Apple for web. That program is commonly priced at $99/year. Apple should remain disabled until the account and credentials are confirmed.

Secrets needed later

Set these as Wrangler/Cloudflare secrets or local development variables when OAuth routes are implemented. Do not commit them.

Required for Google/GitHub MVP:

  • GOOGLE_CLIENT_ID
  • GOOGLE_CLIENT_SECRET
  • GITHUB_CLIENT_ID
  • GITHUB_CLIENT_SECRET
  • SESSION_SECRET

Optional only if Apple is enabled later:

  • APPLE_CLIENT_ID / Service ID
  • APPLE_TEAM_ID
  • APPLE_KEY_ID
  • APPLE_PRIVATE_KEY

Next implementation steps for Task 7 session guard

  1. Add SESSION_SECRET to the Worker Env type only when code needs it.
  2. Parse Cookie headers and extract heygo_session.
  3. Hash the opaque token with Workers-compatible Web Crypto; do not use Node crypto.
  4. Query D1 sessions by session_token_hash and require expires_at to be in the future.
  5. Load the matching users row and expose a typed authenticated user/session context to protected my.heygo.cc routes.
  6. Return 401/redirect for missing, invalid, or expired sessions.
  7. Keep /api/auth/* reserved for the later OAuth route implementation.

Verification notes

Research checked during this spike:

  • npm view better-auth reports 1.6.19 and a dependency stack centered on Web-compatible TypeScript packages such as Kysely/Jose/Noble.
  • Better Auth docs/search results describe social providers, D1 support, programmatic migrations for Cloudflare/serverless, and custom table/column names.
  • Auth.js D1 adapter docs confirm it is available for D1 but creates fixed core tables including accounts, sessions, users, and verification_tokens.
  • Apple/third-party provider docs confirm the web credential shape for Sign in with Apple and the likely paid Apple Developer Program requirement.