Provider-agnostic captcha components for Vaadin Flow. Google reCAPTCHA is intentionally not supported.
- Friendly Captcha (
com.gladtek.vaadin.captchas.friendlycaptcha) - Cloudflare Turnstile (
com.gladtek.vaadin.captchas.turnstile) - hCaptcha (
com.gladtek.vaadin.captchas.hcaptcha)
Each provider ships a Flow Component implementing the shared Captcha
interface, and a matching CaptchaVerifier for server-side token
verification.
<dependency>
<groupId>com.gladtek.vaadin.addons</groupId>
<artifactId>captchas-addon</artifactId>
<version>1.0.1</version>
</dependency>Published to Maven Central.
| Addon version | Vaadin version |
|---|---|
| 1.0.1 | 25.0.0+ |
| 1.0.0 | 25.0.0+ |
All three providers implement the same Captcha (getSiteKey(), reset(),
addTokenListener(...)) and CaptchaVerifier (verify(String token))
interfaces, so application code written against those interfaces is
portable, so swap FriendlyCaptcha/FriendlyCaptchaVerifier for
Turnstile/TurnstileVerifier or HCaptcha/HCaptchaVerifier freely. The
verifier constructors differ per provider's actual API requirements:
FriendlyCaptchaVerifier(String secret, String siteKey) needs both, while
TurnstileVerifier(String secret) and HCaptchaVerifier(String secret) only
need the secret.
FriendlyCaptcha captcha = new FriendlyCaptcha("your-site-key");
captcha.addTokenListener(event -> {
String token = event.getToken();
VerificationResult result = new FriendlyCaptchaVerifier("your-secret", "your-site-key")
.verify(token);
if (result.success()) {
// proceed
}
});
add(captcha);Read captcha.getValue() at click time rather than reacting to the token
event immediately, useful when verification should only happen once, as
part of an overall form submission:
Turnstile captcha = new Turnstile("your-site-key");
Button submit = new Button("Submit", event -> {
VerificationResult result = new TurnstileVerifier("your-secret")
.verify(captcha.getValue());
if (result.success()) {
// proceed
} else {
captcha.reset();
}
});
add(captcha, submit);reset() clears the solved token and re-triggers the widget's own challenge
flow, so the user gets a fresh attempt after a failed server-side
verification:
VerificationResult result = new HCaptchaVerifier("your-secret").verify(captcha.getValue());
if (!result.success()) {
captcha.reset();
}Each component is also a Vaadin HasValue<String> field: its value is the
solved token, or null until the challenge is completed. That means it can
be treated exactly like a TextField in a Binder, with zero extra wiring:
Turnstile captcha = new Turnstile("your-site-key");
Binder<MyForm> binder = new Binder<>(MyForm.class);
binder.forField(captcha)
.asRequired("Captcha is required")
.bind(MyForm::getCaptchaToken, MyForm::setCaptchaToken);binder.validate() will show the standard "required" error until the
challenge is solved, the same way it does for any other empty required
field. This only validates that a token was produced client-side; call the
matching CaptchaVerifier server-side (e.g. on submit) to actually verify
it, same as always.
Each component exposes optional setters for provider-specific display options,
on top of the required siteKey. All default to the provider's own behavior
when left unset:
Turnstile:setTheme(CaptchaTheme),setSize(TurnstileSize),setAppearance(TurnstileAppearance)(e.g.INTERACTION_ONLYfor a managed/invisible-style challenge).HCaptcha:setTheme(CaptchaTheme),setSize(HCaptchaSize)(includingINVISIBLE).FriendlyCaptcha:setTheme(CaptchaTheme),setStartMode(FriendlyCaptchaStartMode)(FOCUS/NONEto defer solving until the widget is interacted with or started manually).
Turnstile captcha = new Turnstile("your-site-key");
captcha.setTheme(CaptchaTheme.DARK);
captcha.setSize(TurnstileSize.COMPACT);These must be set before the component is attached to the page; the widget reads them once, on first render.
Automated tests cover DOM-event-to-server-token wiring and verifier HTTP behavior against a stub server. They cannot solve a real provider challenge in a headless browser; manual/exploratory testing against each provider's sandbox/test keys is required before shipping a change to the widget scripts.