Skip to content

Runbook: reCAPTCHA fails on sign-up/forgot-password locally

Symptom

Sign-up, forgot-password, or any throttled auth endpoint returns a captcha validation error despite running in local development. The error response contains AUTH_RECAPTCHA_TOKEN_MISSING or AUTH_INVALID_CAPTCHA.

Likely cause

The bypass logic at apps/api/src/captcha/google/recaptcha.service.ts:28 requires two conditions:

  1. NODE_ENV must be local (the isLocal flag)
  2. The captcha token must be the literal string "pass"

If either condition is unmet, the service makes a real HTTP call to Google's siteverify endpoint, which fails without a valid RECAPTCHA_SECRET.

Diagnosis

# Check NODE_ENV
sudo docker exec ebit-api env | grep NODE_ENV
# Must be: NODE_ENV=local

# Check if the request includes the bypass token
# In your test or API client, the header must be:
#   x-captcha-token: pass

Fix

For API tests (curl / Playwright / Postman)

Always include the bypass header on auth endpoints that use @ThrottleRecaptcha():

curl -X POST http://localhost:4000/auth/login \
  -H "Content-Type: application/json" \
  -H "x-captcha-token: pass" \
  -d '{"email":"local@example.com","password":"password"}'

For E2E tests (Playwright)

The E2E helpers should inject the header automatically. If not, add to your fetch/API call:

headers: {
  'Content-Type': 'application/json',
  'x-captcha-token': 'pass',
}

For browser testing (ebit-fe)

The frontend's useRecaptcha hook calls Google's client-side reCAPTCHA library. In local dev, the NEXT_PUBLIC_RECAPTCHA_SITE_KEY should be set to Google's test key (6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI) which always returns a valid token — but the server-side still validates it. Since the server bypass accepts "pass", the real Google token also works if RECAPTCHA_SECRET is set to the matching test secret.

The simplest path: ensure NODE_ENV=local on ebit-api, and the bypass handles everything regardless of what token the frontend sends.

If NODE_ENV isn't local

# Check docker-compose.yml for the ebit-api service
grep NODE_ENV docker-compose.yml | head -3
# Should include: NODE_ENV: "local" (or inherited from .env)

Feature flag override

reCAPTCHA can also be globally disabled via the disable_captcha feature flag. If FEATURE_FLAGS_USE_LOCAL=true, set the flag in the in-memory store.

Prevention

  • Never set NODE_ENV to anything other than local in the development compose file
  • E2E test helpers should always include x-captcha-token: pass on auth-related requests
  • If adding new @ThrottleRecaptcha() decorators, document the bypass in the endpoint's test