Google reCAPTCHA v2 Examples
Google reCAPTCHA v2 provides robust bot protection with both visible checkbox and invisible modes. It requires different site keys for visible vs invisible implementations.
Visible reCAPTCHA
Visible reCAPTCHA displays a checkbox that users must interact with. The token is generated automatically when the user completes the challenge.
Key Points for reCAPTCHA
Configuration
- Visible mode: Use standard site key with
size: 'normal'orsize: 'compact' - Invisible mode: Requires different site key and
size: 'invisible' - Badge position: For invisible mode, control badge placement with
badgeoption
Best Practices
- Always handle all three event callbacks (
onToken,onError,onExpired) - Reset widgets after successful form submission
- For invisible mode, trigger execution on form submission, not page load
Basic Implementation
import { Captcha } from '@thg-altitude/captcha';
const recaptcha = new Captcha({
provider: 'recaptcha',
siteKey: 'your-recaptcha-site-key' // Different key needed for invisible mode
});
await recaptcha.render('captcha-container', 'contact-form', {
size: 'normal', // 'normal' | 'compact'
onToken: (captchaToken) => {
console.log('reCAPTCHA solved:', captchaToken);
setToken(captchaToken);
},
onError: () => {
showErrorMessage('Security verification failed. Please try again.');
},
onExpired: () => {
console.log('reCAPTCHA token expired');
showMessage('Security verification expired. Please complete it again.');
}
});
React Implementation
import { useEffect, useRef, useState } from 'react';
import { Captcha } from '@thg-altitude/captcha';
function ContactForm() {
const captchaRef = useRef<HTMLDivElement>(null);
const captcha = useRef<Captcha>();
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [error, setError] = useState<string>('');
useEffect(() => {
captcha.current = new Captcha({
provider: 'recaptcha',
siteKey: 'your-recaptcha-site-key'
});
if (captchaRef.current) {
captcha.current.render(captchaRef.current, 'contact-form', {
size: 'normal',
onToken: handleCaptchaToken,
onError: handleCaptchaError,
onExpired: handleCaptchaExpired
});
}
return () => {
captcha.current?.reset('contact-form');
};
}, []);
const handleCaptchaToken = (token: string) => {
setCaptchaToken(token);
setError('');
};
const handleCaptchaError = () => {
setCaptchaToken(null);
setError('Security verification failed. Please try again.');
};
const handleCaptchaExpired = () => {
setCaptchaToken(null);
setError('Security verification expired. Please complete it again.');
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!captchaToken) {
setError('Please complete the security verification.');
return;
}
try {
await submitForm(captchaToken);
captcha.current?.reset('contact-form');
setCaptchaToken(null);
} catch (error) {
setError('Form submission failed. Please try again.');
}
};
return (
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Email" required />
<textarea placeholder="Message" required />
{error && <div className="error-message">{error}</div>}
<div ref={captchaRef} id="captcha-container"></div>
<button type="submit" disabled={!captchaToken}>
Submit
</button>
</form>
);
}
Invisible reCAPTCHA
Invisible reCAPTCHA runs in the background and only shows a challenge when suspicious activity is detected. It requires manual execution and a different site key.
Basic Implementation
import { Captcha } from '@thg-altitude/captcha';
const invisibleRecaptcha = new Captcha({
provider: 'recaptcha',
siteKey: 'your-invisible-recaptcha-key' // Different key for invisible mode
});
await invisibleRecaptcha.render('container-id', 'form-protection', {
size: 'invisible',
badge: 'bottomright', // 'bottomright' | 'bottomleft' | 'inline'
onToken: (token) => {
// Automatically submit form when token is received
submitForm(token);
},
onError: () => {
console.error('reCAPTCHA verification failed');
}
});
// Execute on form submission
document.getElementById('submit-btn').onclick = async () => {
try {
await invisibleRecaptcha.execute('form-protection');
// Token will be provided via onToken callback
} catch (error) {
console.error('CAPTCHA verification failed:', error);
}
};
React Implementation
import { useEffect } from 'react';
import { Captcha } from '@thg-altitude/captcha';
function ContactForm() {
let captcha;
const captchaId = 'contact-form';
const containerId = 'contact-form-recaptcha';
useEffect(() => {
captcha = new Captcha({
provider: 'recaptcha',
siteKey: 'your-invisible-recaptcha-key'
});
captcha.render(containerId, captchaId, {
size: 'invisible',
badge: 'bottomright',
onToken: (captchaToken) => {
submitForm(captchaToken);
},
onError: () => {
console.error('Error rendering captcha:', captchaId);
}
});
}, []);
const submitForm = async (token) => {
try {
await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Captcha-Response': token
},
body: JSON.stringify(formData)
});
} catch (error) {
console.error('Form submission failed:', error);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!captcha) return;
try {
await captcha.execute(captchaId);
// Form submission happens in onToken callback
} catch (error) {
console.error('reCAPTCHA failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Name" required />
<input type="email" placeholder="Email" required />
<textarea placeholder="Message" required></textarea>
<div id={containerId}></div>
<button type="submit">Submit</button>
</form>
);
}
Vue Implementation
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" type="text" placeholder="Name" required />
<input v-model="form.email" type="email" placeholder="Email" required />
<textarea v-model="form.message" placeholder="Message" required></textarea>
<div id="captchaContainer"></div>
<button type="submit">Submit</button>
</form>
</template>
<script setup>
import { ref, onMounted, onUnmounted, reactive } from 'vue';
import { Captcha } from '@thg-altitude/captcha';
let captcha: Captcha;
const captchaId = 'contact-form';
const containerId = 'captchaContainer';
const form = reactive({
name: '',
email: '',
message: ''
});
onMounted(async () => {
captcha = new Captcha({
provider: 'recaptcha',
siteKey: 'your-invisible-recaptcha-key'
});
await captcha.render(containerId, captchaId, {
size: 'invisible',
onToken: (captchaToken) => {
submitForm(captchaToken);
},
onError: () => {
console.error('Error rendering captcha:', captchaId);
}
});
});
onUnmounted(() => {
captcha?.reset(captchaId);
});
const submitForm = async (token) => {
try {
await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Captcha-Response': token
},
body: JSON.stringify(form)
});
} catch (error) {
console.error('Form submission failed:', error);
}
};
const handleSubmit = async () => {
if (!captcha) return;
try {
await captcha.execute(captchaId);
} catch (error) {
console.error('reCAPTCHA failed:', error);
}
};
</script>