Mockauth is a multi-tenant mock OpenID Connect provider for QA and ephemeral environments. Each tenant issues tokens
through resource-scoped paths like https://<host>/r/<apiResourceId>/oidc, exposing per-resource JWKS,
username-only login, and an admin console (NextAuth + Logto) for managing tenants, API resources, redirect URIs, and RSA
signing keys.
- Next.js App Router (TypeScript, Tailwind)
- NextAuth (Logto provider) for admin console auth
- Prisma + PostgreSQL (Supabase-compatible)
- jose + openid-client for OIDC primitives
- Vitest + Playwright for unit/integration/E2E coverage
- Node.js 20+
- pnpm 10+
- Docker (for local PostgreSQL)
Package manager note: Mockauth is pnpm-only. The
packageManagerfield and.npmrcenforce pnpm 10.29.3, and the checked-inpnpm-lock.yamlis the single source of truth. Avoid runningnpmoryarn installto keep dependencies in sync across environments.
-
Start the local Postgres containers (one for app data, one for tests):
docker run -d --name mockauth-db -p 5432:5432 \ -e POSTGRES_USER=mockauth -e POSTGRES_PASSWORD=mockauth -e POSTGRES_DB=mockauth postgres:16 docker run -d --name mockauth-db-test -p 5433:5432 \ -e POSTGRES_USER=mockauth -e POSTGRES_PASSWORD=mockauth -e POSTGRES_DB=mockauth_test postgres:16
(Alternatively use
docker-compose.dev.yml.) -
Copy
.env.example→.envand update secrets as needed. For local dev you can also use the provided.env.development, which is pre-wired to our shared Logto sandbox. Automated tests read from.env.test(already checked in) and point at themockauth_db-testdatabase. -
Install dependencies and run the initial migration + seed:
pnpm install pnpm prisma:migrate pnpm prisma:seed
(
pnpm prisma:migratetargets the local dev database viaprisma migrate dev. For deploys, usepnpm db:migrate, which wrapsprisma migrate deploy.)
DATABASE_URLshould point at the Supabase pooler (port6543) so the runtime benefits from connection multiplexing. Example:DATABASE_URL=postgres://<user>:<password>@db.<hash>.supabase.co:6543/postgres?sslmode=requireDATABASE_DIRECT_URLshould point at the direct Postgres instance (port5432) so Prisma migrations bypass the pooler. Example:DATABASE_DIRECT_URL=postgres://<user>:<password>@db.<hash>.supabase.co:5432/postgres?sslmode=require- Always include
sslmode=requirefor hosted Supabase projects; Vercel builds read both variables (Prisma prefersdirectUrlforprisma migrate deploy).
| Command | Description |
|---|---|
pnpm dev |
Start Next.js dev server on http://127.0.0.1:3000. |
pnpm lint |
ESLint (Next flat config). |
pnpm typecheck |
TypeScript --noEmit. |
pnpm test |
Prepares test DB, seeds fixtures, runs Vitest suite. |
pnpm test:e2e |
Rebuilds test DB and runs Playwright (uses openid-client to complete Auth Code + PKCE). |
pnpm test:e2e:dev |
Same as above but keeps a dev server running via start-server-and-test. |
pnpm db:generate |
Runs prisma generate (no migrations). Used by postinstall and Vercel builds. |
pnpm db:migrate |
Runs prisma migrate deploy for production/provisioned databases. |
pnpm prisma:migrate |
prisma migrate dev for the dev database. |
pnpm prisma:seed |
Seeds tenants/clients/mock users for manual testing. |
Playwright browsers are installed separately via pnpm playwright:install (run once per workstation or after dependency
updates). CI runs this script automatically before executing E2E specs.
tests/fixtures/logto.dev.config.tscontains the Logto dev tenant details provided by Rowan. These credentials are for local development and automated testing only—never deploy them to production..env.developmentand.env.testalready setLOGTO_ISSUER=https://hdjvaa.logto.app/oidcalong with the matching client ID/secret so NextAuth can talk to the Logto sandbox out of the box.- If you create your own Logto tenant, update the
LOGTO_*env vars (and never commit proprietary secrets;.envstays local-only per.gitignore).
- Accessible at
/adminand protected via NextAuth + Logto. ProvideLOGTO_*env vars to point at your Logto instance. - The Stage 2 UI introduces a sidebar with tenant switching, per-tenant client lists, a focused create form, and
detailed client pages (copy helpers, redirect management, secret rotation, metadata). Each client now includes an
Auth strategies card so you can toggle username/email flows and decide how the OIDC
subclaim is derived. The mock login screen mirrors the configured strategies automatically—no whitelist or per-user setup required.
- NextAuth blocks linking multiple OAuth accounts to the same email by default. In QA we seed
owner@example.test(and other roles) ahead of time, so the first Logto sign-in must be allowed to link automatically. Toggle this withALLOW_EMAIL_LINKING=true(enabled in.env.developmentand.env.test, leftfalseelsewhere by default). NEXTAUTH_URLmust match the public tunnel URL (e.g., the Cloudflare tunnel hostname) and stay stable. If the tunnel rotates, updateNEXTAUTH_URLimmediately, re-add the tunnel callback URL inside Logto (Applications → your client → Sign-in redirect URIs), and keepNEXTAUTH_SECRETunchanged to avoid forcing users to re-authorize.- For automated tests we rely on a built-in mock Logto server that lives under
http://127.0.0.1:3000/api/test/logto. Enable it viaENABLE_TEST_ROUTES=trueand setLOGTO_ISSUERto the same URL. You can POST to/api/test/logto/profilewith{ email, sub, name }to queue the next identity when simulating edge cases. - Client-side sign-in buttons should always point to the relative path
/api/auth/signin/logto?callbackUrl=/adminso they inherit the current host; reserveNEXTAUTH_URLfor server-side NextAuth configuration only.
- Redirect entries must be absolute URLs and use
httpsunless they targetlocalhost,127.0.0.1, or::1overhttpfor local testing. - Supported shapes:
- Exact URL:
https://app.example.com/callback - Host wildcard (single left-most label only):
https://*.example.com/callback - Path wildcard (trailing
/*suffix):https://app.example.com/callback/*
- Exact URL:
- Host and path wildcards are intended for QA only. The Admin form surfaces muted warnings whenever one is entered.
- A full catch-all (
*) exists strictly for QA automation and is gated by theMOCKAUTH_ALLOW_ANY_REDIRECTenv flag (disabled by default). When you type*in the Admin UI, a destructive warning reminds you that this must never be enabled in production.
- Username and email sign-in flows are configured per client. Both are enabled/disabled independently and expose their
own subject-source selector (
enteredreuses the typed identifier,generated_uuid("Generate UUID (stable per identity)") stores a persistent UUID per tenant + strategy + identifier). - Username strategy returns
preferred_usernamewhenprofileis requested. Email strategy requires theemailscope and emitsemail+email_verifiedaccording to the configured mode (always true, always false, or QA-selected at login). It never exposespreferred_username. - All subject decisions are stored on the session + authorization code so every token and
userinforesponse reflects the strategy that was used during login.
- Mockauth seeds clients with the OIDC defaults
openid,profile, andemail, but you can add any custom scope that matches^[a-z0-9:_-]{1,64}$.openidremains mandatory and cannot be removed. - The Admin UI exposes a tags-based Scopes card on each client. Use the suggestions for standard OIDC scopes or type custom values; duplicates are ignored automatically.
- Discovery advertises only the platform-wide OIDC defaults via
scopes_supported. Custom scopes stay per-client and opaque to discovery. - The authorize endpoint validates that every requested scope is allowed for the client and still requires
openid.
- The admin create dialog includes a Proxy client mode alongside the default regular clients. Proxy mode brokers OAuth/OIDC against an upstream identity provider while preserving Mockauth’s tenant boundary and redirect validation.
- The create/edit forms collect upstream authorization/token/userinfo/JWKS endpoints, provider client credentials, and optional scope mappings. Default provider scopes cover the fallback when an app requests nothing; scope mappings translate app scopes (left-hand column) into upstream scopes (right-hand column). When no mapping applies, Mockauth forwards the original scope verbatim.
- Toggle the advanced flags to match the upstream provider:
- Provider supports PKCE stores a verifier on the transaction and sends
code_challengewhen redirecting. - Provider issues ID tokens forwards the app’s
nonceand expects an upstreamid_token. - Passthrough prompt/login_hint forwards those request parameters when present.
- Passthrough token payload returns the upstream JSON verbatim; otherwise Mockauth emits a minimal compliant access token response derived from the provider payload.
- Provider supports PKCE stores a verifier on the transaction and sends
- Proxy clients display a “proxy mode” badge on the detail page. The new Proxy provider card lets you rotate upstream secrets, edit endpoints, and adjust mappings without recreating the client. Leaving the secret blank keeps the existing encrypted value.
- Callback handling lives at
/r/<apiResourceId>/oidc/proxy/callback. Authorization requests set a short-lived transaction cookie; the callback matches it, trades the upstream code for tokens, and issues a Mockauth authorization code referencing the stored upstream response. The regular token endpoint then serves proxied access/refresh tokens (and ID tokens when provided) while enforcing the client’s token auth method and PKCE requirements.
- Tenant slugs have been removed from OIDC URLs. Issuers are now scoped purely by API resource.
- Each tenant still exposes its default API resource in the Admin sidebar, and issuers follow the
https://<host>/r/<apiResourceId>/oidcpattern. Copy the resource ID directly from the Admin UI before updating relying parties.
For the seeded tenant tenant_qa (default resource tenant_qa_default_resource):
| Endpoint | Path |
|---|---|
| Issuer | http(s)://<host>/r/tenant_qa_default_resource/oidc |
| Discovery | /r/tenant_qa_default_resource/oidc/.well-known/openid-configuration |
| JWKS | /r/tenant_qa_default_resource/oidc/jwks.json |
| Authorize | /r/tenant_qa_default_resource/oidc/authorize (Authorization Code + PKCE S256 only) |
| Token | /r/tenant_qa_default_resource/oidc/token |
| UserInfo | /r/tenant_qa_default_resource/oidc/userinfo |
- The QA login form lives at
/r/<apiResourceId>/oidc/loginand renders whichever strategies the client has enabled. Sessions are tenant-scoped and stored separately from NextAuth admin auth.
vercel.jsonpins the install command topnpm install --frozen-lockfileso deploys stay in lockstep with the repo.- The build command always runs
pnpm prisma:generateand only executespnpm db:migratewhenVERCEL_ENV=production(preview deployments stay read-only, production applies migrations beforenext build). - Local
postinstallstill callspnpm prisma:generateand never attempts to run migrations, keepingpnpm installsafe on contributor machines.
.github/workflows/ci.yml runs lint, typecheck, vitest, Playwright E2E, and Next build on every push/PR. All tests must
pass locally before pushing—CI is a guardrail, not a substitute for local verification.