Open source · MIT license · Self-hosted

Self-hosted captcha.
Zero vendors.

Two drop-in components — server-side PNG images backed by MongoDB, or a fully browser-side canvas variant. No third-party services required.

Try it live View on GitHub

Interactive demo

CanvasCaptcha — browser-only

Works immediately. No backend, no database, no API calls. Copy the component and drop it in.

Security check
Case-sensitive · 5 characters · Mixed case + digits · No ambiguous chars (I/i/l/O/o/0/1)

Drop-in usage (React)

jsx
import { useRef } from 'react';
import CanvasCaptcha from './CanvasCaptcha';

function ContactForm() {
  const captchaRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    // validate() returns true/false
    // auto-redraws on wrong answer
    if (!captchaRef.current.validate()) return;
    // ✓ safe to submit
  };

  return (
    <form onSubmit={handleSubmit}>
      <CanvasCaptcha ref={captchaRef} />
      <button type="submit">Send</button>
    </form>
  );
}
Zero dependencies
One file, no npm install needed. Copy CanvasCaptcha.jsx and you're done.

ServerCaptcha — backend-verified

Images generated server-side. Answers stored in MongoDB. The browser never sees the solution.

Your EasyCaptcha backend URL
🖥 Start your service, then click Load
Enter backend URL above and click Load

Drop-in usage (React)

jsx
import { useState } from 'react';
import ServerCaptcha from './ServerCaptcha';

function LoginForm() {
  const [captcha, setCaptcha] = useState(null);
  const [reset, setReset]     = useState(0);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!captcha) return;
    const res = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({
        captcha_token_id: captcha.tokenId,
        captcha_answer:   captcha.answer,
      }),
    });
    if (!res.ok) setReset(r => r + 1);
  };

  return (
    <form onSubmit={handleSubmit}>
      <ServerCaptcha
        apiUrl="http://localhost:8080"
        onReady={setCaptcha}
        resetTrigger={reset}
      />
    </form>
  );
}
How it works
Answer is stored in MongoDB only. Your backend verifies via POST /captcha/verify. The browser never sees the solution.

Built-in security

Production-ready defaults. No config required.

🔒

Answer never in browser

The correct code is stored in MongoDB only. Only a distorted PNG image is returned to the client.

Single-use tokens

Each token is marked used immediately on first correct verification. Replay attacks are impossible.

Auto-expiry

MongoDB TTL index auto-deletes tokens after TOKEN_TTL_MINUTES (default 5). No cron jobs needed.

🛡

Per-IP rate limiting

Sliding 60-second window blocks brute-force image generation. Default: 15 requests/min/IP.

🔑

API key guard

/captcha/verify requires an X-API-Key header. Only your backend can call it — not the browser.

Timing-safe comparison

secrets.compare_digest prevents timing attacks on the API key check.

Up in 30 seconds

Docker Compose spins up the service and MongoDB together.

Terminal
# 1. Clone the repo
git clone https://github.com/ajaykumarvarma/EasyCaptcha.git
cd easycaptcha

# 2. Generate a strong secret key
python -c "import secrets; print(secrets.token_hex(32))"
# → paste the output as API_SECRET_KEY in docker/docker-compose.yml

# 3. Start (MongoDB included)
docker compose -f docker/docker-compose.yml up --build

Service live at http://localhost:8080  ·  Docs at http://localhost:8080/docs