DEV Community

Joseph Nelson
Joseph Nelson

Posted on

Building CAPTCHA Protection with ProCaptcha in React

roCaptcha is a privacy-preserving CAPTCHA library for React applications. It provides a decentralized approach to bot protection while maintaining user privacy. This guide walks through setting up and creating CAPTCHA protection using ProCaptcha with React, from installation to a working implementation. This is part 56 of a series on using ProCaptcha with React.

Prerequisites

CAPTCHA Types

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Basic knowledge of React hooks (useState, useEffect)
  • Familiarity with JavaScript/TypeScript
  • Understanding of async operations

Installation

Install ProCaptcha using your preferred package manager:

npm install @prosopo/captcha
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add @prosopo/captcha
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add @prosopo/captcha
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "@prosopo/captcha": "^1.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

ProCaptcha requires configuration. Set up the provider and component:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ProsopoProvider } from '@prosopo/captcha';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <ProsopoProvider
      config={{
        dappName: 'MyApp',
        networkId: 'development'
      }}
    >
      <App />
    </ProsopoProvider>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a simple CAPTCHA form. Create a new file src/CaptchaExample.jsx:

// src/CaptchaExample.jsx
import React, { useState } from 'react';
import { useProsopo } from '@prosopo/captcha';

function CaptchaExample() {
  const { captcha, verify } = useProsopo();
  const [isVerified, setIsVerified] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  const handleVerify = async () => {
    setIsLoading(true);
    try {
      const result = await captcha.verify();
      setIsVerified(result);
      if (result) {
        alert('CAPTCHA verified successfully!');
      } else {
        alert('CAPTCHA verification failed. Please try again.');
      }
    } catch (error) {
      console.error('CAPTCHA error:', error);
      alert('Error verifying CAPTCHA. Please try again.');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
      <h2>ProCaptcha Example</h2>
      <div style={{ marginBottom: '20px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        {captcha && (
          <div>
            <p>Complete the CAPTCHA challenge below:</p>
            <div style={{ marginTop: '15px' }}>
              {captcha.render()}
            </div>
          </div>
        )}
      </div>
      <button
        onClick={handleVerify}
        disabled={isLoading}
        style={{
          padding: '10px 20px',
          backgroundColor: isLoading ? '#6c757d' : '#007bff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: isLoading ? 'not-allowed' : 'pointer',
          width: '100%'
        }}
      >
        {isLoading ? 'Verifying...' : 'Verify CAPTCHA'}
      </button>
      {isVerified && (
        <p style={{ color: 'green', marginTop: '10px', textAlign: 'center' }}>
          ✓ CAPTCHA verified successfully!
        </p>
      )}
    </div>
  );
}

export default CaptchaExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import CaptchaExample from './CaptchaExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <CaptchaExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a basic CAPTCHA form that verifies user interaction.

Understanding the Basics

ProCaptcha provides several key features:

  • ProsopoProvider: Context provider for CAPTCHA functionality
  • useProsopo hook: Access CAPTCHA methods and state
  • Privacy-preserving: Decentralized approach to bot protection
  • Verification: Async verification process
  • Customizable: Configurable for different networks and dapps

Key concepts:

  • Provider Setup: Wrap your app with ProsopoProvider
  • Hook Usage: Use useProsopo hook to access CAPTCHA
  • Verification: Call captcha.verify() to verify user interaction
  • State Management: Track verification state in your component
  • Error Handling: Handle verification errors gracefully

Here's an example with form integration:

// src/FormWithCaptcha.jsx
import React, { useState } from 'react';
import { useProsopo } from '@prosopo/captcha';

function FormWithCaptcha() {
  const { captcha, verify } = useProsopo();
  const [formData, setFormData] = useState({
    email: '',
    message: ''
  });
  const [isVerified, setIsVerified] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  const handleVerify = async () => {
    try {
      const result = await verify();
      setIsVerified(result);
    } catch (error) {
      console.error('Verification error:', error);
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!isVerified) {
      alert('Please complete the CAPTCHA first.');
      return;
    }
    setIsSubmitting(true);
    // Simulate form submission
    await new Promise(resolve => setTimeout(resolve, 1000));
    alert('Form submitted successfully!');
    setIsSubmitting(false);
  };

  return (
    <div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto' }}>
      <h2>Form with ProCaptcha</h2>
      <form onSubmit={handleSubmit}>
        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px' }}>
            Email:
          </label>
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleInputChange}
            required
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box'
            }}
          />
        </div>
        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px' }}>
            Message:
          </label>
          <textarea
            name="message"
            value={formData.message}
            onChange={handleInputChange}
            required
            rows={4}
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box',
              resize: 'vertical'
            }}
          />
        </div>
        <div style={{ marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '4px', backgroundColor: '#f8f9fa' }}>
          <label style={{ display: 'block', marginBottom: '10px' }}>
            CAPTCHA Verification:
          </label>
          {captcha && (
            <div style={{ marginBottom: '10px' }}>
              {captcha.render()}
            </div>
          )}
          <button
            type="button"
            onClick={handleVerify}
            style={{
              padding: '8px 16px',
              backgroundColor: '#28a745',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            Verify CAPTCHA
          </button>
          {isVerified && (
            <p style={{ color: 'green', marginTop: '10px' }}>
              ✓ Verified
            </p>
          )}
        </div>
        <button
          type="submit"
          disabled={!isVerified || isSubmitting}
          style={{
            padding: '10px 20px',
            backgroundColor: (!isVerified || isSubmitting) ? '#6c757d' : '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: (!isVerified || isSubmitting) ? 'not-allowed' : 'pointer',
            width: '100%'
          }}
        >
          {isSubmitting ? 'Submitting...' : 'Submit'}
        </button>
      </form>
    </div>
  );
}

export default FormWithCaptcha;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a complete contact form with ProCaptcha protection:

// src/ContactFormWithProCaptcha.jsx
import React, { useState } from 'react';
import { useProsopo } from '@prosopo/captcha';

function ContactFormWithProCaptcha() {
  const { captcha, verify } = useProsopo();
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    subject: '',
    message: ''
  });
  const [isVerified, setIsVerified] = useState(false);
  const [isVerifying, setIsVerifying] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [error, setError] = useState('');

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    setError('');
  };

  const handleVerify = async () => {
    setIsVerifying(true);
    setError('');
    try {
      const result = await verify();
      setIsVerified(result);
      if (!result) {
        setError('CAPTCHA verification failed. Please try again.');
      }
    } catch (err) {
      setError('Error verifying CAPTCHA. Please try again.');
      console.error('Verification error:', err);
    } finally {
      setIsVerifying(false);
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');

    if (!isVerified) {
      setError('Please complete the CAPTCHA verification first.');
      return;
    }

    setIsSubmitting(true);
    try {
      // Simulate API call
      await new Promise(resolve => setTimeout(resolve, 1500));
      alert('Form submitted successfully!');
      // Reset form
      setFormData({ name: '', email: '', subject: '', message: '' });
      setIsVerified(false);
    } catch (err) {
      setError('Error submitting form. Please try again.');
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
      <h1>Contact Form with ProCaptcha</h1>
      <form onSubmit={handleSubmit}>
        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px', fontWeight: '500' }}>
            Name *
          </label>
          <input
            type="text"
            name="name"
            value={formData.name}
            onChange={handleInputChange}
            required
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box'
            }}
          />
        </div>

        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px', fontWeight: '500' }}>
            Email *
          </label>
          <input
            type="email"
            name="email"
            value={formData.email}
            onChange={handleInputChange}
            required
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box'
            }}
          />
        </div>

        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px', fontWeight: '500' }}>
            Subject *
          </label>
          <input
            type="text"
            name="subject"
            value={formData.subject}
            onChange={handleInputChange}
            required
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box'
            }}
          />
        </div>

        <div style={{ marginBottom: '16px' }}>
          <label style={{ display: 'block', marginBottom: '4px', fontWeight: '500' }}>
            Message *
          </label>
          <textarea
            name="message"
            value={formData.message}
            onChange={handleInputChange}
            required
            rows={5}
            style={{
              width: '100%',
              padding: '8px',
              border: '1px solid #ddd',
              borderRadius: '4px',
              boxSizing: 'border-box',
              resize: 'vertical'
            }}
          />
        </div>

        <div style={{ marginBottom: '20px', padding: '15px', border: '1px solid #ddd', borderRadius: '4px', backgroundColor: '#f8f9fa' }}>
          <label style={{ display: 'block', marginBottom: '10px', fontWeight: '500' }}>
            CAPTCHA Verification *
          </label>
          {captcha && (
            <div style={{ marginBottom: '10px' }}>
              {captcha.render()}
            </div>
          )}
          <button
            type="button"
            onClick={handleVerify}
            disabled={isVerifying || isVerified}
            style={{
              padding: '8px 16px',
              backgroundColor: (isVerifying || isVerified) ? '#6c757d' : '#28a745',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: (isVerifying || isVerified) ? 'not-allowed' : 'pointer'
            }}
          >
            {isVerifying ? 'Verifying...' : isVerified ? '✓ Verified' : 'Verify CAPTCHA'}
          </button>
        </div>

        {error && (
          <div style={{
            padding: '10px',
            backgroundColor: '#f8d7da',
            color: '#721c24',
            borderRadius: '4px',
            marginBottom: '16px'
          }}>
            {error}
          </div>
        )}

        <button
          type="submit"
          disabled={!isVerified || isSubmitting}
          style={{
            padding: '10px 20px',
            backgroundColor: (!isVerified || isSubmitting) ? '#6c757d' : '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: (!isVerified || isSubmitting) ? 'not-allowed' : 'pointer',
            width: '100%',
            fontSize: '16px'
          }}
        >
          {isSubmitting ? 'Submitting...' : 'Submit Form'}
        </button>
      </form>
    </div>
  );
}

export default ContactFormWithProCaptcha;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import ContactFormWithProCaptcha from './ContactFormWithProCaptcha';
import './App.css';

function App() {
  return (
    <div className="App">
      <ContactFormWithProCaptcha />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Contact form with ProCaptcha protection
  • Form validation
  • CAPTCHA verification
  • Error handling
  • User feedback
  • Privacy-preserving bot protection

Common Issues / Troubleshooting

  1. Provider not configured: Make sure you've wrapped your app with ProsopoProvider and provided the required configuration (dappName, networkId).

  2. Hook not working: Ensure you're using useProsopo hook inside a component that's wrapped by ProsopoProvider. The hook must be used within the provider context.

  3. Verification failing: Check that the CAPTCHA challenge is completed correctly. The verification process is async, so handle it with proper error handling.

  4. Network configuration: Ensure the networkId in the provider config matches your environment. Use 'development' for local development.

  5. Captcha not rendering: Make sure captcha is available from the hook before calling captcha.render(). Check that the provider is properly initialized.

  6. State management: Track verification state in your component. The verify() function returns a promise that resolves to a boolean indicating verification success.

Next Steps

Now that you have an understanding of ProCaptcha:

  • Learn about advanced CAPTCHA configurations
  • Explore network-specific settings
  • Implement CAPTCHA in different form types
  • Add CAPTCHA to authentication flows
  • Learn about privacy-preserving features
  • Check the official repository: https://github.com/prosopo/captcha
  • Look for part 57 of this series for more advanced topics

Summary

You've successfully integrated ProCaptcha into your React application and created CAPTCHA protection for forms. ProCaptcha provides a privacy-preserving, decentralized solution for bot protection with flexible configuration options.

SEO Keywords

procaptcha
React ProCaptcha
procaptcha tutorial
React privacy CAPTCHA
procaptcha installation
React Prosopo CAPTCHA
procaptcha example
React decentralized CAPTCHA
procaptcha setup
React bot protection
procaptcha customization
React CAPTCHA library
procaptcha verification
React privacy-preserving
procaptcha getting started

Top comments (0)