Integrationen

Fertige Pakete für Laravel, Symfony, WordPress und die WoltLab Suite — und die Anleitung für eine eigene.

Laravel Integration

1. Repository eintragen

Fügen Sie das CaptchaCore-Repository in Ihre composer.json ein:

{
    "repositories": [
        {
            "type": "composer",
            "url": "https://captchacore.eu/packages"
        }
    ]
}

2. Paket installieren

composer require captchacore/laravel

# optional
php artisan vendor:publish --tag=captchacore-config
php artisan vendor:publish --tag=captchacore-lang

ServiceProvider, Middleware-Alias, Blade-Component und Facade registriert Laravel per Auto-Discovery. Die Konfiguration müssen Sie nur veröffentlichen, wenn Sie Werte außerhalb der .env ändern wollen.

3. .env konfigurieren

CAPTCHACORE_URL=https://api.captchacore.eu
CAPTCHACORE_SITE_KEY=cc_pub_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
CAPTCHACORE_SECRET_KEY=cc_sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

# optional
CAPTCHACORE_TIMEOUT=3             # Sekunden
CAPTCHACORE_FAIL_OPEN=true        # bei Störung des Dienstes durchlassen
CAPTCHACORE_WIDGET_MODE=          # interactive | visible | invisible
CAPTCHACORE_WIDGET_THEME=         # auto | light | dark
CAPTCHACORE_WIDGET_COLOR=         # z. B. #34d399
CAPTCHACORE_WIDGET_LABEL=
CAPTCHACORE_WIDGET_BRAND=
CAPTCHACORE_WIDGET_SIZE=          # normal | compact
CAPTCHACORE_WIDGET_LANG=          # de | en | fr | es …
CAPTCHACORE_WIDGET_ENDPOINT=eu    # eu | global
CAPTCHACORE_WIDGET_CDN_URL=       # eigener Widget-Host (optional)

Leer gelassene Widget-Optionen gibt das Paket nicht aus: Das Widget übernimmt dann die Einstellungen aus dem Kundenbereich und die Sprache der Seite.

Blade-Component

<form method="post" action="/contact">
    @csrf
    <!-- … -->
    <x-captchacore::widget form-type="contact" />

    @error('captchacore')
        <p class="error">{{ $message }}</p>
    @enderror

    <button type="submit">Absenden</button>
</form>

Der Formulartyp von Widget und Prüfung muss gleich sein: form-type="contact" gehört zu captchacore:contact bzw. new CaptchaCoreToken('contact'). Jeder Typ hat eine eigene Schwierigkeit; ein abweichender Typ wird abgelehnt.

Middleware

use App\Http\Controllers\ContactController;
use Illuminate\Support\Facades\Route;

Route::post('/contact', [ContactController::class, 'send'])
    ->middleware('captchacore:contact');

Geprüft werden POST, PUT, PATCH und DELETE. Bei Ablehnung leitet die Middleware mit Eingaben und der Meldung unter dem Schlüssel captchacore zurück. Anfragen per fetch() oder XHR erhalten JSON mit Status 422 und errors.captchacore — senden Sie dazu Accept: application/json mit (Beispiel unter „Formulare per fetch() absenden“).

Validation Rule

use CaptchaCore\Laravel\Rules\CaptchaCoreToken;

'captchacore_token' => [new CaptchaCoreToken('contact')],

Die Regel greift auch ohne Token — required ist nicht nötig und würde eine zweite Meldung erzeugen. Die Meldung steht unter dem Feldnamen: @error('captchacore_token').

Facade

use CaptchaCore\Laravel\Facades\CaptchaCore;

$result = CaptchaCore::verify($request->input('captchacore_token'), 'comment');

if (! $result->passed()) {
    return back()->withInput()->withErrors(['captchacore' => __('captchacore::messages.failed')]);
}

Meldungen, Logs und Proxys

  • Die Meldungen liegen auf Deutsch, Englisch, Spanisch und Französisch bei und folgen der App-Sprache. Anpassen nach vendor:publish --tag=captchacore-lang in lang/vendor/captchacore.
  • Ablehnungen landen mit Gründen und Risikowert als info im Log, Ausfälle mit Fail-Open als warning — das Token selbst wird nie protokolliert.
  • Hinter Load-Balancer, CDN oder Reverse-Proxy richten Sie TrustProxies ein ($middleware->trustProxies(at: …)), sonst sieht CaptchaCore für alle Besucher die IP des Proxys.
  • Strikte CSP: Die Komponente übernimmt den Vite-Nonce (Vite::useCspNonce()) oder nonce="…" und setzt ihn ans Script-Tag.

Tests

use CaptchaCore\Laravel\Facades\CaptchaCore;

CaptchaCore::fake();   // jede Prüfung besteht; wird bei jedem App-Start zurückgesetzt
$this->post('/contact', ['captchacore_token' => 'fake'])->assertRedirect();

Symfony Bundle

Für Symfony 6.4 LTS, 7.x und 8.x. Vier Integrationswege: FormType, Validator-Constraint, Controller-Attribute und Security-Badge für den Login.

1. Installieren

Fügen Sie das CaptchaCore-Repository in Ihre composer.json ein:

{
    "repositories": [
        { "type": "composer", "url": "https://captchacore.eu/packages" }
    ]
}
composer require captchacore/captchacore-bundle

Bundle in config/bundles.php eintragen und die Konfiguration als config/packages/captchacore.yaml anlegen: Beides ist von Hand nötig — für das Bundle gibt es kein Flex-Rezept.

// config/bundles.php
CaptchaCore\SymfonyBundle\CaptchaCoreBundle::class => ['all' => true],

# config/packages/captchacore.yaml
captchacore:
    service_url: '%env(CAPTCHACORE_URL)%'
    site_key: '%env(CAPTCHACORE_SITE_KEY)%'
    secret_key: '%env(CAPTCHACORE_SECRET_KEY)%'

2. .env konfigurieren

CAPTCHACORE_URL=https://api.captchacore.eu
CAPTCHACORE_SITE_KEY=cc_pub_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
CAPTCHACORE_SECRET_KEY=cc_sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

FormType (empfohlen)

use CaptchaCore\SymfonyBundle\Form\Type\CaptchaCoreType;

$builder
    ->add('email', EmailType::class)
    ->add('message', TextareaType::class)
    ->add('captcha', CaptchaCoreType::class, [
        'form_type' => 'contact',
    ]);

Für HTML-Formulare der empfohlene Weg: Eine Ablehnung erscheint als normale Formularfehlermeldung. Das Widget sendet sein Token immer als captchacore_token; der FormType übernimmt es vor der Validierung.

Controller-Attribute

use CaptchaCore\SymfonyBundle\Security\Attribute\RequiresCaptcha;

#[Route('/contact', methods: ['POST'])]
#[RequiresCaptcha(formType: 'contact')]
public function submit(Request $request): Response { /* ... */ }

Eine Ablehnung endet mit Status 403 (im Log als info, nicht als error) — passend für Endpunkte, die per JavaScript aufgerufen werden. Mit throwOnFailure: false kommt stattdessen JSON mit Status 422. Für HTML-Formulare nehmen Sie den FormType. Den Login schützt das Attribut nicht, weil die Firewall vor dem Controller anmeldet.

Validator-Constraint (DTOs)

Für #[MapRequestPayload] braucht Symfony zusätzlich symfony/serializer und symfony/property-access.

use CaptchaCore\SymfonyBundle\Validator\CaptchaCoreToken;

final class ContactDto {
    public function __construct(
        #[Assert\NotBlank]
        public string $email,

        #[CaptchaCoreToken(formType: 'contact')]
        public string $captchaToken,
    ) {}
}

Programmatisch

use CaptchaCore\SymfonyBundle\Client\CaptchaCoreClient;

$result = $this->captcha->verify($token, 'login');

if ($result->blocked()) {
    throw new AccessDeniedHttpException();
}

// Properties: valid, riskScore, action, confidence, reasons, stepUp

Twig (standalone)

{{ captchacore_widget(form_type: 'contact') }}

@{# Strikte CSP: Nonce Ihrer Seite mitgeben #}
{{ captchacore_widget(form_type: 'contact', nonce: page_nonce) }}
{{ form_row(form.captcha, {nonce: page_nonce}) }}

Ohne form_type gilt wie beim FormType default_form_type. Das Script wird pro Seite nur einmal eingebunden, auch bei mehreren Widgets oder gemischt mit dem FormType. Das Widget übernimmt den Nonce seines Script-Tags für sein Style-Element; erlauben Sie ihn in script-src und style-src-elem. Einen eigenen Widget-Host setzen Sie mit widget.cdn_url.

Login schützen

Das Standard-form_login von Symfony ist nicht geschützt: Geprüft werden nur Anmeldungen, die ein CaptchaCoreBadge tragen. Ersetzen Sie form_login durch einen eigenen Authenticator, der das Badge anhängt — das Token wird dann vor dem Passwort geprüft.

// src/Security/LoginFormAuthenticator.php
namespace App\Security;

use CaptchaCore\SymfonyBundle\Security\CaptchaCoreAuthenticator;
use CaptchaCore\SymfonyBundle\Security\CaptchaCoreBadge;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\SecurityRequestAttributes;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

final class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
    use TargetPathTrait;

    public function __construct(private readonly UrlGeneratorInterface $urlGenerator) {}

    public function authenticate(Request $request): Passport
    {
        $payload = $request->getPayload();
        $username = $payload->getString('_username');
        $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $username);

        return new Passport(
            new UserBadge($username),
            new PasswordCredentials($payload->getString('_password')),
            [
                new CaptchaCoreBadge(CaptchaCoreAuthenticator::extractToken($request) ?? '', 'login'),
                new CsrfTokenBadge('authenticate', $payload->getString('_csrf_token')),
                new RememberMeBadge(),
            ],
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        $target = $this->getTargetPath($request->getSession(), $firewallName);

        return new RedirectResponse($target ?? $this->urlGenerator->generate('app_home'));
    }

    protected function getLoginUrl(Request $request): string
    {
        return $this->urlGenerator->generate('app_login');
    }
}
# config/packages/security.yaml — statt form_login
security:
    firewalls:
        main:
            custom_authenticator: App\Security\LoginFormAuthenticator
            logout:
                path: app_logout

@{# templates/security/login.html.twig, im <form method="post"> #}
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
{{ captchacore_widget(form_type: 'login') }}

app_login ist die Route Ihrer Login-Seite (GET zeigt das Formular, POST übernimmt der Authenticator), app_home die Seite nach der Anmeldung. Das vollständige Beispiel mit Controller und Template steht in der README des Bundles.

Tests

CaptchaCoreClient::fake(VerificationResult::allow());
$this->client->request('POST', '/contact', [...]);
self::assertResponseIsSuccessful();

WordPress Plugin

  1. Plugin installieren — in WordPress unter Plugins → Installieren nach „CaptchaCore" suchen (Eintrag im WordPress-Verzeichnis). Updates kommen dann von WordPress selbst. Alternativ das ZIP aus dem Kundenbereich hochladen.
  2. Aktivieren unter Plugins
  3. Einstellungen > CaptchaCore öffnen
  4. Service-URL, Site Key und Secret Key eintragen
  5. Widget-Modus, Theme und Farbe wählen
  6. Gewünschte Formulare aktivieren (Login, Register, Kommentare, etc.)

Kein Code nötig. Das Plugin bindet das Widget automatisch in alle aktivierten WordPress-Formulare ein und verifiziert Tokens serverseitig.

WoltLab Suite

Das Paket wird über den offiziellen WoltLab Plugin-Store bezogen. Installation und alle künftigen Updates laufen darüber — im Kundenbereich gibt es dafür keinen eigenen Download.

  1. Paket erwerben im Plugin-Store und im ACP unter Pakete → Paket installieren einspielen
  2. Zugangsdaten hinterlegen unter Optionen → Sicherheit → Anti-Spam → CaptchaCore: Service-URL, Site Key und Secret Key
  3. Erscheinungsbild wählen: Modus, Theme, Akzentfarbe und Sprache — „auto" übernimmt die Sprache des Forums.
  4. CaptchaCore als Captcha auswählen unter Optionen → Sicherheit → Anti-Spam → Captcha — damit gilt es für Registrierung, Kontaktformular und alle weiteren Systemformulare
Einstellung Bedeutung
Endpunkt EU-Standard oder weltweiter Endpunkt. EU hält alle Verifikationsdaten in der EU; der globale Endpunkt beschleunigt weit entfernte Besucher.
Modus interactive, visible oder invisible — dieselben drei Modi wie überall, siehe Widget-Modi.
Verhalten bei Ausfall Ist CaptchaCore nicht erreichbar, lässt das Plugin das Formular standardmäßig durch und protokolliert den Fehler. Abschaltbar, wenn im Zweifel lieber blockiert werden soll.

Ein Paket für 6.1 und 6.2. Der Handler prüft jeden Token serverseitig gegen die V2-API; die Zusatzprüfung („step_up") erledigt das Widget selbst und der Nutzer bekommt dafür eine eigene Meldung statt einer Fehlermeldung.

Eigene Integration bauen

Sie wollen CaptchaCore in ein System bringen, für das es noch kein Paket gibt, etwa Shopware, Joomla oder ein eigenes Framework? Der Aufwand liegt bei wenigen Stunden. Diese Reihenfolge hat sich bewährt.

01

Script ausliefern

Binden Sie captchacore-v2.min.js vom EU-Endpunkt ein und geben Sie Site-Key und Service-URL als data-Attribute mit. In einem CMS gehört das in den Hook, der Skripte im Frontend registriert. Laden Sie das Script nur auf Seiten, die ein geschütztes Formular enthalten.

02

Formulare markieren

Setzen Sie data-captchacore auf das Formular und fügen Sie einen leeren Container mit data-captchacore-widget ein. Über data-form-type bestimmen Sie, welche Policy greift. Mehrere Formulare auf einer Seite sind kein Problem, jedes bekommt sein eigenes Widget.

03

Token serverseitig prüfen

Lesen Sie das Feld captchacore_token aus dem Request und schicken Sie es mit Ihrem geheimen Schlüssel an /api/v2/verify. Prüfen Sie decision und behandeln Sie step_up nicht wie block.

04

Ausfälle abfangen

Setzen Sie einen Timeout von etwa drei Sekunden und entscheiden Sie bewusst, ob bei einem Ausfall durchgelassen oder blockiert wird. Machen Sie das im Plugin einstellbar, die Antwort fällt je nach Formular anders aus.

05

Nachladen berücksichtigen

Wenn Ihr System Formulare per AJAX austauscht, rufen Sie danach CaptchaCoreV2.attach() auf.

06

Einstellungen anbieten

Site-Key, geheimer Schlüssel, Auswahl der geschützten Formulare, Erscheinungsbild und das Verhalten bei Ausfall gehören in die Konfigurationsoberfläche Ihres Plugins.

Vorlagen zum Abschauen

Alle mitgelieferten Integrationen sind nach demselben Muster gebaut. Für ein Symfony-basiertes System wie Shopware 6 ist das Symfony-Bundle die nächstliegende Vorlage, für ein klassisches CMS das WordPress-Plugin.

Vor der Veröffentlichung prüfen

  • Formular ohne JavaScript abgeschickt — greift Ihre Fail-Regel?
  • Token zweimal verwendet — die zweite Prüfung muss scheitern, der Nonce ist einmalig.
  • Token nach mehr als fünf Minuten abgeschickt — das Widget erneuert ihn selbst, prüfen Sie das im Langzeittest.
  • Zwei geschützte Formulare auf einer Seite — beide müssen unabhängig funktionieren.
  • Formular per AJAX nachgeladen — erscheint das Widget?
  • Geheimer Schlüssel taucht nirgends im HTML oder in JavaScript auf.
  • Antwort mit decision=step_up — der Nutzer darf nicht in einer Sackgasse landen.

Sie bauen eine Integration und möchten, dass wir sie hier aufführen oder mitpflegen? Melden Sie sich — wir stellen Testzugänge und einen Ansprechpartner bereit.

← Zurück Einstieg Weiter → Widget