A developer tool for dynamically creating, saving, and launching isolated OIDC or SAML test instances. Configure an identity provider, authenticate, inspect claims and payloads, exercise token lifecycle actions, validate logout behavior, test provisioning, and debug protocol behavior from a single enterprise-style workbench.
Live: authlab-snowy.vercel.app
- Dynamic Provider Registry — Create multiple OIDC or SAML app instances, each with its own slug-based URL
- Isolated Sessions — Each tenant gets its own encrypted cookie (
authlab_{slug}), so you can test multiple providers simultaneously - OIDC Workbench — Custom authorization parameters, PKCE modes (
S256,PLAIN,NONE), nonce validation, UserInfo, introspection, revocation, refresh, client credentials, device authorization, token exchange, PAR, and both front-channel and back-channel logout testing - Lifecycle Inspector — Token timeline,
acr/amrdiagnostics, JWT signature validation,at_hash/c_hashvalidation, claims diff, trace logging, compliance reporting, decoded claims, and raw JSON/XML views - Per-App SAML Signing — Upload or generate self-signed SP signing material per app instance for signed metadata and AuthN requests
- Enterprise SAML Controls — NameID format, ForceAuthn, IsPassive, AuthnContext, signature algorithm, clock skew, encrypted assertions, and SAML SLO per app instance
- SAML Trust Diagnostics — Structured assertion parsing, signature detail inspection, IdP certificate health/expiry checks, and run-level compliance summaries
- SCIM Mock Provisioning — App-scoped SCIM
ServiceProviderConfig,Schemas,ResourceTypes,Users, andGroupsendpoints with persisted mock resources and request logs - Callback Routing — App-specific callback URL for both OIDC and SAML; state/RelayState maps back to the correct tenant
- Encryption at Rest — Client secrets and IdP certificates encrypted with AES-256-GCM in the database
- Secret Redaction — API never exposes actual secrets; returns
hasClientSecret: booleaninstead - Team-Centric Dashboard — Team switcher updates apps and shows live team membership/actions in the dashboard sidebar
- Cross-Team App Transfer — Team admins/owners can move or copy app configurations across teams
- Dense SaaS UI — Compact management tables, runtime launch controls, analyst-focused inspector tabs, mobile-friendly stacked table layouts, and explicit team access / join-queue states
- App Organization — Lightweight tags (up to 10 per app), auto-detected IDP grouping by hostname with SSO and cross-protocol badges, known-provider labeling (Okta, Microsoft Entra ID, Auth0, Google Workspace, OneLogin, Ping Identity, Keycloak), and Flat / By IDP / By Tag dashboard view toggle
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, TypeScript, Tailwind CSS v4) |
| Database (local) | SQLite via Prisma 7 + @prisma/adapter-better-sqlite3 |
| Database (production) | Turso (libSQL) via Prisma 7 + @prisma/adapter-libsql |
| OIDC | openid-client v6 (dynamic discovery, PKCE) |
| SAML | @node-saml/node-saml v5 (standalone, no Passport) |
| Sessions | iron-session (encrypted cookies, dynamic names) |
| Encryption | AES-256-GCM (Node.js crypto) |
| Validation | Zod v4 (discriminated unions) |
| Hosting | Vercel + Turso |
- Node.js >= 20
- npm >= 10
git clone https://github.com/humac/authlab.git
cd authlabnpm installThis also runs prisma generate automatically (via the postinstall script), which generates the Prisma client at src/generated/prisma/client/.
cp .env.example .envEdit .env and generate secret keys:
# Generate a 64-character hex string for MASTER_ENCRYPTION_KEY
openssl rand -hex 32
# Generate a 64-character hex string for SESSION_PASSWORD
openssl rand -hex 32Your .env should look like:
DATABASE_URL="file:./dev.db"
MASTER_ENCRYPTION_KEY="<your-64-char-hex-string>"
SESSION_PASSWORD="<your-64-char-hex-string>"
NEXT_PUBLIC_APP_URL="http://localhost:3000"The TURSO_* variables are only needed for production — leave them commented out for local development.
npx prisma db pushThis creates a dev.db SQLite file in the project root with the current Prisma schema, including app instances, auth runs, lifecycle events, and SCIM mock resources.
npm run devVisit http://localhost:3000. You should see the AuthLab dashboard with a "Create Your First App" prompt.
Recent UX baseline highlights:
- management tables collapse into readable labeled rows on mobile instead of horizontal scroll
- team access states are explained directly in the Teams directory (
No access,Request pending, role-based access, and join queue summaries) - compact search inputs and adjacent action buttons share the same control height
Run both local test suites before creating a commit:
npm run test:unit
npm run test:integration
npm run test:securityIf your work changes repo workflow, testing strategy, or agent guidance, update AGENTS.md and CLAUDE.md in the same branch before committing.
Browser E2E coverage is available separately with:
npm run test:e2eNightly auth-path latency baselines use:
npm run test:perf- Click Create Your First App (or Create New App in the sidebar)
- Choose OIDC or SAML
- Fill in your identity provider's configuration:
- OIDC: Issuer URL, Client ID, Client Secret
- SAML: SSO Entry Point URL, Issuer/Entity ID, IdP Certificate
- SAML Shortcut: Use Import IdP Metadata to parse XML or fetch metadata from URL and apply values
- Set a name and slug (auto-generated from name)
- Optionally add tags (e.g.
production,staging,okta) to organize apps - Review and create
Apps sharing the same identity provider are automatically grouped in the dashboard's By IDP view. When multiple apps point to the same provider hostname, SSO and cross-protocol badges highlight the relationship.
Register these callback URLs in your identity provider's configuration:
- OIDC (per app):
http://localhost:3000/api/auth/callback/oidc/{slug} - OIDC front-channel logout (per app):
http://localhost:3000/api/auth/frontchannel-logout/{slug} - OIDC back-channel logout (per app):
http://localhost:3000/api/auth/backchannel-logout/{slug} - SAML (per app):
http://localhost:3000/api/auth/callback/saml/{slug}
Use the exact slug for the app instance you are testing (shown on each app test page).
OIDC example for slug finance-oidc: http://localhost:3000/api/auth/callback/oidc/finance-oidc
SAML example for slug hr-saml: http://localhost:3000/api/auth/callback/saml/hr-saml
For each SAML app, AuthLab exposes:
- Unsigned metadata:
http://localhost:3000/api/saml/metadata/{slug} - Signed metadata:
http://localhost:3000/api/saml/metadata/{slug}?signed=true
Signed metadata and signed AuthN requests now use the app instance's own stored SP signing material. If configured, metadata also publishes the app-specific decryption certificate and SAML SLO callback URL.
You can provide signing material in the app create/edit flow by:
- pasting PEM certificate and private key values
- or using Generate Test Keypair for a self-signed testing certificate
If a SAML app does not have signing material configured, unsigned metadata still works and the signed metadata endpoint returns 400.
- Click Test on your app instance card
- Click the Login with OIDC/SAML button
- Authenticate at your IdP
- View the inspector page with lifecycle, validation, trace, claims diff, compliance, raw payload, and protocol-specific trust diagnostics
For OIDC apps, the test workbench also supports:
- Browser login
- Client credentials
- Device authorization
- Token exchange
For SAML apps, the inspector now also highlights:
- signature structure and certificate matching details
- IdP signing certificate subject, fingerprint, and expiry posture
- assertion/compliance posture across Conditions, SubjectConfirmation, SLO, and signing configuration
For app-level provisioning tests, the app detail page exposes:
- SCIM base URL
- SCIM discovery endpoints
- app-scoped bearer token
- recent SCIM resources and request logs
npm run build
npm startAuthLab is deployed on Vercel with Turso as the production database (SQLite-compatible, works in serverless environments).
-
Install the Turso CLI and create a database:
brew install tursodatabase/tap/turso turso auth login turso db create authlab turso db tokens create authlab
-
Push the schema to Turso:
export TURSO_DATABASE_NAME=authlab ./scripts/ci/apply-turso-migrations.sh
Set environment variables on Vercel using printf (not echo, which adds trailing newlines that break auth tokens):
printf 'libsql://your-db.turso.io' | vercel env add TURSO_DATABASE_URL production
printf 'your-turso-auth-token' | vercel env add TURSO_AUTH_TOKEN production
printf 'file:/tmp/dummy.db' | vercel env add DATABASE_URL production
printf 'your-64-char-hex-key' | vercel env add MASTER_ENCRYPTION_KEY production
printf 'your-64-char-hex-password' | vercel env add SESSION_PASSWORD production
printf 'https://your-app.vercel.app' | vercel env add NEXT_PUBLIC_APP_URL productionOnly needed if you want to prepare PEM material outside AuthLab before uploading it to a SAML app instance.
Generate an SP key pair:
openssl genrsa -out saml-sp-private-key.pem 2048
openssl req -new -x509 \
-key saml-sp-private-key.pem \
-sha256 -days 3650 \
-subj "/CN=authlab.keydatalabs.ca/O=KeyDataLabs/C=CA" \
-out saml-sp-public-cert.pemDeploy:
vercel --prodAfter deployment, upload the PEM pair into the target SAML app instance or use the in-product test keypair generator.
AuthLab currently uses a three-branch release model:
main— integration branchalpha— staged alpha release branchbeta— staged beta release branch
Suggested flow:
- Merge and stabilize work on
main. - Cut or fast-forward
alphafrom themaincommit you want to release as the alpha line. - Advance
betafrom the tested beta line you want to stage separately. - Tag immutable releases from those branches, for example:
v0.1.0-alphav0.2.0-beta
- Deploy from
alphaorbeta, not directly frommain. - Roll back by redeploying the previous tagged commit or prior branch head.
This keeps main as integration while making deploy and rollback decisions branch- and tag-based.
This repo now includes:
.github/workflows/ci.ymlfor pull request and merge queue validation.github/workflows/deploy-production.ymlfor pre-deploy verification, Turso migrations, and production release.github/workflows/nightly-performance.ymlfor nightly auth-path latency baselines and artifact reporting
The pull request gate runs:
npm cinpm run lintnpm run typechecknpm run test:unitnpm run test:integrationnpm run test:securitynpm run prisma:validatenpm run build:cinpm run test:e2e
The deploy workflow now:
- Installs dependencies and runs
lint,typecheck, andprisma validate - Pulls project settings and validates required production env vars
- Builds Vercel artifacts before any production migration runs
- Applies pending SQL files from
prisma/turso-migrations/to Turso - Deploys the verified prebuilt artifact with
--prebuilt
Required repository secrets:
TURSO_API_TOKENTURSO_DATABASE_NAME(example:authlab)VERCEL_TOKENVERCEL_ORG_IDVERCEL_PROJECT_ID
Each Turso migration file is applied exactly once and tracked in _authlab_schema_migrations.
prisma/turso-migrations/0001_init.sql is the baseline schema for greenfield deployments.
Do not delete historical migration files after deployment; keep them committed for replay/recovery.
The repo includes .vercelignore with .next to prevent uploading local Next.js build output.
For the full gate design and branch protection recommendations, see docs/ci-cd-testing-strategy.md.
Prisma CLI doesn't support libsql:// URLs. After editing prisma/schema.prisma:
# Apply locally
npx prisma db push
# Generate an incremental SQL migration file for Turso
npx prisma migrate diff --from-config-datasource --to-schema prisma/schema.prisma --script > prisma/turso-migrations/<timestamp>_<change>.sql
# Push to deploy
git add . && git commit -m "update schema" && git pushImportant notes:
- Keep all files in
prisma/turso-migrations/in git; each file is applied once and then tracked. --from-config-datasourcereads from the datasource inprisma.config.ts(local SQLite in this project). If local SQLite is ahead of Turso, the generated diff can be empty even when Turso is missing tables/columns.- Always verify production schema directly in Turso before closing a deployment.
Production verification checklist:
- Confirm key tables exist:
turso db shell authlab ".tables" - Confirm expected columns for changed tables:
turso db shell authlab ".schema SystemSetting" - Re-test affected flows against production (for example: user registration, admin settings).
If production is missing SystemSetting, apply this emergency SQL:
CREATE TABLE IF NOT EXISTS "SystemSetting" (
"key" TEXT NOT NULL PRIMARY KEY,
"value" TEXT NOT NULL,
"updatedAt" DATETIME NOT NULL
);Then verify:
turso db shell authlab ".schema SystemSetting"If production still has legacy AppInstance without teamId, run:
turso db shell authlab < prisma/production-reconcile-2026-03-05.sqlWhat this does:
- Creates
SystemSettingif missing. - Creates a legacy team (
legacy_migration_team) if missing. - Rebuilds
AppInstancewith requiredteamId+ foreign key toTeam. - Preserves existing
AppInstancerows by assigning them to the legacy team. - On first user registration, AuthLab automatically reassigns legacy-team apps to that first user's personal team.
Post-checks:
turso db shell authlab ".schema AppInstance"
turso db shell authlab "PRAGMA table_info(\"AppInstance\");"
turso db shell authlab "PRAGMA foreign_key_list(\"AppInstance\");"authlab/
├── prisma/
│ └── schema.prisma # Data model (AppInstance + Protocol enum)
├── prisma.config.ts # Prisma 7 config (always uses local SQLite for CLI)
├── next.config.ts # Next.js config (serverExternalPackages for native modules)
├── src/
│ ├── app/ # Next.js App Router pages and API routes
│ ├── lib/ # Core libraries (auth, encryption, session, DB)
│ ├── repositories/ # Data access layer with transparent encryption
│ ├── components/ # React components (ui, layout, apps, inspector)
│ ├── types/ # TypeScript interfaces
│ └── generated/ # Prisma-generated client (not committed)
├── CLAUDE.md # AI assistant context
├── AGENTS.md # Agent role definitions
└── .env.example # Environment variable template
MIT