auth0-mock

module
v0.229.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 27, 2026 License: MIT

README ยถ

auth0-mock

A drop-in mock of Auth0's HTTP API (Authentication + Management) that you can point any Auth0-using service at, with no code changes.

Real RS256 JWTs. 400+ Management API endpoints. Runtime claim and permission injection. MFA, PKCE, OIDC discovery. HTTP and HTTPS.

Quick start ยท What's mocked ยท Recipes ยท Architecture ยท Contributing

CI CodeQL Go Report Card codecov Go Reference OpenSSF Scorecard OpenSSF Best Practices Signed releases govulncheck Release Go version GHCR Go module Conventional Commits PRs welcome


๐Ÿ“‘ Table of contents


โœจ What is this?

A self-contained Go service that looks and behaves like Auth0 to a calling client:

  • ๐ŸŽซ Mints real RS256 JWTs signed with an in-process key, and publishes the matching JWKS at /.well-known/jwks.json. Consumer SDKs validate signatures normally โ€” no InsecureSkipVerify, no fake-token kludges.
  • ๐Ÿ“ฆ Covers the whole Management API by embedding a stripped skeleton of Auth0's OpenAPI 3.1 document (~400 operations: paths, methods, and schemas; Auth0's prose removed) and routing every endpoint through one generic handler. You stub responses by POSTing {method, path, response} to /admin0/expectations, and the OpenAPI schema validates the stubbed body at registration time. An optional request matcher lets you register multiple responses per operation; resolution is 4-tier (exact-path beats template-path; within a path, a request-matched expectation beats a catch-all; newest wins within a tier).
  • ๐Ÿ›  Shapes runtime state over HTTP: custom JWT claims, per-audience permissions, and the MFA-required flag are mutable mid-test through /admin0/* endpoints. No restart, no config-file juggling.
  • ๐Ÿณ Ships as a single static binary (~13 MB) or a small Docker image. Sub-second boot, both HTTP (:8080) and HTTPS (:8443) by default.

โ†‘ Back to table of contents

๐ŸŽฏ Who is this for?

Anyone whose service talks to Auth0 in tests or local dev:

  • CI pipelines that need deterministic Auth0 responses without burning rate limit on a real tenant.
  • Local dev loops where you don't want to share an Auth0 tenant or wait on its latency.
  • Integration test suites for Auth0 SPA / native / API SDKs (auth0-react, auth0-js, auth0-spa-js, auth0-android, auth0-swift, auth0-react-native, etc.).
  • Resilience tests for code paths that hit /api/v2/users, /api/v2/clients, /api/v2/roles, etc.
  • Service-to-service flows using client_credentials, with realistic scopes and permissions claim shapes.

It is not for: production traffic, replacing your IdP, or anything that needs a real RBAC engine.

โ†‘ Back to table of contents

๐Ÿš€ Quick start

# latest stable
curl -fsSL https://raw.githubusercontent.com/sergiught/auth0-mock/main/install.sh | bash

# pinned version
curl -fsSL https://raw.githubusercontent.com/sergiught/auth0-mock/main/install.sh | bash -s v0.227.0

# install to a user-writable dir (no sudo)
BIN_DIR="$HOME/.local/bin" bash <(curl -fsSL https://raw.githubusercontent.com/sergiught/auth0-mock/main/install.sh)

The script downloads the goreleaser-built archive, verifies its sha256 against the release's checksums.txt, and installs the binary as auth0-mock. Source is at install.sh โ€” review before piping to bash if that bothers you.

From source
make build && ./bin/auth0-mock
Via go install
go install github.com/sergiught/auth0-mock/cmd/api@latest
$(go env GOPATH)/bin/api -version    # installs as `api`, not `auth0-mock`

install.sh and make build are still the recommended paths because they stamp the binary with version / commit / date (visible via auth0-mock -version) and install it as auth0-mock. go install is here for Go developers who'd rather rebuild from source every time and don't mind the cmd-package naming.

Live-reload dev loop (air)

Sub-second rebuild on every save under cmd/ or internal/, no docker, no bind-mounts, no flakiness:

make watch     # installs air into ./bin on first run
Docker
docker compose up -d --build
docker compose logs -f auth0-mock

docker compose builds from the local dev Dockerfile (Go toolchain + source) for fast --build iteration. The release pipeline uses a separate slim Dockerfile.release (binary-only, fed by goreleaser) โ€” ghcr.io/sergiught/auth0-mock:vX.Y.Z and docker.io/sergiught/auth0-mock:vX.Y.Z are what hits Docker Hub.

Smoke test
# 1. Mint a real signed access token
TOKEN=$(curl -s -X POST http://localhost:8080/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials&client_id=demo&client_secret=x&audience=http://localhost:8080/api/v2/' \
  | jq -r .access_token)

# 2. Stub a Management API response
curl -X POST http://localhost:8080/admin0/expectations \
  -H 'Content-Type: application/json' \
  -d '{"method":"GET","path":"/api/v2/users/auth0|123","response":{"status":200,"body":{"user_id":"auth0|123","email":"alice@example.com"}}}'

# 3. Call the stubbed endpoint with your bearer
curl http://localhost:8080/api/v2/users/auth0%7C123 \
  -H "Authorization: Bearer ${TOKEN}"
# => {"user_id":"auth0|123","email":"alice@example.com"}

Your code calls auth0-mock the same way it calls Auth0. No SDK shims, no monkey-patching.

โ†‘ Back to table of contents

๐Ÿ“ฎ Calling the API from Postman or Insomnia

[!TIP] Prefer a browser? Run the mock and open http://localhost:8080/docs for an interactive reference rendered by Scalar. Every endpoint is clickable, and "Try it" points at the same instance that served the page.

The mock ships a merged OpenAPI 3.1 document that covers every HTTP surface it exposes:

  • The Auth0 Authentication API (/oauth/token, /authorize, /userinfo, /v2/logout, /dbconnections/*, /passwordless/*).
  • The Auth0 Management API (everything under /api/v2). Canned responses are programmed centrally via POST /admin0/expectations.
  • The admin0 control plane (/admin0/*).
  • The service endpoints (/healthz, /.well-known/jwks.json, /openapi.json, /openapi.yaml).
Importing
  • From the repo: import api/auth0-mock.openapi.json directly.
  • From a running instance: point your client at http://localhost:8080/openapi.json (or /openapi.yaml).

Both Postman and Insomnia will create a folder per tag (auth-api, admin0, service, plus the Management API's existing tags) and fill in request bodies from the schemas.

Regenerating the spec

The merged JSON is committed and checked for drift in CI. Re-run make openapi after editing any of the auth0-mock-authored fragments:

  • internal/authapi/authapi.openapi.yaml
  • internal/admin0/admin0.openapi.yaml
  • internal/router/service.openapi.yaml

api/auth0-management-api.openapi.json is a generated skeleton, not a hand-edited file. To pull in a newer Auth0 Management API spec, run make refresh-spec (see CONTRIBUTING.md).

โ†‘ Back to table of contents

๐Ÿ“‹ What's mocked

๐ŸŽซ Authentication API (hand-coded, fully functional)
Endpoint Method Notes
/oauth/token POST All Auth0 grants (see table below)
/oauth/revoke POST 200 no-op (mock doesn't track refresh state)
/authorize GET 302 with code (or implicit token); stashes PKCE challenge if present
/userinfo GET Returns claims from the bearer
/v2/logout GET 302 to returnTo
/.well-known/jwks.json GET Real JWKS for the in-process signing key
/.well-known/openid-configuration GET OIDC discovery rooted at the configured issuer
/dbconnections/signup POST Returns {_id, email, email_verified:false}
/dbconnections/change_password POST Returns the canned reset-email message
/passwordless/start POST Returns {_id, email, phone_number}
/passwordless/verify POST Mints token if otp=000000
๐Ÿ”‘ OAuth grants supported
grant_type Notes
client_credentials M2M flow, returns access_token only
password Returns access + id + refresh; gates on the MFA flag
refresh_token New access_token; no refresh state tracked
authorization_code Returns access + id; enforces PKCE if challenge was set at /authorize
http://auth0.com/oauth/grant-type/password-realm Auth0 Native SDKs; same as password + realm field threaded into claims
http://auth0.com/oauth/grant-type/passwordless/otp Mints if otp=000000
http://auth0.com/oauth/grant-type/mfa-otp Step 2 of MFA dance; accepts otp=123456
http://auth0.com/oauth/grant-type/mfa-oob Push/SMS step-up; accepts binding_code=123456
http://auth0.com/oauth/grant-type/mfa-recovery-code Recovery flow; accepts recovery_code=ABCDEFGHIJKLMNOP

[!IMPORTANT] Audience is echoed, not enforced. The mock mints tokens with whatever audience you ask for (falling back to DEFAULT_AUDIENCE) and the bearer middleware verifies signature + expiry + issuer but not that the audience matches anything client-side. This is deliberate โ€” tests need to swap audiences freely. Real Auth0 does enforce audience against the client's registered APIs; if your downstream service relies on aud checks, you'll need to add your own assertion in test fixtures.

๐Ÿ“ฆ Management API (spec-driven, ~400 endpoints)

Every operation in the embedded Auth0 Management API skeleton is mounted. Default response is 404 no_match. Tests register stubs:

# Concrete-id stub
curl -X POST http://localhost:8080/admin0/expectations \
  -H 'Content-Type: application/json' \
  -d '{"method":"GET","path":"/api/v2/users/auth0|123","response":{"status":200,"body":{"user_id":"auth0|123","email":"alice@x"}}}'

# Template stub (catch-all for any user id)
curl -X POST http://localhost:8080/admin0/expectations \
  -H 'Content-Type: application/json' \
  -d '{"method":"GET","path":"/api/v2/users/{id}","response":{"status":200,"body":{"user_id":"auth0|*","email":"any@x"}}}'

[!NOTE] Concrete-path stubs win over template stubs at request time. The optional request matcher (subset-matched query + body) lets you register multiple responses per operation; resolution is 4-tier (exact-path beats template-path; within a path, a request-matched expectation beats a catch-all; newest wins within a tier). response.body is validated against the operation's response schema at registration time. Invalid bodies are rejected with 400 invalid_match, unknown operations with 400 unknown_operation, unparseable or incomplete requests with 400 invalid_body, and invalid request matcher fields (unknown fields, mistyped values, unknown query parameters) with 400 invalid_request_match.

๐Ÿ“ก Event streams

GET /api/v2/events is a real Server-Sent Events endpoint. Tests push events through POST /admin0/events; every connected subscriber sees them in real time. The mock keeps a bounded replay buffer (default 100 events, configurable via EVENTS_REPLAY_BUFFER) so reconnecting subscribers can resume via Last-Event-ID, ?from=<id>, or ?from_timestamp=<rfc3339> and the library's native replay path fills in what they missed.

# In one terminal: subscribe (bearer required).
TOKEN=$(curl -sX POST http://localhost:8080/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials&client_id=t&client_secret=t&audience=https://localhost:8443/api/v2/' \
  | jq -r .access_token)
curl -N -H "Authorization: Bearer $TOKEN" \
     'http://localhost:8080/api/v2/events?event_type=user.created'

# In another: push.
curl -X POST http://localhost:8080/admin0/events \
  -H 'Content-Type: application/json' \
  -d '{
    "type":"user.created","offset":"0",
    "event":{
      "specversion":"1.0","type":"user.created","source":"https://auth0.local/",
      "id":"evt_aaaaaaaaaaaaaaaa","time":"2026-05-19T00:00:00Z",
      "a0tenant":"my-tenant","a0stream":"est_aaaaaaaaaaaaaaaa",
      "data":{"object":{"user_id":"u-1","created_at":"2026-05-19T00:00:00Z","updated_at":"2026-05-19T00:00:00Z","identities":[]}}
    }
  }'

The subscriber receives id: evt_aaaaaaaaaaaaaaaa / event: user.created / data: {...}. Comment frames (:keep-alive) arrive every 15s so reverse-proxy idle timeouts don't drop the connection.

Errors are deliberately specific: schema violations โ†’ 400 invalid_event with a one-line "/json/pointer": reason list; unknown ?from_timestamp โ†’ 400 invalid_from_timestamp; aged-out Last-Event-ID โ†’ 410 event_aged_out (matches the 410 in Auth0's OpenAPI).

To set expectations and verify connection lifecycle, GET /admin0/events/subscribers reports {"active":N,"total":M} โ€” active is how many subscribers are connected right now, total how many have connected since the last /admin0/reset (handy for asserting reconnection behaviour). active is eventually-consistent: the mock removes a subscriber only when it observes the connection close, which can lag a client's disconnect by a few milliseconds. So to assert "my stream closed cleanly", poll until active settles rather than reading it immediately:

# After closing the consumer, wait for the count to drain to 0.
until [ "$(curl -s http://localhost:8080/admin0/events/subscribers | jq .active)" = 0 ]; do sleep 0.05; done

From Go tests, skip the hand-rolled loop: c.Events.Subscribers(ctx) returns the typed {Active, Total} counts, and auth0mocktest.WaitForActiveSubscribers(t, c, want, timeout) polls until active settles (or fatals on timeout).

[!NOTE] The mock's WRITE_TIMEOUT (default 30s) is automatically bypassed for /api/v2/events โ€” long-lived subscribers won't be torn down by the server-side deadline. If a reverse proxy fronts the mock (nginx, Envoy, โ€ฆ), disable response buffering for this endpoint (proxy_buffering off; for nginx) so frames reach the client live rather than queuing in the proxy until the connection closes. The mock has no CORS support โ€” browser EventSource clients on a different origin will be blocked by the browser's same-origin policy; run the mock on the page's origin or front it with a CORS-enabling proxy.

See docs/COOKBOOK.md โ†’ Drive an event-stream consumer from a test for the SDK-driven workflow.

๐Ÿ›  Admin surface (no auth, JSON-driven)
Endpoint Method Purpose
/admin0/reset POST Wipe everything: expectations, claims, permissions, MFA flag, clock
/admin0/expectations POST / GET / DELETE Register, list, and clear canned Management API responses
/admin0/claims GET / PUT / DELETE Custom claims merged into every minted JWT
/admin0/permissions GET / DELETE All audiences and their permissions
/admin0/permissions/{audience} GET / PUT / DELETE Per-audience RBAC injection (audience may be a URL, chi wildcard)
/admin0/mfa-required GET / PUT Toggle MFA enforcement at runtime
/admin0/clock GET / PUT / DELETE Freeze the clock at an instant ({"now":"..."}), set an offset ({"offset":"25h"}), or restore real time. Drives JWT iat/exp and bearer validation.
/admin0/clock/advance POST Step the held clock by a Go duration ({"by":"25h"}). Negative durations allowed.
/admin0/events POST Push an Auth0 event-stream envelope onto GET /api/v2/events so consumer SDKs see it live. Body is the full envelope {type, offset, event:{...CloudEvent}} โ€” validated against the OpenAPI text/event-stream schema before fan-out. See Event streams.
/admin0/events/subscribers GET Observe the SSE endpoint: {"active":N,"total":M} โ€” subscribers connected right now, and total connected since the last /admin0/reset. See Event streams.

[!WARNING] /admin0/* is unauthenticated by design so test setup needs zero token plumbing. Never expose it to an untrusted network. Bind the mock to 127.0.0.1 (the default), keep it inside your CI runner / dev container, or front it with your own auth if you must reach it across a network boundary.

๐Ÿฉบ Operations
Endpoint Notes
/healthz Kubernetes-style liveness probe โ€” 200 {"status":"ok"} if the process is up. No auth.
/readyz Kubernetes-style readiness probe โ€” 200 {"status":"ready"} once the JWKS signing key is materialised. The mock's only init dependency (RSA keygen) is synchronous and runs before the listener accepts, so today this is functionally equivalent to /healthz; the endpoint is exposed for orchestrator-convention parity (liveness vs readiness probe separation) and to leave room if future init grows. No auth.

โ†‘ Back to table of contents

๐Ÿ’ก Common recipes

โ†’ See docs/COOKBOOK.md for full recipes. Highlights:

# Inject a custom claim into every token
curl -X PUT http://localhost:8080/admin0/claims \
  -H 'Content-Type: application/json' \
  -d '{"role":"admin","org_id":"o-42"}'

# Set RBAC for an audience (URL-form audience works thanks to chi wildcard)
curl -X PUT http://localhost:8080/admin0/permissions/https://api.example.com/ \
  -H 'Content-Type: application/json' \
  -d '["read:users","write:users"]'

# Force MFA on the next password grant
curl -X PUT http://localhost:8080/admin0/mfa-required \
  -H 'Content-Type: application/json' \
  -d '{"required":true}'

# Freeze the clock so token iat/exp are deterministic
curl -X PUT http://localhost:8080/admin0/clock \
  -H 'Content-Type: application/json' \
  -d '{"now":"2030-01-01T00:00:00Z"}'

# Advance 7 days to simulate token expiry without sleeping
curl -X POST http://localhost:8080/admin0/clock/advance \
  -H 'Content-Type: application/json' \
  -d '{"by":"168h"}'

# Reset everything between tests
curl -X POST http://localhost:8080/admin0/reset

โ†‘ Back to table of contents

๐Ÿ›  Configuration

Environment variables (see .env.example for the full template):

Variable Default Notes
HTTP_ADDR 127.0.0.1:8080 The HTTP listener address. Set to 0.0.0.0:8080 to accept LAN/container traffic (the Dockerfile already does). To run HTTPS-only, set this to off.
HTTPS_ADDR 127.0.0.1:8443 The HTTPS listener address. Set to 0.0.0.0:8443 to accept LAN/container traffic (the Dockerfile already does). To run HTTP-only, set this to off.
TLS_CERT_FILE / TLS_KEY_FILE empty If both set โ†’ load. Else โ†’ auto-generate (see TLS section)
TLS_CACHE_DIR empty If set, persist auto-gen cert to <dir>/tls.{crt,key} and reuse on restart
TLS_HOSTNAMES localhost,127.0.0.1,::1 SAN entries on the auto-generated cert
SIGNING_KEY_FILE empty PEM-encoded RSA key. Otherwise a fresh RS256 key is generated each boot
ISSUER_URL https://localhost:8443/ iss claim and OIDC discovery base
DEFAULT_AUDIENCE https://localhost:8443/api/v2/ Default aud if request doesn't supply one
ACCESS_TOKEN_TTL 24h Minted access token lifetime
ID_TOKEN_TTL 24h Minted ID token lifetime
SPEC_VALIDATION_STRICT true If false, runtime response re-check (defence in depth) logs but doesn't fail
LOG_LEVEL info zerolog levels
DEBUG false When true, every request and response is logged in full at INFO level: method, path, query, headers (Authorization / Cookie redacted), and body (truncated at 8 KiB). Off by default โ€” turn on only while debugging an SDK trace; adds an allocation and a synchronous log write per request.
READ_HEADER_TIMEOUT 5s http.Server's ReadHeaderTimeout
WRITE_TIMEOUT 30s http.Server's WriteTimeout. Bounds slow-write attacks. Doesn't apply to /api/v2/events โ€” the SSE handler clears the deadline per-connection so long-lived subscribers aren't torn down.
IDLE_TIMEOUT 120s http.Server's IdleTimeout. Bounds idle keep-alive connections.
MAX_REQUEST_BODY_BYTES 1048576 (1 MiB) Per-request body cap. Anything larger is read up to this point and the handler surfaces a 400. Set to 0 to disable.
EVENTS_REPLAY_BUFFER 100 Cap of the /api/v2/events SSE replay ring buffer. Reconnecting subscribers can resume from Last-Event-ID, ?from=<id>, or ?from_timestamp=<rfc3339> up to this many events back. <= 0 disables replay (the endpoint still works; resume params become no-ops).
SHUTDOWN_TIMEOUT 5s Graceful-shutdown grace period
LOGOUT_ALLOWED_URLS empty Comma-separated allow-list of absolute returnTo URLs that /v2/logout will 302 to. Empty (default) = no enforcement so SDK tests calling /v2/logout?returnTo=https://โ€ฆ work out of the box. When set, mirrors Auth0's tenant "Allowed Logout URLs" setting: relative URLs are always allowed, unlisted absolutes get 400, dangerous schemes (javascript:, data:, โ€ฆ) and backslash bypasses are rejected. Set in production-like fixtures.
AUTHORIZE_ALLOWED_CALLBACKS empty Comma-separated allow-list of absolute redirect_uri values that /authorize will 302 to. Same threat model as LOGOUT_ALLOWED_URLS but on the higher-value endpoint: /authorize carries code / access_token in the URL, so an unvalidated redirect_uri leaks them. Empty (default) = no enforcement so test SDKs can register any callback; set in production-like fixtures. Mirrors Auth0's per-application "Allowed Callback URLs" setting.
BEARER_REQUIRE_AUDIENCE empty When set, the Mgmt-API bearer middleware rejects tokens whose aud claim doesn't contain this value (mirrors Auth0's tenant-API-audience binding). Empty keeps the "echoed, not enforced" default so tests can swap audiences freely.

โ†‘ Back to table of contents

๐Ÿ”’ HTTPS / TLS

The auto-generated cert covers localhost, 127.0.0.1, ::1 (override with TLS_HOSTNAMES). TLS behaviour is the same on macOS and Linux, but the cert is self-signed, so clients reject it unless told otherwise. Three options:

[!WARNING] macOS gotcha: Go on macOS pulls trust roots from the system Security framework and ignores SSL_CERT_FILE / SSL_CERT_DIR (Linux Go honors them). The Linux SSL_CERT_FILE=./tls.crt go run โ€ฆ trick simply doesn't work on macOS. On macOS, trust the cert via mkcert (option 1 below, easiest), or import it into the keychain (security add-trusted-cert โ€ฆ, recipe in docs/COOKBOOK.md), or build a custom tls.Config{RootCAs: pool} in your client code.

mkcert installs a local CA into your platform's trust store and signs certs with it. Browsers, Go, and curl accept the result without flags:

mkcert -install                                                # one-time per workstation
mkcert -cert-file tls.crt -key-file tls.key localhost 127.0.0.1 ::1

docker run -e TLS_CERT_FILE=/certs/tls.crt -e TLS_KEY_FILE=/certs/tls.key \
  -v "$PWD:/certs" auth0-mock

Pick a path; the mock writes its auto-generated cert there on first boot and reuses it on subsequent restarts. Trust the cert once and trust persists:

docker compose run --rm -e TLS_CACHE_DIR=/data/tls \
  -v auth0-mock-tls:/data/tls auth0-mock
3. Skip verification

Fine for ephemeral tests, ugly for anything else:

curl -k https://localhost:8443/.well-known/openid-configuration
# Go: &tls.Config{InsecureSkipVerify: true}

To install the mock's cert into your OS trust store (after option 2 so it's stable across boots), see docs/COOKBOOK.md.

โ†‘ Back to table of contents

๐Ÿงช Testing the mock

go test -race ./...                        # unit tests
go test -tags=features ./cmd/api/...       # godog acceptance suite (every endpoint, end-to-end)

The godog harness boots the service in-process on a random port and exercises every Auth API path, every admin endpoint, and the spec-driven Management API surface. See features/ for the gherkin and features/scenario/ for the harness.

โ†‘ Back to table of contents

๐Ÿ›ก๏ธ Verifying releases

Every tagged release ships with a Cosign signature on each Docker image and an SPDX-JSON SBOM per release archive. Both are produced by GitHub-hosted CI and uploaded as part of the same workflow that publishes the binaries.

Verify a Docker image (keyless signing โ€” no shared secret required). Replace vX.Y.Z with the tag you want to verify, e.g. v0.227.0:

cosign verify \
  --certificate-identity-regexp 'https://github.com/sergiught/auth0-mock/\.github/workflows/release\.yml@.+' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/sergiught/auth0-mock:vX.Y.Z

[!NOTE] Only the ghcr.io/... tag is cosign-signed by the release workflow. The docker.io/sergiught/... mirror is a publish-only convenience; verify the equivalent GHCR digest if you need attestation.

A successful verification proves the image was built by this repo's release workflow at that tag โ€” not a CDN-substituted copy.

Find an SBOM: every release on the GitHub Releases page carries an auth0-mock_<version>_<os>_<arch>.tar.gz.spdx.json alongside each archive. Pass it to your SBOM scanner of choice (Snyk, FOSSA, Black Duck, grype, etc.).

โ†‘ Back to table of contents

๐Ÿ— Architecture

โ†’ Full deep-dive: docs/ARCHITECTURE.md.

At-a-glance:

chi router
  โ”œโ”€โ”€ recovery + request_id + logging       (always-on middleware)
  โ”œโ”€โ”€ /healthz                               liveness
  โ”œโ”€โ”€ /openapi.json /openapi.yaml            merged OpenAPI 3.1 spec
  โ”œโ”€โ”€ /docs                                  Scalar-rendered API reference
  โ”œโ”€โ”€ /admin0/{reset, expectations, claims, permissions/*, mfa-required}
  โ”‚                                          control plane (no auth)
  โ”œโ”€โ”€ /.well-known/{jwks.json, openid-configuration}
  โ”œโ”€โ”€ /oauth/* /authorize /userinfo /v2/logout
  โ”‚   /dbconnections/* /passwordless/*
  โ”‚                                          Auth API (hand-coded, functional)
  โ””โ”€โ”€ /api/v2/*                              Management API (spec-driven; one generic handler)
        /api/v2/.../{verb}                   โ† bearer-enforced; stubs via /admin0/expectations

Every handler is a struct holding its dependencies as fields, implementing http.Handler via ServeHTTP. JSON responses go through go-chi/render.

โ†‘ Back to table of contents

๐Ÿ“‚ Example consumer

examples/consumer/ is a stand-alone Go program that proves the drop-in compatibility end to end: mints a token, verifies its signature against /.well-known/jwks.json using the standard MicahParks/keyfunc + golang-jwt/jwt libraries (NOT the mock's internals), registers a Management API stub, and calls the stubbed endpoint.

make demo                # builds the mock, runs the example end-to-end over HTTPS, cleans up

Under the hood, make demo boots the binary with a persisted self-signed cert, waits for /healthz, runs examples/consumer against it, and tears the mock down on exit. To run by hand instead:

go run ./cmd/api &
go run ./examples/consumer

โ†‘ Back to table of contents

๐Ÿน Go SDK

pkg/auth0mock is the typed Go client for the /admin0/* control plane. Use it from Go test code to register stubs, inject claims, set per-audience permissions, and toggle MFA โ€” without hand-marshalling JSON.

import (
    "testing"

    "github.com/sergiught/auth0-mock/pkg/auth0mock"
    "github.com/sergiught/auth0-mock/pkg/auth0mock/auth0mocktest"
)

func TestUserLookup(t *testing.T) {
    // /admin0 listens on HTTP (8080) by default โ€” no TLS dance needed.
    // For HTTPS (8443) pair with `auth0mock.WithHTTPClient(tlsClient)`,
    // see examples/sdk for the pattern.
    c, err := auth0mock.NewClient("http://localhost:8080")
    if err != nil {
        t.Fatal(err)
    }
    // Reset on entry + exit, Verify all constraints at exit, in the
    // correct LIFO order. One line replaces two and removes the
    // "constraint silently dropped" footgun that comes from getting
    // the cleanup order wrong.
    auth0mocktest.Bracket(t, c)

    // Register a stub and demand it's hit exactly once.
    reg := auth0mocktest.MustApply(t, c.ExpectGet("/api/v2/users/auth0|alice").
        Respond(200).
        JSON(map[string]any{"user_id": "auth0|alice", "email": "alice@example.com"}))
    reg.Times(1)

    // ... your code under test calls the mock the same way it calls Auth0 ...
}

What's covered:

Resource Read Write Wipe Verify
Expectations List Add, fluent ExpectGet/Post/Put/Patch/Delete(...).Respond(...).JSON(...).Apply(ctx) Clear, ClearOp(method, path) Verify(ctx); per-stub: Hits(ctx), Times(n), AtLeast(n), AtMost(n), AnyTimes()
Claims Get Set Clear โ€”
Permissions All, Get(audience) Set(audience, perms) Clear, Delete(audience) โ€”
MFA Get Set (use Set(ctx, false)) โ€”
Clock Get Freeze(ctx, t), Offset(ctx, d), Advance(ctx, d) Reset โ€”
Events Subscribers (live + lifetime counts; auth0mocktest.WaitForActiveSubscribers(t, c, want, timeout) polls until it settles) Push(ctx, payload) (top-level Reset drains subscribers + replay buffer) โ€”
top-level โ€” โ€” Reset โ€” wipes every store (including the clock back to real mode) auth0mocktest.Bracket(t, c) โ€” recommended one-liner: pre-test reset + post-test Reset + post-test Verify, all in correct LIFO order

Apply(ctx) and Expectations.Add(ctx, ...) return a *RegisteredExpectation handle โ€” chain .Times(n) / .AtLeast(n) / .AtMost(n) on it to set hit-count constraints, then MustVerify (or Verify(ctx) for the error-returning variant) checks every constraint at test end. Discard the handle with _ = โ€ฆApply(ctx) if you don't need it.

Which helper? Use auth0mocktest.Bracket(t, c) for every test that wants hit-count assertions โ€” one line wires Reset on entry, Reset on exit, and Verify on exit in the correct LIFO order. Use auth0mocktest.ResetOnCleanup(t, c) when you only want isolation (no Times/AtLeast/AtMost constraints anywhere in the test).

When a stub doesn't match, the mock returns {"errorCode":"no_match"} with HTTP 404 to the SUT. From your test, the quickest debug move is exps, _ := c.Expectations.List(ctx); fmt.Println(exps) to see what's actually registered, and reg.Hits(ctx) on a specific handle to see if it fired.

What's NOT covered: the Auth0 APIs themselves (/oauth/*, /api/v2/*) โ€” point your existing Auth0 SDK at the mock for those. The SDK only wraps the test-fixture-shaping surface.

A runnable end-to-end walk-through lives at examples/sdk/ โ€” its own Go module (with a local-path replace so the example doubles as a copy-and-pin template). The example drives stubs registered through this SDK with the real go-auth0 SDK end-to-end (token mint โ†’ typed Management API call โ†’ Verify the stub was hit). Defaults to https://localhost:8443 because go-auth0 only speaks TLS.

make demo-sdk                                      # full setup: mock + TLS cert + run + teardown
# or, against an already-running mock:
cd examples/sdk && go run . -cert=/path/to/tls.crt # full chain verification
cd examples/sdk && go run .                        # InsecureSkipVerify fallback (demo only)

Full godoc: pkg.go.dev/github.com/sergiught/auth0-mock/pkg/auth0mock.

[!NOTE] The SDK's API is unstable until v1.0.0. Pin a tagged version (go get github.com/sergiught/auth0-mock@v0.227.0 or later) and treat any minor bump as potentially breaking.

โ†‘ Back to table of contents

๐Ÿค Contributing

PRs welcome. See CONTRIBUTING.md for local setup, code style, testing requirements, and how to add a new endpoint.

โ†‘ Back to table of contents

๐Ÿ“– Documentation map

File Audience Purpose
README.md (this file) Everyone Overview, quick start, configuration
docs/ARCHITECTURE.md Contributors / curious users How the service is structured internally
docs/COOKBOOK.md Test authors Recipes for common test scenarios
CONTRIBUTING.md Contributors Dev setup, conventions, PR workflow
SECURITY.md Everyone How to report a vulnerability
CODE_OF_CONDUCT.md Everyone Community standards and enforcement
CHANGELOG.md Everyone What changed between versions
examples/consumer/README.md Test authors Worked end-to-end example

โ†‘ Back to table of contents

โš–๏ธ License

MIT.

โ†‘ Back to table of contents

โš ๏ธ Disclaimer

auth0-mock is an independent, community-built testing tool. It is not affiliated with, endorsed by, or sponsored by Auth0 or Okta, Inc. "Auth0" and "Okta" are trademarks of Okta, Inc.; they are used here only nominatively, to describe what this project mocks.

To route and validate every Management API endpoint, this repo embeds a stripped skeleton of Auth0's published Management API OpenAPI specification (sourced from https://auth0.com/docs/api/management/openapi.json): paths, methods, parameters, and JSON-schema shapes only. Every Auth0-authored description, externalDocs link, and x-* extension is removed before commit by stripUpstreamProse; see the refresh procedure. The raw download is gitignored and never committed; only the skeleton is.

Auth0 does not attach an explicit redistribution license to the published spec. The deliberate stripping above is what lets us redistribute the structural shape for interoperability without redistributing Auth0's prose. If the distinction matters for your compliance review, confirm the terms with Auth0/Okta directly.

โ†‘ Back to table of contents

Directories ยถ

Path Synopsis
Package api embeds the OpenAPI assets the auth0-mock binary needs: the Auth0 Management API skeleton (input to the bundler) and the merged OpenAPI document served at /openapi.json.
Package api embeds the OpenAPI assets the auth0-mock binary needs: the Auth0 Management API skeleton (input to the bundler) and the merged OpenAPI document served at /openapi.json.
cmd
api command
Command auth0-mock is the entry point for the auth0-mock service.
Command auth0-mock is the entry point for the auth0-mock service.
genopenapi command
Command genopenapi bundles the embedded Auth0 Management API skeleton, the per-package OpenAPI fragments shipped by each surface (authapi, admin0, router service endpoints) into a single OpenAPI 3.1 document.
Command genopenapi bundles the embedded Auth0 Management API skeleton, the per-package OpenAPI fragments shipped by each surface (authapi, admin0, router service endpoints) into a single OpenAPI 3.1 document.
features
scenario
Package scenario holds the godog test harness: a per-scenario context that boots the auth0-mock service in-process on a random port and provides HTTP helpers for the .feature step files.
Package scenario holds the godog test harness: a per-scenario context that boots the auth0-mock service in-process on a random port and provides HTTP helpers for the .feature step files.
internal
admin0
Package admin0 exposes the mock's control-plane endpoints under /admin0/*.
Package admin0 exposes the mock's control-plane endpoints under /admin0/*.
authapi
Package authapi mounts hand-coded Auth0 Authentication API endpoints onto chi.
Package authapi mounts hand-coded Auth0 Authentication API endpoints onto chi.
bearer
Package bearer provides middleware that validates an Authorization: Bearer JWT against a jwks.KeySet and attaches the claims to the request context.
Package bearer provides middleware that validates an Authorization: Bearer JWT against a jwks.KeySet and attaches the claims to the request context.
claims
Package claims owns a per-process map of custom JWT claims that get merged into every access token minted by the authapi package.
Package claims owns a per-process map of custom JWT claims that get merged into every access token minted by the authapi package.
clock
Package clock provides a pluggable Now() time source so the mock's protocol-time outputs (JWT iat/exp, code/OTP TTLs, bearer validation) can be frozen or skewed at runtime via /admin0/clock for deterministic tests.
Package clock provides a pluggable Now() time source so the mock's protocol-time outputs (JWT iat/exp, code/OTP TTLs, bearer validation) can be frozen or skewed at runtime via /admin0/clock for deterministic tests.
config
Package config loads runtime settings from environment variables.
Package config loads runtime settings from environment variables.
events
Package events owns the in-process Server-Sent Events hub for the mock's GET /events endpoint.
Package events owns the in-process Server-Sent Events hub for the mock's GET /events endpoint.
httperr
Package httperr writes JSON error responses in either Auth0 Management API shape or Auth0 Authentication API shape.
Package httperr writes JSON error responses in either Auth0 Management API shape or Auth0 Authentication API shape.
jwks
Package jwks owns the in-process RS256 signing key, JWT minting, JWT verification, and JWKS publication.
Package jwks owns the in-process RS256 signing key, JWT minting, JWT verification, and JWKS publication.
logger
Package logger constructs the project's zerolog.Logger.
Package logger constructs the project's zerolog.Logger.
matches
Package matches owns the in-memory store of registered mock expectations.
Package matches owns the in-memory store of registered mock expectations.
mfa
Package mfa implements the in-memory state for Auth0's MFA challenge dance.
Package mfa implements the in-memory state for Auth0's MFA challenge dance.
mgmtapi
Package mgmtapi mounts the spec-driven Management API surface onto chi: one generic, bearer-protected handler per Auth0 Management API operation.
Package mgmtapi mounts the spec-driven Management API surface onto chi: one generic, bearer-protected handler per Auth0 Management API operation.
middleware
Package middleware contains shared net/http middleware.
Package middleware contains shared net/http middleware.
permissions
Package permissions owns a per-audience map of permission strings.
Package permissions owns a per-audience map of permission strings.
pkce
Package pkce implements the in-memory store of PKCE challenges that link a /authorize redirect to its later /oauth/token exchange.
Package pkce implements the in-memory store of PKCE challenges that link a /authorize redirect to its later /oauth/token exchange.
router
Package router builds the http.Handler that fronts the mock service.
Package router builds the http.Handler that fronts the mock service.
server
Package server hosts the HTTP and (later) HTTPS listeners.
Package server hosts the HTTP and (later) HTTPS listeners.
spec
Package spec wraps the embedded Auth0 Management API skeleton.
Package spec wraps the embedded Auth0 Management API skeleton.
tlscert
Package tlscert produces a *tls.Config from either user-supplied cert/key files or an auto-generated self-signed cert covering configured hostnames.
Package tlscert produces a *tls.Config from either user-supplied cert/key files or an auto-generated self-signed cert covering configured hostnames.
version
Package version exposes build-time metadata baked into the binary at link time via `-ldflags="-X ..."`.
Package version exposes build-time metadata baked into the binary at link time via `-ldflags="-X ..."`.
pkg
auth0mock
Package auth0mock is the Go SDK for the auth0-mock control plane.
Package auth0mock is the Go SDK for the auth0-mock control plane.
auth0mock/auth0mocktest
Package auth0mocktest provides testing.TB-aware helpers for the pkg/auth0mock SDK.
Package auth0mocktest provides testing.TB-aware helpers for the pkg/auth0mock SDK.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL