Pre-request & Refresh scenarios for Captcha (reCAPTCHA / hCaptcha)

This document provides concise, copy-paste ready examples for common "pre-request" and "refresh" flows used by frontends (browser) and API clients (Postman) when protecting endpoints such as signup, OTP-resend and forgot-password.

1) Frontend — reCAPTCHA v3 (recommended for frictionless UX)

- Pre-request (obtain token right before sending request)

```
// Assumes grecaptcha v3 script has been loaded and grecaptcha is available
async function getRecaptchaToken(action = 'signup') {
  return await grecaptcha.execute(window.RECAPTCHA_SITE_KEY, { action });
}

async function submitSignup(formData) {
  // Try up to N attempts if server indicates verification failure
  const maxAttempts = 3;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const token = await getRecaptchaToken('signup');
    formData.captcha_token = token;

    try {
      const resp = await axios.post('/api/citizen/signup', formData);
      // success
      return resp.data;
    } catch (err) {
      // If server returns a captcha-related validation error, retry with a fresh token
      if (err.response && err.response.status === 422 && (err.response.data.message || '').toLowerCase().includes('captcha')) {
        if (attempt === maxAttempts) throw new Error('Captcha verification failed after multiple attempts');
        // otherwise loop to request a fresh token and re-submit
        continue;
      }
      throw err; // non-captcha error
    }
  }
}
```

- Refresh behavior
  - For reCAPTCHA v3 you must call grecaptcha.execute() each time you need a new token — tokens are short-lived.
  - Limit retries on client (2–3 attempts), and show a friendly message (e.g. "Captcha failed — please try again") after retries exhausted.

2) Frontend — reCAPTCHA v2 (checkbox / invisible)

- Checkbox flow: render widget, submit the form only if grecaptcha.getResponse(widgetId) is non-empty. If server rejects captcha verification, call grecaptcha.reset(widgetId) to force the user to re-solve the captcha.

- Invisible flow: call grecaptcha.execute(widgetId) on submit, it returns a token similarly to v3.

Example (invisible):

```
function onSubmitInvisible(ev) {
  ev.preventDefault();
  grecaptcha.execute(invisibleWidgetId);
}

function onRecaptchaCallback(token) {
  // called by grecaptcha after solve
  formData.captcha_token = token;
  submitToApi(formData).catch(err => {
    if (err.response && err.response.status === 422) {
      // instruct user to retry: reset widget and show message
      grecaptcha.reset(invisibleWidgetId);
    }
  });
}
```

3) Frontend — hCaptcha

- API is similar to reCAPTCHA v2: use hcaptcha.execute() for invisible flows, or hcaptcha.getResponse(widgetId).
- On server 422 failures, call execute again or reset the widget.

4) Postman pre-request script (local dev with dev-token endpoint)

- Use this pre-request script to request a short-lived dev token from the local dev helper route we added: `/api/__dev/captcha/generate`.
- Make sure your Postman environment has `API_BASE_URL` set (e.g. `http://localhost:8000/api`) and that you are running the API locally in `local` or `testing` environment.

```
// Postman pre-request script
const apiBase = pm.environment.get('API_BASE_URL') || pm.variables.get('API_BASE_URL');
const captchaEnabled = pm.environment.get('CAPTCHA_ENABLED') === 'true';

if (!captchaEnabled) {
  // No captcha enforced by server — use empty token or a placeholder
  pm.environment.set('captcha_token', '');
} else {
  // Request a dev token from the API (only available in local/testing)
  pm.sendRequest({
    url: apiBase + '/__dev/captcha/generate',
    method: 'GET'
  }, function (err, res) {
    if (err || !res || res.code !== 200) {
      console.log('Failed to get dev captcha token', err);
      pm.environment.set('captcha_token', '');
    } else {
      pm.environment.set('captcha_token', res.json().token);
    }
  });
}
```

- Then use the `{{captcha_token}}` variable in your request body as the `captcha_token` field.

5) Server-side refresh & retry considerations

- Do not allow infinite client retries. Enforce a maximum retry attempts client-side (2–3) and server-side rate-limiting is still applied.
- Log captcha verification failures and monitor trends. Frequent failures may indicate a misconfigured site/secret or client misbehavior.
- For reCAPTCHA v3, your server should check the `score` and reject if below threshold; communicate that to the client so it knows to refresh a token and try again.

6) Test scenarios covered by our automated tests

- Missing token → 422
- Invalid token → 422
- reCAPTCHA v3 low score → 422
- Valid token → 200
- Developer-generated `dev-…` token for local/testing (dev helper route) → accepted when app is local/testing
- Refresh flow: first verification failure, second verification success

If you'd like I can:

- Add a small example React component that implements the retry logic with UI state.
- Add a Postman collection that includes the pre-request script and a sample signup request using the `{{captcha_token}}` variable.
- Make the dev helper endpoint require a short secret header so it can't be inadvertently used by public-facing local deployments.

Examples added in this repository:

- React example component: `resources/js/components/SignupWithCaptcha.jsx` — demonstrates the retry logic and dev-token fallback.
- Postman collection for local testing: `postman/CaptchaDevCollection.postman_collection.json` — includes a `Get Dev Captcha Token` request and a `Signup (with dev token)` request.
