An open-source, self-hostable identity server that speaks the Auth0 Authentication API. Point an existing Auth0 SDK at a zeroauthy tenant domain and it works with no code changes.
zeroauthy is a clean-room implementation built from public documentation and open standards. It is not affiliated with Auth0 or Okta. See CLEAN_ROOM.md.
OpenID Certified. zeroauthy is
certified by the OpenID Foundation
as an OpenID Connect Provider for the Basic OP, Config OP and Form Post OP
profiles (commit 38b4229, October 2026). See docs/conformance.md.
Status: 1.0 early preview. zeroauthy covers what most Auth0 tenants use, but not everything yet. Try it, and check the compatibility page for the gaps (WebAuthn and passkeys, SAML, enterprise directories and more) before relying on it.
Use it for testing. zeroauthy also makes a disposable, Auth0-compatible login server for end-to-end tests: one container seeded with known tenants, apps and users, so your test suite can run real logins with your Auth0 SDK locally and in CI. See the testing guide and the kit in examples/testing.
What works in 1.0
- The Authentication API and hosted login: authorization code with PKCE, refresh token rotation, client credentials, password grants, PAR, the device flow, logout, consent, signup and password reset
- MFA (one-time passwords, email, SMS, voice, recovery codes) and passwordless email and SMS (MFA, passwordless)
- Database, custom database, Google, Microsoft and generic OAuth 2 / OIDC connections
- The Management API for applications, APIs, connections, users, roles, organizations, Actions, branding, email and phone, custom domains, logs and event streams (Management API)
- An admin console (console), SQLite, PostgreSQL, MySQL and gRPC storage plugins
- Verified with auth0-spa-js, auth0-react, nextjs-auth0, express-openid-connect, auth0-auth-js, node-auth0, Auth0.NET, the Auth0 CLI and the Deploy CLI
Start here: quickstart (container to SDK login), deployment, upgrading, migrating from Auth0, stability promise, security policy and the changelog.
zeroauthy aims to be a 1:1, drop-in replacement for Auth0's Authentication API and Management API; the compatibility page lists what is still missing. The Authentication API side is complete apart from what the features below add. The Management API is being built resource by resource, each with its console screens; the full breakdown is in the Management API roadmap.
Done (Plans 1–34)
- Core server, signing keys, discovery and JWKS, hosted login and signup, authorization code with PKCE, client credentials, password grants, rotating refresh tokens, logout, password reset, hosted-page languages
- Storage: memory, SQLite, PostgreSQL, MySQL, gRPC storage plugins
- Custom database connections (Auth0 scripts, import mode)
- Consent and third-party applications, PAR, device flow
- OpenID Certified (Basic, Config, Form Post OP); conformance suite in CI (those plus RP-Initiated Logout)
- Management API and console: applications, APIs, client grants, database connections, users (with search, identity linking, sessions, refresh tokens, grants and tickets), user import/export jobs and verification email
- Tenant email providers and templates, signup welcome and security notices; see docs/email.md for active workflows and stored-only limits
Tenant settings and attack protection (Plan 19)
Signing keys and deployment migration (Plan 20)
- Managed signing keys, stored-only custom public JWKS, and credential-preserving bootstrap are implemented.
- The pinned Deploy CLI passes local YAML/directory imports with generated or preserved credentials, including organizations, Actions and event streams. Database connections remain excluded from that matrix, and Guardian factor providers cannot be exported because of a bug in the Deploy CLI itself (MFA); the upstream observer lane remains unavailable. See signing keys and Deploy CLI migration for current limits.
Branding and Universal Login (Plan 22)
- Tenant branding, themes, bounded page templates, identifier-first login, localized text and transient form partials; console editors with isolated preview. See branding and the implementation record for supported syntax and compatibility limits.
Custom domains and automatic certificates (Plan 23)
- Verified domain ownership, shared automatic TLS, authenticated customer proxies, domain-bound login and notification links, and console controls. See the operator guide and acceptance record.
Roles, connections and Actions (Plans 24–28)
- Generic OAuth/OIDC, Google and Microsoft connections; roles and permissions.
- Actions: login and credentials exchange, signup, password reset/change and custom email triggers; immutable Modules, durable jobs, console and migration tooling. Limits and verification are in the Plan 28 implementation record.
Organizations I (Plan 29)
- Organizations, members, role assignments, enabled connections, hosted prompt and picker, claims/refresh checks, branding, Actions context and scoped operators. See Organizations and the verification record.
Organizations II (Plan 30)
- Invitations, organization application grants, verified discovery domains and console controls. See Organizations and the implementation record.
Logs, log streams and statistics (Plan 31)
- Searchable tenant logs, live console views, Action execution logs, outbound log streams, daily statistics and active-user counts. See Logs for supported destinations and privacy controls.
Event streams (Plan 32)
- Transactional CloudEvents for user, organization and enterprise-connection
changes; guarded webhooks and durable
event-streamActions; retries, failed-delivery inspection, redelivery and auto-disable; resumable SSE, console controls, seed and Deploy CLI support. - EventBridge configurations are stored disabled, and group events await Plan 38. See Event streams for payloads, retention and other limits, and the validation report for actual results, skips and conformance outcomes.
MFA (Plan 33)
- One-time passwords, email codes, recovery codes, MFA policies,
remember-browser, hosted MFA prompts, the MFA API and grants, Guardian and
user authentication-method APIs, and Actions
multifactor/challengeWith*. WebAuthn and passkeys move to Plan 33b. See MFA.
Passwordless and phone (Plan 34)
- Email and SMS passwordless connections, magic links and the OTP grant, phone providers (Twilio, Amazon SNS, custom through Actions) and templates, SMS and voice MFA, phone identifiers and phone password reset. See passwordless and phone providers.
Later (Plans 33b, 35–48)
| Plan | Area | Depth | Needs |
|---|---|---|---|
| 33b | WebAuthn MFA and passkeys | full | 33 |
| 35 | Client credentials: private_key_jwt, JAR, token exchange profiles |
short | 27 |
| 36 | Remaining enterprise OIDC: oidc, okta, Entra extras, connection keys (waad sign-in moves to 25) |
short | 24, 25 |
| 37 | SAML and WS-Federation (sub-project 4), SCIM, self-service SSO profiles | full | 36 |
| 38 | AD/LDAP connector, Forms and Flows, network ACLs, remaining resources | full | 27, 37 |
| 39 | Additional social providers (Apple, Facebook, GitHub, …) | short (Apple full) | 24 |
| 40 | Actions III: newer triggers, Early Access members, /userinfo claims |
short | 28, 22 |
| 41 | OIDC protocol extras, CIBA, formal OIDF certification | full | 33, 35 |
| 42 | My Account API and My Organization API | full | 30, 33 |
| 43 | Token Vault and connected accounts | full | 24, 35 |
| 44 | ACUL and the remaining Universal Login modes | full | 22, 29, 33 |
| 45 | Operational hardening sweep | short | — |
| 46 | Release pipeline, packaging, samples | short | 45 |
| 47 | Hosted zeroauthy (multi-tenant SaaS) | full | 29, 31, 46 |
| 48 | Refresh tokens II: rotation conversion, strict leeway, grant-revocation cascade, multi-resource and session-bound tokens | short | 17, 3 |
The roadmap tracks 42 resource groupings; its coverage table records completed and remaining work. Four groups are not planned (Rules, Hooks and rules-configs, which Auth0 is retiring, and customer-managed encryption keys).
Run the container image (it includes Node.js for Actions and stores SQLite in
the /data volume):
docker run -d --name zeroauthy -p 8443:8443 -v zeroauthy-data:/data \
-v "$PWD/certs:/certs:ro" \
-e ZEROAUTHY_SERVER__LISTEN=:8443 \
-e ZEROAUTHY_SERVER__SYSTEM_HOST=system.localhost:8443 \
-e ZEROAUTHY_SERVER__TLS_CERT_FILE=/certs/cert.pem \
-e ZEROAUTHY_SERVER__TLS_KEY_FILE=/certs/key.pem \
-e ZEROAUTHY_SECURITY__MASTER_KEY="$(cat master.key)" \
ghcr.io/occupysaas/zeroauthy:1.0.0-rc.1
docker exec zeroauthy zeroauthy admin create --email you@example.comThe quickstart walks through the certificate and master key, the first tenant, application and user, and a login with express-openid-connect. Release binaries for linux/amd64, linux/arm64 and darwin/arm64 are on the releases page. For a server, start from examples/zeroauthy.yaml and deployment.
This runs the server with the end-to-end test configuration
(tests/e2e/config.yaml: memory storage, debug logging, published test
secrets). It is for development, not for real users. It needs Go 1.26.10.
go build -o bin/zeroauthy ./cmd/zeroauthy
# Self-signed certificate for localhost and the console's system host
# (SDKs require HTTPS).
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,DNS:system.localhost" \
-keyout key.pem -out cert.pem
ZEROAUTHY_SERVER__TLS_CERT_FILE=cert.pem ZEROAUTHY_SERVER__TLS_KEY_FILE=key.pem \
./bin/zeroauthy serve --config tests/e2e/config.yamlThen:
curl --cacert cert.pem https://localhost:8443/.well-known/openid-configuration
curl --cacert cert.pem https://localhost:8443/oauth/token \
-d grant_type=client_credentials -d client_id=e2e-m2m \
-d client_secret=e2e-m2m-secret-not-for-production -d audience=https://api.e2e.localWith the quick-start server running, open:
https://localhost:8443/authorize?client_id=e2e-spa&response_type=code&redirect_uri=https://localhost:3443/&scope=openid%20profile&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&state=demo
and sign in as alice@e2e.local / Correct-Horse-Battery-9 (test data from
tests/e2e/seed.yaml). You are redirected to https://localhost:3443/?code=….
The console is served only on the system tenant's host, not on localhost.
With the quick-start config (system_host: system.localhost:8443) it is at
https://system.localhost:8443/admin/. Browsers resolve *.localhost to
127.0.0.1; for other clients add 127.0.0.1 system.localhost to /etc/hosts.
The quick-start config stores everything in memory, so the first operator
must be created by the server itself: start it with
ZEROAUTHY_BOOTSTRAP__ADMIN_EMAIL set.
ZEROAUTHY_SERVER__TLS_CERT_FILE=cert.pem ZEROAUTHY_SERVER__TLS_KEY_FILE=key.pem \
ZEROAUTHY_BOOTSTRAP__ADMIN_EMAIL=you@example.com \
./bin/zeroauthy serve --config tests/e2e/config.yamlThe server writes the one-time setup link to an owner-only (0600) file in the
host's temporary directory and logs its path as setup_file, plus the operator
email and one-hour validity. Read that private file on the server, open the link,
and choose a password of at least 12 characters. Delete the file after use, then
sign in at https://system.localhost:8443/admin/. Server shutdown leaves the
file available for delivery. Memory storage is lost on restart, and so is the
operator; the next start creates a new private setup file.
zeroauthy admin create --email you@example.com --config <file> does the same
from another terminal, but only with persistent storage (SQLite, PostgreSQL,
MySQL): it opens the database directly, so with memory:// its link points at
data the server never sees.
The built console (console/dist) is embedded in the binary at compile time.
After changing the console, run make console and then rebuild the binary
(make build or the go build above); a running server keeps the console it
was built with. See docs/console.md for the screens.
API reference: https://localhost:8443/docs. The raw contract is at
/openapi.yaml, and its source is api/authentication.yaml.
YAML file (--config) plus ZEROAUTHY_* environment variables. __
separates levels: ZEROAUTHY_STORAGE__DSN=memory:// sets storage.dsn.
See the configuration reference for every
setting. Tenants, APIs, applications and grants can come from a declarative
seed file; tenants and applications can also be
managed in the console or through the
Management API. The seed never touches what the
console or the API created. By default the seed is applied at every start and
overwrites what it defines. Set seed_mode: create_only
(ZEROAUTHY_SEED_MODE=create_only) to have it only create what does not
exist yet (tenants, APIs, applications, grants, connections, users) and change
nothing that does, so edits made in the console survive a restart. In that
mode the console shows no "defined by the seed" banner. zeroauthy seed
follows the same setting.
Unknown configuration keys are rejected at startup.
Actions. Configure the actions section or ZEROAUTHY_ACTIONS__*.
Defaults: node: node, bundled npm_cli,
registry: https://registry.npmjs.org/,
cache_dir: <user cache dir>/zeroauthy/actions-cache, max_memory_mb: 256,
idle_timeout: 5m, build_timeout: 2m, max_artifact_mb: 50 (range 1–50),
and max_concurrent_builds: 2. Explicit npm/cache paths must be absolute;
npm_cli points to bin/npm-cli.js. Node ≥22.13 runs Actions; npm and the
registry are needed only on build nodes. Set cache_dir explicitly for
services and registry to an HTTP(S) offline mirror when needed. See the
Actions operator guide for dependency restrictions, replica
artifacts, seed ownership, sandbox limits and hardening.
Event streams. The events section configures retention (default 168h,
minimum 24h), the stream quota (default 10 per tenant) and concurrent SSE
connections (default 4 per tenant per server instance). Webhook destinations
require HTTPS and refuse private-network targets by default. See
Event streams for deployment settings, migration and
plugin compatibility.
Master key. Signing keys and client secrets are stored envelope-encrypted under a 32-byte
master key. Generate one with openssl rand -base64 32 and set it as
ZEROAUTHY_SECURITY__MASTER_KEY (or point security.master_key_file at a
file containing it). It is required for persistent storage; with
memory:// storage zeroauthy uses an ephemeral key and logs a warning.
The master key cannot be rotated in 1.0, and a database backup is useless
without it: back it up separately (deployment).
Storage. storage.dsn selects the driver:
| DSN | Driver |
|---|---|
memory:// |
In memory; lost on exit (tests, demos) |
sqlite:///var/lib/zeroauthy/zeroauthy.db |
SQLite (pure Go, no CGO); sqlite://relative.db is relative to the working directory |
postgres://user:pass@host:5432/db?sslmode=require |
PostgreSQL 14+ (postgresql:// also works); the database needs a UTF-8 LC_CTYPE (see below) |
mysql://user:pass@host:3306/db |
MySQL 8.0.17+ (MariaDB is not supported) |
plugin:///abs/path/to/binary?arg=x&arg=y |
External gRPC storage plugin; each arg becomes one argument of the binary. Needs security.master_key |
PostgreSQL locale. Create the database with a UTF-8 LC_CTYPE (for
example CREATE DATABASE zeroauthy LC_CTYPE 'C.UTF-8' TEMPLATE template0;
the official container images default to en_US.utf8). Under C or POSIX
PostgreSQL's lower() folds ASCII letters only, so case-insensitive user
search and sort would miss non-ASCII matches (Émilie vs émilie) that the
other drivers find. zeroauthy logs a warning at startup in that case and
still runs.
Writing a storage plugin. A plugin is a Go program that calls
pkg/storeplugin.Serve with any store.Store; cmd/zeroauthy-store-memory
is a minimal template. Check your driver by running
storetest.Run (package pkg/storetest) with a factory that calls
storeplugin.Open on your built binary. Transactions work across the
boundary; see decision D-0021 for the protocol.
Plan 20 adds result-returning rotation/revocation, certificate compare-and-set,
and the separate custom public-key repository to the storage contract. Rebuild
storage plugins against the current pkg/storeplugin; missing lifecycle RPCs
fail closed with an upgrade diagnostic. Transaction views and tenant deletion
must cover both managed keys and custom sets; run pkg/storetest conformance.
The SQL drivers have a schema. Apply pending migrations with
zeroauthy migrate --config zeroauthy.yaml before starting a new version,
or set storage.auto_migrate: true (ZEROAUTHY_STORAGE__AUTO_MIGRATE=true)
to have serve migrate at startup. Without it, serve refuses to start
against a schema that is behind or ahead of the binary. zeroauthy seed
requires a migrated database and ignores storage.auto_migrate. Back up the
database before migrating. MySQL commits DDL implicitly, and most early
migrations cannot resume after a partial failure: if a MySQL migration fails,
restore the backup instead of rerunning it. See upgrading.
System tenant and operators. zeroauthy keeps a reserved tenant named
system for itself: its users are the console operators. It is reachable at
system.<server.base_domain>, or at server.system_host, which is required
when server.base_domain is empty (for example system.localhost:8443). A server.system_host under the base domain must be exactly
system.<server.base_domain>. The
server creates it at every start; no other tenant may use the name system
or that host. Create an operator with
zeroauthy admin create --email you@example.com --config zeroauthy.yaml,
which prints a one-time setup link (valid for one hour) where the operator
chooses a password. Automated installs may set
ZEROAUTHY_BOOTSTRAP__ADMIN_EMAIL instead: on a start that finds no
operator, the server creates that one and writes its one-hour setup link to a
private 0600 file in the host's temporary directory. The server log contains only
the setup_file path, email and validity. Read the file on the server and delete
it after use; shutdown does not remove it. If file delivery fails after the
operator was persisted, use admin create for that email to issue a fresh link.
No password is ever taken from configuration. Operators sign in to the
admin console at /admin/ on that host.
Proxies. Enable server.trust_proxy_headers only behind a proxy that
sets X-Forwarded-Host, and list that proxy in server.trusted_proxy_cidrs;
otherwise clients could choose their own tenant. See
deployment.
Request rate limits. security.rate_limit limits every tenant
Authentication API route with a token bucket per tenant, client IP (IPv6 by
/64) and endpoint group: token (/oauth/token), authorize, userinfo,
discovery (/.well-known/*, JWKS included) and other (PAR, device code,
revoke, /dbconnections/*, /passwordless/start, /mfa/*, logout and the
hosted pages). Defaults: enabled: true, rate: 50 (requests per second)
and burst: 200 for each group, max_tracked_keys: 50000 buckets per group,
and max_concurrent_secret_verifications: 0 (GOMAXPROCS, at least 4: legacy
argon2id client secret checks running at once; this cap applies even when the
limiter is disabled). Set a group with, for example,
ZEROAUTHY_SECURITY__RATE_LIMIT__TOKEN__RATE=20. The counters are per server
process; see attack protection.
jobs.workers defaults to 2; set it to 0 to disable workers on a process
while another process handles the shared database queue. jobs.lease
defaults to 60s (minimum 1s); workers renew every third of the lease.
Environment overrides are ZEROAUTHY_JOBS__WORKERS and
ZEROAUTHY_JOBS__LEASE. All serving processes must share the same storage
and master key for job reads and export downloads. See
the import/export guide for limits and reset advice.
A database connection can run your own Node.js scripts (login, get_user,
create, change_password) against an existing user store, either as the
permanent source of truth or as an import that migrates users on first
login. Scripts written for Auth0 run as written (native addons and a few
blocked built-ins such as child_process are the known differences) and
need Node.js 22.13 or later. See docs/custom-db.md, including the security
notes for hosting untrusted tenants.
Releases are manual: a tagged commit pushed to the release branch, then a maintainer's approval in GitHub Actions. See RELEASING.md for the short checklist and the full walkthrough.
make test # unit + contract + storage conformance tests (memory and SQLite)
make test-sql # SQL driver tests against PostgreSQL 17 and MySQL 8.4 containers (needs podman or docker)
make generate # regenerate code from api/authentication.yaml and api/management.yaml
make console # lint, test and rebuild the admin console (needs Node 22+; then make build to embed it)
make e2e # Auth0 SDKs against a live zeroauthy on SQLite (needs Node 22+, openssl)
make e2e-events # Event Streams browser, SDK, CLI, SSE and privacy acceptance
make e2e-deploy # pinned Deploy CLI YAML/directory round trips
make check-sources # verify every x-source provenance link
make conformance # OpenID conformance suite against zeroauthy (podman or docker, Linux; ~10-20 min)zeroauthy is OpenID Certified for the Basic OP, Config OP and Form Post OP profiles (commit 38b4229, approved 2026-10-30). It is also tested against the RP-Initiated Logout OP profile, which OIDF certifies only alongside a session, front-channel or back-channel logout profile; back-channel logout is planned (Plan 41). make conformance runs all four plans locally in containers (Linux, podman or docker). Local results pass, with a few accepted warnings and skips. See docs/conformance.md.
