# Clerk OAuth Multi-Environment Setup

> **Runtime status:** localhost, the optional exact local-VM rehearsal, and
> apex production callbacks remain current. The permanent shared-development
> origin is retired; remove its external integrations only through the
> [retirement runbook](./SHARED_DEVELOPMENT_RETIREMENT.md). The Phase-B
> `vm-staging` procedure below is transitional context retained for approved
> rehearsal/recovery only.

Last updated: August 20, 2026

This runbook aligns Clerk social sign-in with StratoFusion's automatic storage
connection flow across the permanent OAuth environments:

- local: `http://localhost:3000`.
- optional local VM: `https://local-vm.stratofusion.io`.
- production: `https://stratofusion.io`.

It also covers the one temporary Phase-B migration rehearsal at
`https://vm-staging.stratofusion.io`.

Use PowerShell for normal Windows 11 work. Use Git Bash explicitly only for
`.sh` scripts. Do not use WSL.

## Scope

This runbook covers the social-login side of OAuth:

- Clerk social connections for Google and Microsoft.
- Google Cloud Console OAuth client configuration used by Clerk and the app.
- Azure App Registration configuration used by Clerk and the app.
- Dropbox direct OAuth callback configuration.
- environment-variable verification for local, local-VM, and production.
- the controlled Phase-B staging overlay and its rollback.
- per-environment testing for automatic storage connection.

Important:

- Do not share production Clerk or provider resources with local development.
- Do not share Google or Microsoft OAuth apps across environments.
- Do not use arbitrary preview deployments for OAuth verification.
- Do not trust `X-Forwarded-Host` as an authorization source. It must agree
  with the direct request authority, and callback returns use the validated
  configured site origin.
- Phase B is not a new credential environment. It temporarily adds the exact
  staging origin to the production Clerk, Google, Microsoft, and Dropbox
  configurations. Keep those staging entries alongside the apex entries
  through the initial cutover rollback window.
- The same environment-specific Google or Microsoft app can be reused by both StratoFusion direct OAuth and the matching Clerk social connection, but that app must contain both redirect URIs for that environment.

## Environment Matrix

| Environment | Stable app URL | Clerk instance type | Google client name | Azure app registration | Direct app callbacks |
| --- | --- | --- | --- | --- | --- |
| Local | `http://localhost:3000` | separate local test instance | `stratofusion-local` | `Stratofusion Local` | `/api/google`, `/api/onedrive`, and `/api/dropbox` on localhost |
| Local VM | `https://local-vm.stratofusion.io` | local test instance | local/test client with the exact callback | local/test registration with the exact callback | callbacks on the local-VM origin |
| Phase B | `https://vm-staging.stratofusion.io` | production live instance with temporary allowed origin | production client with temporary callback | production registration with temporary callback | `/api/google`, `/api/onedrive`, and `/api/dropbox` on vm-staging |
| Prod | `https://stratofusion.io` | separate prod live instance | `stratofusion-prod` | `Stratofusion Prod` | `/api/google`, `/api/onedrive`, and `/api/dropbox` on the apex |

For the Clerk callback URI, always copy the exact redirect URI shown in the corresponding Clerk social connection page for that environment. Do not guess it and do not copy it from a different Clerk instance.

## Controlled Phase-B OAuth Rehearsal

Enable staging OAuth only when all of these values agree exactly:

```dotenv
OAUTH_REHEARSAL_ORIGIN="https://vm-staging.stratofusion.io"
NEXT_PUBLIC_SITE_URL="https://vm-staging.stratofusion.io"
NEXT_PUBLIC_APP_URL="https://vm-staging.stratofusion.io"
GOOGLE_REDIRECT_URI="https://vm-staging.stratofusion.io/api/google"
ONEDRIVE_REDIRECT_URI="https://vm-staging.stratofusion.io/api/onedrive"
DROPBOX_REDIRECT_URI="https://vm-staging.stratofusion.io/api/dropbox"
```

Then make the matching external changes:

1. Add `https://vm-staging.stratofusion.io` to the production Clerk instance's
   allowed origins.
2. Add the staging Google callback to the production Google OAuth client.
3. Add the staging OneDrive callback as a Web redirect URI on the production
   Azure app registration.
4. Add the staging Dropbox callback to the production Dropbox app.
5. Keep every apex callback in place while rehearsing.

The OAuth-specific validator reports `staging` only for this exact opt-in. The
generic application environment may remain `unknown`, which prevents the
temporary host from being treated as the permanent app environment. Separate
resource-safety checks still enforce the production database name, live Stripe
mode, configured production rclone host, and strong cron secret. Wildcards,
arbitrary `*.vercel.app` URLs, other
`*.stratofusion.io` hosts, paths, custom ports, and non-HTTPS origins remain
unsupported.

At cutover, remove `OAUTH_REHEARSAL_ORIGIN`, restore both app URLs and all three
runtime provider redirects to `https://stratofusion.io`, and rebuild the app.
Do not remove apex dashboard callbacks. Retain the staging Clerk origin and
provider callbacks through the initial rollback window, then remove them only
as a separately approved cleanup after production is stable.

## 1. Clerk Dashboard Configuration

Apply the following process independently in the local Clerk instance, the dev Clerk instance, and the prod Clerk instance.

Shared checks:

1. Open the correct Clerk instance for the target environment.
2. Go to the social connections area in Clerk and open `Google`.
3. Enable the Google social connection.
4. Switch the connection to custom credentials. Do not rely on Clerk's shared development credentials for this flow because StratoFusion needs provider scopes beyond basic identity.
5. Paste the environment's `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`.
6. Add the Google Drive scope required by StratoFusion auto-connection:
   `https://www.googleapis.com/auth/drive`
7. Copy the exact Google redirect URI shown by Clerk for this instance. You will add that URI to the matching Google OAuth client in Step 2.
8. Save the connection.
9. Open `Microsoft`.
10. Enable the Microsoft social connection.
11. Switch the connection to custom credentials.
12. Paste the environment's `ONEDRIVE_CLIENT_ID` and `ONEDRIVE_CLIENT_SECRET`.
13. Add the Microsoft scopes required by StratoFusion auto-connection:
   `https://graph.microsoft.com/Files.ReadWrite.All`
   `https://graph.microsoft.com/User.Read`
   `offline_access`
14. Copy the exact Microsoft redirect URI shown by Clerk for this instance. You will add that URI to the matching Azure app registration in Step 3.
15. Save the connection.

Local Clerk instance:

1. Confirm the instance uses a `pk_test_...` publishable key and `sk_test_...` secret key.
2. Confirm Google custom credentials point to the local Google OAuth client only.
3. Confirm Microsoft custom credentials point to the local Azure app registration only.
4. Record the local Clerk Google redirect URI and the local Clerk Microsoft redirect URI.

Prod Clerk instance:

1. Confirm the instance uses a `pk_live_...` publishable key and `sk_live_...` secret key.
2. Confirm Google custom credentials point to the prod Google OAuth client only.
3. Confirm Microsoft custom credentials point to the prod Azure app registration only.
4. Record the prod Clerk Google redirect URI and the prod Clerk Microsoft redirect URI.

### Mandatory administrative MFA

Apply this independently to every environment containing an admin identity:

1. In the correct Clerk instance, enable at least one approved MFA strategy.
   Prefer authenticator-app TOTP and issue backup codes through Clerk's managed
   flow; enable SMS only when its residual risk is accepted.
2. Enable Clerk's `Require multi-factor authentication` session task where the
   instance and current sign-in flow support it.
3. Enroll every user ID listed in that environment's `ADMIN_USER_IDS`.
4. Sign out, sign in again, and complete the second factor. A previously active
   first-factor-only session is not sufficient.
5. Verify `/admin/infrastructure` loads only after the second factor and that an
   admin token with Clerk's `fva: [0, -1]` marker receives the
   `admin_mfa_required` denial.
6. Capture redacted dashboard configuration, admin inventory, and access-test
   evidence. Do not store session tokens, QR seeds, backup codes, phone numbers,
   or other authentication secrets in the repository.

StratoFusion checks the signed Clerk v2 factor-verification-age claim at
middleware, admin page layout, and admin API resource boundaries. Code cannot
enable Clerk MFA strategies or enroll administrators, so incomplete dashboard
configuration intentionally leaves admin access fail-closed.

Operator stop conditions:

- If a Clerk instance is using a client ID from another environment, stop and correct it before continuing.
- If the Clerk instance shows a callback URI from another environment, stop and confirm you opened the correct Clerk instance.

## 2. Google Cloud Console Configuration

Use the Google OAuth client that belongs to the same environment as the Clerk instance you configured in Step 1.

Shared checks for all three Google OAuth clients:

1. Open Google Cloud Console and select the correct project.
2. Open the existing OAuth client for the environment or create a `Web application` OAuth client if it does not exist.
3. Confirm the `Google Drive API` is enabled in that project.
4. Confirm the consent screen is configured for the correct environment workflow.
5. Outside Phase B, keep exactly two redirect URIs for the environment:
   - the StratoFusion direct callback for that environment.
   - the Clerk Google callback copied from the matching Clerk instance.
6. Remove redirect URIs from other environments, except for the one temporary
   staging direct callback on the production client during Phase B.
7. Copy the client ID and secret back into:
   - the environment variables for that environment.
   - the matching Clerk Google social connection custom credentials.

Local Google OAuth client:

1. Client name should be `stratofusion-local`.
2. Authorized redirect URIs must include:
   - `http://localhost:3000/api/google`.
   - the local Clerk Google callback URI copied from Step 1.
3. Do not leave production or retired shared-development redirect URIs on this client.
4. If the OAuth consent screen is still in testing, ensure the local operator accounts are listed as test users.

Prod Google OAuth client:

1. Client name should be `stratofusion-prod`.
2. Authorized redirect URIs must include:
   - `https://stratofusion.io/api/google`.
   - the prod Clerk Google callback URI copied from Step 1.
3. Do not leave localhost or dev redirect URIs on this client.
4. Because the flow requests Drive access, confirm the consent-screen publishing state and scope review are appropriate for production traffic before enabling the production Clerk instance.

## 3. Azure App Registration Configuration

Use the Azure app registration that belongs to the same environment as the Clerk instance you configured in Step 1.

Shared checks for all three app registrations:

1. Open Microsoft Entra admin center and the correct tenant.
2. Open the environment-specific app registration.
3. Confirm `Supported account types` is:
   `Accounts in any organizational directory and personal Microsoft accounts`
4. Under `Authentication`, configure the `Web` platform.
5. Outside Phase B, keep exactly two redirect URIs for the environment:
   - the StratoFusion direct callback for that environment.
   - the Clerk Microsoft callback copied from the matching Clerk instance.
6. Remove redirect URIs from other environments, except for the one temporary
   staging direct callback on the production registration during Phase B.
7. Under `Certificates & secrets`, confirm one valid client secret exists and copy it back into:
   - the environment variables for that environment.
   - the matching Clerk Microsoft social connection custom credentials.
8. Under `API permissions`, verify delegated Microsoft Graph permissions include:
   - `Files.ReadWrite.All`.
   - `User.Read`.
   - `offline_access`.
9. If tenant policy requires it, grant admin consent before testing.

Local Azure app registration:

1. App registration should be `Stratofusion Local`.
2. Redirect URIs must include:
   - `http://localhost:3000/api/onedrive`.
   - the local Clerk Microsoft callback URI copied from Step 1.
3. Do not leave dev or prod redirect URIs on this app registration.

Prod Azure app registration:

1. App registration should be `Stratofusion Prod`.
2. Redirect URIs must include:
   - `https://stratofusion.io/api/onedrive`.
   - the prod Clerk Microsoft callback URI copied from Step 1.
3. Do not leave localhost or dev redirect URIs on this app registration.

## 4. Environment Variable Verification Checklist

The application now validates these settings at startup. A mismatch blocks auto-connection before users can sign in.

Shared required keys:

- `NEXT_PUBLIC_SITE_URL`.
- `NEXT_PUBLIC_APP_URL`.
- `OAUTH_REHEARSAL_ORIGIN` during Phase B only.
- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`.
- `CLERK_SECRET_KEY`.
- `GOOGLE_CLIENT_ID`.
- `GOOGLE_CLIENT_SECRET`.
- `GOOGLE_REDIRECT_URI`.
- `ONEDRIVE_CLIENT_ID`.
- `ONEDRIVE_CLIENT_SECRET`.
- `ONEDRIVE_REDIRECT_URI`.
- `DROPBOX_CLIENT_ID`.
- `DROPBOX_CLIENT_SECRET`.
- `DROPBOX_REDIRECT_URI`.
- `SENTRY_DSN`.
- `SENTRY_ENVIRONMENT`.

Expected values by environment:

| Environment | Site/app URL | Rehearsal opt-in | Clerk key mode | Google | OneDrive | Dropbox |
| --- | --- | --- | --- | --- | --- | --- |
| Local | `http://localhost:3000` | unset | test | `/api/google` | `/api/onedrive` | `/api/dropbox` |
| Local VM | `https://local-vm.stratofusion.io` | exact local-VM origin | test | `/api/google` | `/api/onedrive` | `/api/dropbox` |
| Phase B | `https://vm-staging.stratofusion.io` | exact staging origin | live | `/api/google` | `/api/onedrive` | `/api/dropbox` |
| Prod | `https://stratofusion.io` | unset | live | `/api/google` | `/api/onedrive` | `/api/dropbox` |

Each callback path in this table is resolved against the site/app URL in the
same row; mixing rows must fail validation.

Local verification:

```powershell
pnpm env:guard
pnpm env:doctor:local
```

Local-VM verification:

- Confirm `DEPLOY_ENV=local-vm`, both app URLs, the exact rehearsal origin,
  and all provider redirects match the local-VM origin without printing keys.
- Confirm the Windows hosts-file aliases resolve only on the local workstation.

Production verification follows the
[Production VM Runbook](./VM_PRODUCTION_RUNBOOK.md). Inspect only variable
names, modes, and callback hosts; never print keys, secrets, or full database
URLs.

Phase-B VM verification:

```bash
grep -E '^(OAUTH_REHEARSAL_ORIGIN|NEXT_PUBLIC_SITE_URL|NEXT_PUBLIC_APP_URL|GOOGLE_REDIRECT_URI|ONEDRIVE_REDIRECT_URI|DROPBOX_REDIRECT_URI)=' /opt/stratofusion/deploy/.env
```

## 5. Automated Verification

Run the targeted test suite before operator validation:

```bash
pnpm test -- src/lib/deployment-host.test.ts src/lib/clerk-oauth-integration.test.ts src/lib/oauth.test.ts src/lib/__tests__/oauth-helpers.test.ts src/app/api/auth/clerk-oauth/__tests__/route.test.ts src/app/api/google/__tests__/route.test.ts src/app/api/onedrive/__tests__/route.test.ts src/app/api/dropbox/__tests__/route.test.ts src/instrumentation.test.ts
pnpm --dir fly-rclone exec jest src/config/__tests__/corsOrigins.test.js --runInBand --coverage=false
```

## 6. Per-Environment Manual Testing Plan

Apply the permanent-environment test story in order: local, CI, optional
local-VM rehearsal, then production. Run the Phase-B story only during an
explicitly approved migration rehearsal.

Shared success criteria:

1. Application startup does not log `OAuth environment validation failed`.
2. `GET /api/auth/clerk-oauth` succeeds for an authenticated user and lists the expected social provider.
3. Signing in with Google automatically creates or reuses a Google storage connection.
4. Signing in with Microsoft automatically creates or reuses a OneDrive storage connection.
5. The app lands on the expected post-auth flow without asking the user to manually reconnect storage.
6. Sentry does not record a new `feature=clerk-auto-connection` failure event for the happy path.

Local:

1. Start the app locally.
2. Create a brand-new test user with Google social sign-in.
3. Confirm the user reaches the app and Google storage is already connected.
4. Sign out.
5. Create a brand-new test user with Microsoft social sign-in.
6. Confirm the user reaches the app and OneDrive storage is already connected.

Local VM:

1. Start the local Compose rehearsal with cron disabled.
2. Confirm the exact local-VM origin is present only in the selected test
   Clerk and provider apps.
3. Repeat Google, Microsoft, and Dropbox connection flows without production
   credentials or callbacks.
4. Remove temporary local-VM callback registrations when the rehearsal ends if
   they are not part of the standing local test setup.

Phase B:

1. Confirm the exact rehearsal opt-in and all five URL values from the Phase-B
   block are active in the built client and VM runtime.
2. Sign in through Clerk at `https://vm-staging.stratofusion.io` and confirm the
   URL remains on staging.
3. Connect Google Drive, OneDrive, and Dropbox directly, one at a time.
4. Confirm each provider callback and final popup/same-tab return stay on
   staging and the account is stored in the VM database.
5. Confirm an arbitrary preview host remains blocked and the rclone gateway
   allows the exact staging origin only.
6. Confirm conflicting direct/forwarded hosts receive `403` and cannot alter a
   callback return through forwarded port or protocol values.

Prod:

1. Deploy the `main` branch to the `stratofusion-prod` production deployment.
2. Repeat the Google and Microsoft sign-in flow on `https://stratofusion.io`.
3. Confirm the production Clerk instance uses live keys and production-only OAuth apps.
4. Confirm no `.clerk.accounts.dev` host appears in startup validation or Sentry context for production.

Disconnect safety verification:

1. Manually disconnect an auto-connected Google or OneDrive account.
2. Sign out and sign back in with the same social provider.
3. Confirm StratoFusion does not silently reconnect the manually disconnected account.

Failure investigation:

1. Check startup logs for `OAuth environment validation failed`.
2. Check route logs for `CLERK-AUTO-CONNECT` entries with `oauthEnvironment`, `clerkFrontendApiHost`, and provider context.
3. Check Sentry events tagged with:
   - `feature=clerk-auto-connection`.
   - `oauth_environment`.
   - `oauth_provider`.
   - `oauth_error_code`.

## References

- Clerk social connections overview: https://clerk.com/docs/guides/configure/auth-strategies/social-connections/overview.
- Clerk Google social connection: https://clerk.com/docs/authentication/social-connections/google.
- Clerk Microsoft social connection: https://clerk.com/docs/authentication/social-connections/microsoft.
- Google OAuth clients: https://support.google.com/cloud/answer/6158849.
- Google app audience and test users: https://support.google.com/cloud/answer/13464323.
- Google app data access and scopes: https://support.google.com/cloud/answer/13807380.
- Microsoft app registration quickstart: https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app.
- Microsoft redirect URI configuration: https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-redirect-uri.
- Microsoft Graph delegated permissions reference: https://learn.microsoft.com/en-us/graph/permissions-reference.
