Skip to content

Staging OAuth

OAuth providers register redirect URIs per app. To test changes that touch the auth flow, you typically want a separate OAuth client configured against a staging redirect URI rather than https://hivecast.ai/....

Why a separate client

Sharing the production OAuth client with staging is wrong because:

  • Test users land in prod state. A successful staging sign-in creates a real principal record in production system.auth. Cleanup is manual and error-prone.
  • Bad code paths can mint prod sessions. A bug in staging that signs sessions with the prod issuer string would issue valid prod cookies.
  • No log isolation. Staging traffic mixes with production audit logs.

The fix is to register an additional OAuth client per provider scoped to the staging environment, with its own redirect URI and its own issuer string.

Per-provider configuration

Google

In Google Cloud Console (OAuth 2.0 Client IDs):

  1. Create a new "Web application" OAuth client.
  2. Authorized redirect URI: https://staging.hivecast.ai/api/auth/callback/google.
  3. Authorized JavaScript origin: https://staging.hivecast.ai.
  4. Copy clientId + clientSecret to the staging environment's config.

GitHub (target state)

In GitHub Developer Settings → OAuth Apps:

  1. Create a new OAuth App.
  2. Homepage URL: https://staging.hivecast.ai.
  3. Authorization callback URL: https://staging.hivecast.ai/api/auth/callback/github.
  4. Copy clientId + clientSecret.

Anthropic (provider OAuth)

For local-loopback OAuth flows, the redirect is always http://localhost:<port>/callback. Anthropic's app registration accepts this; no separate staging client is needed because the OAuth happens device-side, not cloud-side.

Per-environment session secret + issuer

HostSessionService is constructed with two security-relevant inputs:

  • secret (auth.sessionSecret config) — the HMAC key. Distinct per environment.
  • issuer (auth.issuer config) — the JWT iss claim. Distinct per environment.

The combination prevents tokens from one environment from being accepted by another:

EnvironmentIssuerSecret rotated separately
Productionhivecast.aiyes
Stagingstaging.hivecast.aiyes
Local devlocalhost-dev (or per-developer)yes

If staging's secret leaks, production sessions are not affected.

The mx_session cookie sets Path=/ without a Domain attribute, so each origin has its own cookie. Staging and prod share neither cookies nor sessions even if a developer is signed in to both in the same browser.

For browsers that share cookies across subdomains (if a Domain were ever set), a wildcard would be a security regression. The current default is correct.

Test principal isolation

Per CLAUDE.md Rule 8 (commit everything, but secrets are excluded from git):

  • Do NOT commit staging OAuth client secrets to the repo.
  • Use environment-specific secret managers (or .env.staging files excluded by .gitignore).
  • Rotate immediately if a secret is committed accidentally.

Production safety: deny-listing test domains

Staging clients in OIDC providers should be configured with hd: 'staging.hivecast.ai' or equivalent restriction where possible. This prevents a misconfigured staging client from being used to sign in real production users.

Test user accounts

For end-to-end test runs:

  • Create a dedicated test Google account (test-staging@<your domain>).
  • Sign in via the staging OAuth client.
  • Verify that:
    • the resulting mx_session cookie's iss is the staging issuer
    • the principal record lands in the staging system.auth state file, not production
    • the staging-only Devices page is reachable

See also

  • HiveCast account login — how the cookie/JWT mints.
  • Local testing — running auth without any cloud at all.
  • WORKSTREAMS/matrix-web/USER-FLOWS-AND-AUTH-BOUNDARIES.md — what belongs cloud-side vs Device-side.

Source: projects/matrix-3/packages/system-auth/src/host-auth.ts (HostSessionService constructor accepts secret + issuer); projects/matrix-3/packages/system-auth/src/index.ts (auth config wiring at lines 82-108).