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:
NODE_ENVmust belocal(theisLocalflag)- 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:
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_ENVto anything other thanlocalin the development compose file - E2E test helpers should always include
x-captcha-token: passon auth-related requests - If adding new
@ThrottleRecaptcha()decorators, document the bypass in the endpoint's test