#captcha

5 messages · Page 1 of 1 (latest)

strange pilot
#

What is the preferred way to integrate a captcha in website with next.js and convex? cluodflare turnstile maybe? how does the implementation overview look like?

solemn badgeBOT
#

Hi @strange pilot. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Convex’s docs and support threads don’t describe a full, step‑by‑step Turnstile (or other CAPTCHA) integration, but they do outline the recommended pattern conceptually:

  1. Protect the “entry point” with CAPTCHA, not every Convex function.
    Convex recommends treating CAPTCHA as a way to authorize an anonymous session ID, then rate‑limit and gate behavior based on that session, not on IP alone. [Rate limiting]

  2. Use Next.js as the CAPTCHA boundary, Convex as the app backend.
    From the Discord thread, people wanting Turnstile + Convex are doing exactly what you’re suggesting: wrap Convex calls behind a Next.js route or server action that:

    • Reads the client IP (from X-Forwarded-For or req.ip depending on your hosting),
    • Verifies the CAPTCHA with Cloudflare,
    • Only then calls Convex. [Discord IP thread]
  3. Why not do CAPTCHA directly in Convex?

    • Convex functions (queries/mutations/actions) don’t currently expose the client IP in ctx, which Turnstile often wants. [Discord IP thread]
    • You can get X-Forwarded-For in HTTP Actions, but that still leaves you implementing the Turnstile verification logic yourself and then calling into your internal Convex functions. [Discord IP thread]
    • Because of this, the most straightforward pattern with Next.js is to use Next.js route handlers / server actions as the CAPTCHA verification layer.

High‑level implementation overview (Next.js App Router + Convex)

Client side (Next.js page / component)

#
  1. Render the Turnstile widget (or other CAPTCHA) in your form.
  2. On submit:
    • Get the CAPTCHA token from the widget.
    • POST to a Next.js route handler, e.g. POST /api/submitForm, with:
      • captchaToken
      • Your form data.

Next.js route handler (app/api/submitForm/route.ts)

  1. Extract:
    • captchaToken and form data from the request body.
    • Client IP from headers (e.g. x-forwarded-for) if Turnstile requires it.
  2. Call Cloudflare Turnstile’s verify endpoint with:
    • Your secret key,
    • The captchaToken,
    • The client IP (if required).
  3. If verification fails → return 4xx.
  4. If verification succeeds:
    • Call Convex from the server using fetchMutation / fetchAction from convex/nextjs to perform the actual work. [Next.js module]
    • Optionally associate a session ID (cookie or header) with “captcha passed” and use that to gate future calls, as suggested in the rate‑limiting article. [Rate limiting]

Convex side

  • Your Convex mutation/action just assumes “this request has already passed CAPTCHA” because the Next.js route handler enforces it.
  • For anonymous users, you can:
    • Pass a session ID in the args and rate‑limit by that ID, as recommended in the rate‑limiting guide. [Rate limiting]

Using Convex HTTP Actions instead of Next.js routes

If you prefer to stay inside Convex:

#
  1. Create a Convex HTTP Action that:
    • Reads X-Forwarded-For to get the client IP. [Discord IP thread]
    • Verifies the Turnstile token with Cloudflare.
    • On success, calls internal Convex mutations/actions.
  2. Call that HTTP Action from your Next.js client instead of calling Convex directly.

Summary

  • Convex doesn’t have a built‑in Turnstile integration; the preferred pattern is:
    • CAPTCHA in the frontend,
    • Verification in a trusted server boundary (Next.js route handler or Convex HTTP Action),
    • Then call Convex only after verification.
  • For anonymous users, combine this with a session ID–based rate‑limiting strategy rather than relying on IP alone. [Rate limiting]

If you share whether you’re using App Router or Pages Router and where you host (Vercel, etc.), I can outline a more concrete file‑by‑file structure within those constraints.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: