Skip to main content

reCAPTCHA Integration

Assemble Web supports Google reCAPTCHA v3 to protect forms from automated abuse (bots, credential stuffing, spam submissions). The integration is opt-in and controlled entirely via environment variables, so it can be enabled or disabled per environment without any code changes.

Where It Is Used

reCAPTCHA is currently integrated into the following forms:

FormComponentLocation
LoginLoginFormsrc/features/Login/LoginForm.tsx
Sign-upSignupFormsrc/features/Signup/SignupForm.tsx

Both forms follow the same integration model: when reCAPTCHA is enabled, the token is obtained on form submission and verified server-side before the underlying action (authentication or account creation) is performed.

Environment Variables

Four environment variables control the integration:

VariableScopeRequiredDescription
NEXT_PUBLIC_RECAPTCHA_ENABLEDClientYesSet to 'true' to enable reCAPTCHA. Any other value disables it.
NEXT_PUBLIC_RECAPTCHA_KEYClientYes (when enabled)The site key obtained from the Google reCAPTCHA Admin Console. Exposed to the browser.
NEXT_PUBLIC_RECAPTCHA_MIN_SCOREClientNoMinimum acceptable score (0.0–1.0). Defaults to 0.8. Submissions with a score below this threshold are rejected.
RECAPTCHA_SECRETServerYes (when enabled)The secret key obtained from the Google reCAPTCHA Admin Console. Never expose this to the client.
warning

RECAPTCHA_SECRET must never be prefixed with NEXT_PUBLIC_. Doing so would expose it to the browser and allow anyone to bypass server-side verification.

How It Works

The integration is split between client-side token generation and a server-side verification API route.

1. Client — Token Generation

When the user submits a protected form and reCAPTCHA is enabled (NEXT_PUBLIC_RECAPTCHA_ENABLED === 'true' and NEXT_PUBLIC_RECAPTCHA_KEY is set), the form calls grecaptcha.execute() to obtain a short-lived token:

grecaptcha.ready(() => {
const token = await grecaptcha.execute(siteKey, { action: 'submit' });
// token is then sent to the server-side verification endpoint
});

If the grecaptcha global is not available (e.g. the reCAPTCHA script failed to load), an error notification is shown and the submission is blocked.

2. Server — Token Verification (/api/recaptcha)

The token is POST-ed to the internal Next.js API route at src/app/api/recaptcha/route.ts, which forwards it to Google's verification endpoint:

POST https://www.google.com/recaptcha/api/siteverify

The server sends the token and the RECAPTCHA_SECRET to Google and returns the raw verification response (containing success and score) back to the client.

3. Client — Score Evaluation

After receiving the verification result, the form checks:

  • data.success must be true.
  • data.score must be ≥ NEXT_PUBLIC_RECAPTCHA_MIN_SCORE (default 0.8).

If either condition fails, the submission is rejected and the user is shown an error notification. Only when both conditions pass does the form proceed with the primary action (login or sign-up).

Flow Diagram

User submits form


grecaptcha.execute() ──► Google reCAPTCHA ──► token


POST /api/recaptcha { captcha: token }


Next.js API route ──► Google siteverify API ──► { success, score }


score ≥ minScore?
├─ No ──► show error, block submission
└─ Yes ──► proceed with login / sign-up

reCAPTCHA Disclosure

Google requires that all sites using reCAPTCHA display the following notice wherever the widget is used. When NEXT_PUBLIC_RECAPTCHA_ENABLED is true, the forms render this disclaimer automatically via the page_sign_in_captcha_disclaimer i18n key:

The disclaimer is rendered in a styled container at the bottom of the form and is conditionally shown only when reCAPTCHA is active. Refer to loginPage.module.css (.recaptchaDisclaimer) for the styling.

Disabling reCAPTCHA

Set NEXT_PUBLIC_RECAPTCHA_ENABLED to any value other than 'true' (or omit it entirely). The integration is fully bypassed: no token generation occurs, no network call is made to /api/recaptcha, and the disclaimer is hidden. The form falls back to its standard submission flow.