Berth 8080 · Manifest № RFC-6749 Moored at github.com/IAmLuisJ/tokendock ↗

Fake OAuth 2.0 authorization server · for CI

Tokendock

Your pipeline can't reach your real identity provider. TokenDock issues real RS256-signed JWTs from fake credentials — so the app under test validates tokens with its normal middleware, and your JWT-protected flows finally run in CI.

View on GitHub docker run · 30-second setup
Image size
~4 MB no OS, just the binary
Cold start
<100 ms
Config required
None to first token
Runtime deps
Zero one static binary

The problem

Auth is where CI testing goes to die

Every service you test in CI expects a bearer token signed by your identity provider — which lives behind a VPN, rate-limits you, or simply shouldn't be handed to a build runner. So teams stub out auth, mark those tests skip, and ship JWT-handling code that has never actually been exercised.

TokenDock replaces the identity provider, not your auth code. It speaks enough real OAuth 2.0 — client credentials grant, JWKS, OIDC discovery — that your app validates its tokens exactly as it would in production. No test flags. No mocked middleware. No code changes.

Honest by design — TokenDock signs whatever you configure with an ephemeral key that dies with the container. It is deliberately useless outside a test run.

Bill of lading

Three calls, no ceremony

  1. Berth the container

    Add TokenDock to your workflow's services: block or run it anywhere Docker runs. It boots in milliseconds with a built-in demo client, or takes your clients via env vars or YAML.

  2. Request a token

    Your service or test calls POST /token with its client ID and secret — the standard client credentials grant, Basic or form-body auth, RFC 6749 errors and all.

  3. Validate like production

    The app under test discovers signing keys from /.well-known/jwks.json via OIDC discovery and verifies the RS256 signature — the same code path it runs in production.

Quickstart

Cast off

Anywhere Docker runs
# zero config: demo client included
docker run -p 8080:8080 ghcr.io/iamluisj/tokendock:latest

# fetch a signed JWT
curl -u tokendock:tokendock-secret \
  -d grant_type=client_credentials \
  http://localhost:8080/token
GitHub Actions workflow
services:
  tokendock:
    image: ghcr.io/iamluisj/tokendock:latest
    ports: ["8080:8080"]
    env:
      TOKENDOCK_CLIENT_ID: my-service
      TOKENDOCK_CLIENT_SECRET: ci-secret
      TOKENDOCK_SCOPES: read,write
      TOKENDOCK_AUDIENCE: my-api

Prefer a one-liner? The composite action starts the container, waits for health, and hands your next step the issuer, token-endpoint, and jwks-uri as outputs.

Cargo manifest

What's in the hold

Harbor traffic

Not the only ship in port

The established vessel here is navikt/mock-oauth2-server — mature, capable, and the right choice for plenty of crews. Here's an honest manifest of the differences.

Comparative manifest
Line itemTokenDockmock-oauth2-server
Runtime & image~4 MB static Go binary no OS layer~200 MB JVM image Kotlin
Cold startMillisecondsSeconds JVM startup
Grant typesClient credentials + token exchange RFC 8693Authorization code, token exchange, JWT bearer, refresh & more
Interactive login pageNoneYes for browser-driven E2E tests
Embed in test codeContainer onlyJVM library JUnit-friendly
IssuersOne per containerMultiple per instance
ConfigurationZero-config default, then env vars or YAMLJSON or programmatic API
CI wrapperComposite GitHub Action health-wait + outputs

Sail with TokenDock when…

  • You're testing machine-to-machine bearer tokens — services calling services
  • You want a service container that's ready before your app finishes booting
  • Your stack isn't JVM and a library-mode server buys you nothing
  • You'd rather declare clients in six lines of env vars than maintain a config API

Sail with mock-oauth2-server when…

  • Your E2E tests drive a browser through a real login redirect
  • You need authorization code flow or refresh tokens
  • You're on the JVM and want the server embedded in your JUnit lifecycle
  • You need several issuers from one instance