A tiny Go HTTP service that mints signed, expiring QR codes for approved target URLs.
It exposes two endpoints:
GET /v1/sign— protected “mint” endpoint that creates a signed, expiring QR request URLGET /v1/qr— public endpoint that validates the signature + expiration and returns a PNG QR code
QR codes are easy to copy and reuse. This service lets you generate QR codes that:
- expire after a configurable TTL
- can only be minted by callers with a bearer token
- only encode valid
http/httpsURLs (and rejectslocalhost)
Signature format:
sig = base64url( HMAC-SHA256(secret, u + "\n" + exp) )
The QR endpoint requires:
u= target URL to encodeexp= unix timestamp (seconds) when the request expiressig= HMAC signature overuandexp
If the signature is valid and exp is in the future, the service returns a 256×256 PNG QR code encoding the target URL.
- Go 1.23+
- Environment variables:
QR_SIGNING_SECRET— secret key used for HMAC signingQR_MINT_TOKEN— bearer token required to call/v1/sign
Dependencies used:
github.com/joho/godotenv(optional.envloading)github.com/skip2/go-qrcode(PNG QR code generation)
go mod downloadCreate a .env file (or export env vars):
echo "QR_SIGNING_SECRET=$(openssl rand -hex 32)" > .env
echo "QR_MINT_TOKEN=$(openssl rand -hex 16)" >> .envThen start the server:
go run .The service listens on:
- 0.0.0.0:8000
Mint a signed QR request path.
- Authorization: Bearer <QR_MINT_TOKEN>
- u (string, required): target URL to encode (must be http or https, not localhost)
- ttl (int, required): time-to-live in seconds (1..86400)
- 200 text/plain: a relative path like:
/v1/qr?u=<escaped>&exp=<unix>&sig=<base64url>
curl -s \
-H "Authorization: Bearer $QR_MINT_TOKEN" \
"http://localhost:8000/v1/sign?u=https://example.com&ttl=300"Return a PNG QR code after validating signature + expiration.
- u (string, required): target URL to encode
- exp (int, required): unix timestamp when the request expires
- sig (string, required): base64url HMAC signature
- 200 image/png: PNG QR code encoding the target URL (256×256)
- 401: expired or bad signature
- 400: missing/invalid params or invalid target URL
First mint a signed path:
SIGNED_PATH=$(
curl -s \
-H "Authorization: Bearer $QR_MINT_TOKEN" \
"http://localhost:8000/v1/sign?u=https://example.com&ttl=300"
)
echo "$SIGNED_PATH"Then fetch the QR image:
curl -s "http://localhost:8000$SIGNED_PATH" -o qr.png
open qr.png # macOS; use your OS viewerThe target URL must:
- parse successfully
- use scheme http or https
- include a host
- not have hostname localhost
(Other private/internal hostnames are not blocked by default—see “Hardening” below.)
- /v1/sign is protected via a constant-time compare of the Authorization header.
- /v1/qr uses constant-time comparison on decoded HMAC bytes.
- Responses for QR images include Cache-Control: no-store.
Currently everything lives in main.go.