Skip to content

Keycloak

Official CaptchaLa plugin for Keycloak. Adds a CAPTCHA challenge to Keycloak's built-in login, registration and reset-password screens, verified server-side, without writing any code. Ships as a single provider JAR you drop into Keycloak.

What it covers

Each flow below is wired in as a separate authentication step — you protect only the screens you want.

  • Browser login (the username / password form)
  • Self-registration
  • Reset password (the "forgot password" screen)

The challenge renders as a floating widget by default (float), or as a click-to-open modal (popup).

Install

1. Get the JAR

The plugin is open source at github.com/Captcha-La/captchala-keycloak. Two options:

  • Download the release (recommended) — grab keycloak-captchala-<version>.jar from the repository's Releases page.
  • Build from sourcegit clone the repo and run mvn clean package; the JAR is produced under target/.

The plugin targets Keycloak ≥ 26 (Quarkus) and Java 17.

2. Deploy it

bash
cp keycloak-captchala-1.0.0.jar /opt/keycloak/providers/
/opt/keycloak/bin/kc.sh build
# restart Keycloak

Then set the login theme: Realm settings → Themes → Login theme → captchala.

3. Get your keys

Sign up at dash.captcha.la, create an application, then copy:

  • App Key — public, embedded in the page
  • App Secret — server-side only, used by Keycloak to mint a one-time token and call /v1/validate

4. Wire the flows

In the admin console, add the matching execution to each flow you want protected, then click the gear icon to paste your App Key and App Secret:

  • Browser login — duplicate the browser flow, replace Username Password Form with Captchala Username Password Form (REQUIRED), bind it as the realm browser flow.
  • Registration — duplicate the registration flow, add Captchala Registration (REQUIRED) to the registration-form sub-flow, bind it as the realm registration flow.
  • Reset password — duplicate the reset credentials flow, replace Choose User with Captchala Reset Credential (choose user) (REQUIRED), bind it as the realm reset-credentials flow.

Open one of the protected screens in a private window to confirm the challenge renders.

Configuration

Every execution is configured independently from its gear icon. The defaults work out of the box once the keys are set.

SettingKeyDefaultDescription
App Keyapp.keyPublic key from the CaptchaLa dashboard. Required.
App Secretapp.secretServer secret. Required. Stored masked, never exposed to the browser.
API base URLapi.basehttps://apiv1.captcha.laCaptchaLa API base.
Widget script URLscript.urlhttps://api.captcha.la/sdk/jslib/captchala.jsWhere the widget JS loads from.
Widget CSS URLcss.urlhttps://api.captcha.la/sdk/jslib/captchala.cssWhere the widget CSS loads from.
Render modeproductfloatfloat (floating widget) or popup (modal).
Action labelactionper flowThe action sent on validation (login, register, reset).
Languagelanguagerealm localeWidget language; blank follows the Keycloak locale.
Fail closedfail.closedtrueWhen the CaptchaLa API is unreachable, deny the submission. Turn off to fail open.
Use server tokenuse.server.tokentrueMint a one-time server token at render for stronger anti-replay.

FAQ

Is the plugin free?

Yes. The Keycloak plugin is free and open source. The CaptchaLa free plan covers 10,000 verifications a month — paid plans only apply if you need more volume.

Which Keycloak versions are supported?

Keycloak 26+ on the Quarkus distribution, built against 26.5.6 with Java 17. Drop the JAR into providers/, run kc.sh build, and restart.

Which screens can it protect?

Browser login, self-registration, and reset password. Each is added to its flow independently, so you can protect one, two, or all three.

Is the App Secret ever exposed to the browser?

No. The secret is stored masked and is only ever sent in the X-App-Secret header from Keycloak to the CaptchaLa API. It never appears in any page, template, or log. Each challenge is verified with a single-use token, so a solved token can't be replayed.

What happens if the CaptchaLa API is unreachable?

By default the plugin fails closed (the submission is denied). Set fail.closed to off on any execution to fail open instead — useful for staged rollout, not recommended for production.

Source

MIT-licensed examples · CaptchaLa is operated independently