A lightweight Dockerized ALTCHA challenge/verify service built with Bun + Express. It exposes simple endpoints to generate ALTCHA challenges and verify solutions, and includes a separate demo UI container.
- Runtime: Bun
- Ports: 3000 (API), 8080 (demo)
- Upstream libs: altcha, altcha-lib
Use Docker Compose (recommended):
# Optionally create a .env file (see below) or set variables in your shell
[ -f .env ] || cp .env.example .env
# Start the API only (production default)
docker compose up --buildTo also start the demo service, add the demo profile:
docker compose --profile demo up --build- Demo: http://localhost:8080
To start the API with a Valkey-backed replay store, add the redis profile and set REDIS_URL:
REDIS_URL=redis://valkey:6379 docker compose --profile redis up --buildTo override the secret temporarily:
ALTCHA_HMAC_SECRET="your-very-long-random-key" docker compose up --buildThe API service reads the following environment variables:
- SECRET (required): HMAC key used to sign/verify challenges. The API app requires this container/runtime value. Docker Compose maps
ALTCHA_HMAC_SECRETto containerSECRETand supplies$ecret.keyonly as a local testing fallback whenALTCHA_HMAC_SECRETis unset; don’t use it in production. - PORT: API port, default 3000.
- EXPIREMINUTES: Challenge expiry in minutes, default 10.
- MAXRECORDS: Size of in‑memory single‑use token cache, default 1000.
- CORS_ORIGIN: Allowed CORS origin(s), default *. Use comma-separated values for multiple origins.
- ALGORITHM: ALTCHA v2 algorithm, default PBKDF2/SHA-256.
- MAXNUMBER: ALTCHA v2 proof-of-work cost (difficulty), default 5000.
- REDIS_URL (optional): Redis or Valkey URL for a shared replay-store backend. When set, the API uses Redis instead of the in-memory cache. Example:
redis://valkey:6379.
Docker Compose also reads the following image overrides:
API_IMAGE(optional): Image used for the server service. Default:ghcr.io/greensec/altcha-docker:main. Set this to use a custom build or a different tag.DEMO_IMAGE(optional): Image used for the demo service. Default:ghcr.io/greensec/altcha-docker-demo:main.
Generate a strong HMAC secret with at least 32 characters:
# Linux / macOS (OpenSSL)
openssl rand -base64 48
# Or with /dev/urandom
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 48; echoCopy the output into .env as ALTCHA_HMAC_SECRET (or SECRET for direct Bun runs).
The demo service reads the following environment variables:
- API_BASE_URL: Base API URL used by the demo to proxy
GET /challengeand verify demo form submissions fromPOST /test, default http://server:3000 in Docker Compose. - DEMO_PORT: Demo HTTP port, default 8080.
You can provide variables via:
- .env file in the project root (Docker Compose reads it automatically)
- compose.yaml environment section
- Directly in your shell
Example .env:
ALTCHA_HMAC_SECRET=change-me-to-a-long-random-string
# Direct Bun/API runtime only:
SECRET=change-me-to-a-long-random-string
PORT=3000
EXPIREMINUTES=10
MAXRECORDS=1000
CORS_ORIGIN=*
ALGORITHM=PBKDF2/SHA-256
MAXNUMBER=5000
API_BASE_URL=http://server:3000
DEMO_PORT=8080DEMO=trueno longer starts the demo UI inside the API container.- Use the separate
demoservice in Docker Compose, or build/run the Dockerfiledemotarget.
MAXNUMBERis now the preferred environment variable for ALTCHA v2 proof-of-work difficulty.- Existing setups using
COSTstill work for backward compatibility. - If both are set,
MAXNUMBERtakes precedence.
Recommended update for existing deployments:
# old
# COST=5000
# new
MAXNUMBER=5000-
GET /
- Returns 204 No Content. Liveness probe endpoint.
-
GET /challenge
- Returns a signed ALTCHA challenge JSON produced by altcha-lib.
- 200 OK with challenge payload.
-
GET /verify?altcha=
- Verifies the provided ALTCHA solution via query string.
- 202 Accepted on success.
- 417 Expectation Failed on failure or when a token is reused (single-use enforced with an in-memory cache).
-
POST /verify
- Verifies the provided ALTCHA solution via JSON body (
{ "altcha": string }). - 202 Accepted on success.
- 417 Expectation Failed on failure or reuse.
- Verifies the provided ALTCHA solution via JSON body (
Notes:
- CORS is open (origin: *).
- Record reuse protection is best-effort and stored in-memory; scale-out or restarts will reset the cache. For production, pair with a shared store or upstream protections as needed.
- Security note:
GET /verify?altcha=<payload>sends the ALTCHA payload in the query string. Query parameters may be logged by reverse proxies, load balancers, and browser history. For privacy-sensitive integrations, consider implementing aPOST /verifywrapper that accepts the payload in the request body instead.
Docker Compose starts a dedicated demo service at http://localhost:8080. The demo serves /, exposes GET /challenge for the widget and proxies it to API /challenge, and accepts the demo form at POST /test, which calls API /verify through the same API_BASE_URL target. The demo does not expose a public /verify route.
Add the widget to your form and point challengeurl at this service:
<script async defer src="https://cdn.jsdelivr.net/gh/altcha-org/altcha@v3.1.0/dist/altcha.min.js" type="module"></script>
<form action="/your-submit" method="POST">
<input name="email" placeholder="Email" />
<altcha-widget challengeurl="http://localhost:3000/challenge"></altcha-widget>
<button>Submit</button>
<!-- On submit, include the `altcha` field value in your request body -->
<!-- Example server should call GET /verify?altcha=... and accept 202 as success -->
<!-- 417 means invalid or reused token -->
</form>Test verification manually:
# Assuming $payload contains the exact `altcha` value from the client
curl -G \
--data-urlencode "altcha=$payload" \
http://localhost:3000/verify -iExpect 202 on success or 417 on failure/reuse.
You can run locally with Bun (requires Bun installed):
bun install
bun run build
bun startOr for live reload during development:
bun run dev- Change
ALTCHA_HMAC_SECRETfor Docker Compose, orSECRETfor direct API/container runtime, to a strong unique value. Never use the default. - Do not bake
.envfiles or secrets into images; provide runtime environment variables from Compose, your orchestrator, or a secret manager. - Consider terminating TLS in front of the container and restricting access to /verify if needed.
- Warning: In-memory replay protection is single-instance only and is cleared on every container restart. Any routine deploy or crash recovery silently opens a replay window for recently-issued challenges. For production, set
REDIS_URLto use a shared Redis or Valkey backend, or pair with upstream protections. - Pin image versions and consider multi-arch builds if deploying across architectures.
- Both the
apianddemoDockerfile stages include aHEALTHCHECKfor orchestrator-level health detection. - The API container handles
SIGTERM/SIGINTgracefully, draining active connections before exit.
Licensed under the MIT License.
❤️ made with passion in Erlangen by Umami Creative GmbH