Architecture

Two apps, one config package

  • apps/auth (c-auth.mvscloud.com) — tenant user authentication, the /account/* self-service surface (profile, 2FA, passkeys, backup codes, sessions), the registry-driven app switcher, and the OIDC Identity Provider.
  • apps/platform-auth — operator authentication for MVS internal staff, hosted at c2-auth.mvscloud.com. Okta (OIDC + SAML fallback) is the primary operator sign-in; SCIM provisioning is wired from Okta.

Platform-AUTH (c2-auth.mvscloud.com) and platform-ADMIN (c2.mvscloud.com) are distinct hosts, sharing the .mvscloud.com cookie so operators authenticate once and carry the session to admin. The passkey relying-party ID is mvscloud.com.

Both apps mount Better Auth instances defined in packages/auth-server/central-auth.ts and packages/auth-server/platform-auth.ts (Better Auth 1.7.0-beta.5, pinned exact). packages/auth-client is the React/HTTP client downstream apps use.

Database isolation

All Better Auth tables live in a dedicated Neon project (mvs-auth-db) reached via AUTH_DATABASE_URL through the auth-only Prisma client @mvs/auth-db. Auth state is never co-resident with control-plane business data or tenant data. A Vercel cron (/api/cron/keep-warm, every 4 minutes) defeats Neon autosuspend.

Both auth apps — and both OIDC providers — share this single database. There is no second database for the platform surface; isolation is purely by table namespacing, keyed to two distinct user directories:

SurfaceUser directoryOIDC client/key tables
Central (c-auth.mvscloud.com)User (tenant / healthcare)oauth_client, jwks
Platform (c2-auth.mvscloud.com)PlatformUser (MVS operators)platform_oauth_*, platform_jwks

The two surfaces never share rows. Central OIDC clients and signing keys live in oauth_client / jwks and FK to User; platform OIDC clients and keys live in platform_oauth_* / platform_jwks and FK to PlatformUser. Cross-surface use of an Initial Access Token or client is rejected.

Sessions and cookies

  • Sessions share automatically across *.mvscloud.com (cookie-scoped, crossSubDomainCookies), with JWE-encrypted cookie caching.
  • The hop to a different eTLD (mvspay.io) uses the legacy cross-domain token flow (/api/cross-domain-token/api/exchange-token): one-time, SHA-256-hashed, 60-second tokens.
  • The OIDC Identity Provider is the long-term replacement for that bespoke flow.

OIDC Identity Providers (two surfaces)

The estate runs two independent OIDC providers, one per auth app. They share the same engine and the same mvs-auth-db, but are isolated by table namespacing and back distinct user directories (see Database isolation):

Central providerPlatform provider
Issuerc-auth.mvscloud.comc2-auth.mvscloud.com
AudienceTenant / healthcare appsMVS operator apps
User directoryUserPlatformUser
Client / key tablesoauth_client, jwksplatform_oauth_*, platform_jwks
Provider gateOIDC_PROVIDER_ENABLED=truePLATFORM_OIDC_PROVIDER_ENABLED=true

Both providers are live in production. Both sign ID tokens with EdDSA (Ed25519) using their own keys, require PKCE (S256 only), and set allowDynamicClientRegistration: false — the RFC 7591 /oauth2/register endpoint is off, so there is no dynamic client registration on either surface. Each provider’s gate is read at cold start; when off, that surface’s discovery document returns 404.

Central provider — c-auth.mvscloud.com

Gated by OIDC_PROVIDER_ENABLED=true (on in production):

  • Discovery: /.well-known/openid-configuration
  • JWKS: /api/auth/jwks (EdDSA / Ed25519)
  • Authorization-code flow with PKCE required (S256 only) at /api/auth/oauth2/authorize and /api/auth/oauth2/token
  • Opaque access tokens (mvs_at_*) validated via /api/auth/oauth2/introspect
  • Namespaced custom claims (https://mvscloud.com/*: org_id, org_slug, role, tenant_db) — sourced from tenant-org membership — in the ID token and userinfo

Relying parties are registered as oauth_client database rows one of two supported ways: the gated, authenticated self-service registration API (POST /api/auth/admin/oauth-clients, admin session or scoped Initial Access Token) or the operator CLI pnpm --filter @mvs/auth-server oidc:register. The OIDC_CLIENT_* env triplets are vestigial.

Platform provider — c2-auth.mvscloud.com

The operator-side equivalent of the central IdP: a full OIDC provider for MVS internal apps, gated by PLATFORM_OIDC_PROVIDER_ENABLED=true. Its issuer is pinned via PLATFORM_OIDC_PROVIDER_ISSUER_URL (default https://c2-auth.mvscloud.com):

  • Backs the PlatformUser directory and writes the platform_oauth_* / platform_jwks tables — never oauth_client / jwks
  • Namespaced custom claims (https://mvscloud.com/*: role, groups) sourced from PlatformUser, not from tenant-org membership
  • Operator relying parties register out-of-band as platform_oauth_client rows via the platform admin / Initial Access Token registration API (surface=platform); platform IATs and clients are rejected on the central surface and vice versa

See the operator-app OIDC integration guide to wire an operator app as a relying party.