Intégrations

Des paquets prêts à l’emploi pour Laravel, Symfony, WordPress et WoltLab Suite — et le guide pour créer le vôtre.

Intégration Laravel

1. Déclarer le dépôt

Ajoutez le dépôt CaptchaCore à votre composer.json :

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

2. Installer le paquet

composer require captchacore/laravel

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

Laravel enregistre le ServiceProvider, l’alias du middleware, le composant Blade et la façade via l’auto-discovery. Vous ne devez publier la configuration que si vous souhaitez modifier des valeurs en dehors du .env.

3. Configurer le .env

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)

Le paquet n’émet pas les options du widget laissées vides : le widget reprend alors les réglages de l’espace client et la langue de la page.

Composant Blade

<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>

Le type de formulaire du widget et de la vérification doit être identique : form-type="contact" correspond à captchacore:contact ou new CaptchaCoreToken('contact'). Chaque type a sa propre difficulté ; un type différent est rejeté.

Middleware

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

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

Les méthodes POST, PUT, PATCH et DELETE sont vérifiées. En cas de refus, le middleware redirige en arrière avec les saisies et le message sous la clé captchacore. Les requêtes via fetch() ou XHR reçoivent du JSON avec le statut 422 et errors.captchacore — envoyez pour cela Accept: application/json (exemple sous « Envoyer des formulaires via fetch() »).

Règle de validation

use CaptchaCore\Laravel\Rules\CaptchaCoreToken;

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

La règle s’applique aussi sans token — required n’est pas nécessaire et produirait un second message. Le message se trouve sous le nom du champ : @error('captchacore_token').

Façade

use CaptchaCore\Laravel\Facades\CaptchaCore;

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

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

Messages, logs et proxys

  • Les messages sont fournis en allemand, anglais, espagnol et français et suivent la langue de l’application. Adaptez-les après vendor:publish --tag=captchacore-lang dans lang/vendor/captchacore.
  • Les refus sont journalisés en info avec les motifs et le score de risque, les pannes avec fail-open en warning — le token lui-même n’est jamais journalisé.
  • Derrière un répartiteur de charge, un CDN ou un proxy inverse, configurez TrustProxies ($middleware->trustProxies(at: …)), sinon CaptchaCore voit l’IP du proxy pour tous les visiteurs.
  • CSP stricte : le composant reprend le nonce de Vite (Vite::useCspNonce()) ou nonce="…" et l’ajoute à la balise script.

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

Pour Symfony 6.4 LTS, 7.x et 8.x. Quatre voies d'intégration : FormType, contrainte de validation, attributs de contrôleur et un badge Security pour la connexion.

1. Installer

Ajoutez le dépôt CaptchaCore à votre composer.json :

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

Inscrivez le bundle dans config/bundles.php et créez la configuration dans config/packages/captchacore.yaml : Les deux étapes sont manuelles — il n’existe pas de recette Flex pour le bundle.

// 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. Configurer le .env

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

FormType (recommandé)

use CaptchaCore\SymfonyBundle\Form\Type\CaptchaCoreType;

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

La voie recommandée pour les formulaires HTML : un refus apparaît comme une erreur de formulaire normale. Le widget envoie toujours son token sous captchacore_token ; le FormType le reprend avant la validation.

Attributs de contrôleur

use CaptchaCore\SymfonyBundle\Security\Attribute\RequiresCaptcha;

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

Un refus se termine par le statut 403 (journalisé en info, pas en error) — adapté aux points de terminaison appelés via JavaScript. Avec throwOnFailure: false, vous obtenez à la place du JSON avec le statut 422. Pour les formulaires HTML, utilisez le FormType. L’attribut ne protège pas la connexion, car le pare-feu authentifie avant le contrôleur.

Contrainte Validator (DTOs)

Pour #[MapRequestPayload], Symfony a besoin en plus de symfony/serializer et 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,
    ) {}
}

Programmatique

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}) }}

Sans form_type, default_form_type s’applique, comme pour le FormType. Le script n’est inclus qu’une fois par page, même avec plusieurs widgets ou combiné au FormType. Le widget reprend le nonce de sa balise script pour son élément style ; autorisez-le dans script-src et style-src-elem. Définissez votre propre hôte de widget avec widget.cdn_url.

Protéger la connexion

Le form_login standard de Symfony n’est pas protégé : seules les connexions portant un CaptchaCoreBadge sont vérifiées. Remplacez form_login par votre propre authenticator qui ajoute le badge — le token est alors vérifié avant le mot de passe.

// 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 est la route de votre page de connexion (GET affiche le formulaire, POST est traité par l’authenticator), app_home la page après la connexion. L’exemple complet avec contrôleur et template se trouve dans le README du bundle.

Tests

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

Plugin WordPress

  1. Installez l’extension — dans WordPress, ouvrez Extensions → Ajouter et cherchez « CaptchaCore » (fiche dans le répertoire WordPress). Les mises à jour viennent ensuite de WordPress. Sinon, téléversez le ZIP depuis votre espace client.
  2. Activer dans Extensions
  3. Ouvrir Réglages > CaptchaCore
  4. Renseigner l'URL du service, la clé de site et la clé secrète
  5. Choisir le mode du widget, le thème et la couleur
  6. Activer les formulaires souhaités (connexion, inscription, commentaires, etc.)

Aucun code requis. Le plugin intègre automatiquement le widget dans tous les formulaires WordPress activés et vérifie les tokens côté serveur.

WoltLab Suite

Le paquet est distribué via la boutique officielle de plugins WoltLab. L’installation et toutes les mises à jour passent par elle — il n’y a pas de téléchargement séparé dans l’espace client.

  1. Achetez le paquet dans la boutique de plugins et installez-le dans l’ACP sous Paquets → Installer un paquet
  2. Saisissez vos identifiants sous Options → Sécurité → Anti-spam → CaptchaCore : URL du service, site key et secret key
  3. Choisissez l’apparence : mode, thème, couleur d’accent et langue — « auto » suit la langue du forum.
  4. Sélectionnez CaptchaCore comme captcha sous Options → Sécurité → Anti-spam → Captcha — il s’applique alors à l’inscription, au formulaire de contact et à tous les autres formulaires du système
Réglage Signification
Point d'accès Endpoint UE par défaut ou mondial. L’UE conserve toutes les données de vérification dans l’UE ; l’endpoint global accélère la réponse pour les visiteurs éloignés.
Mode interactive, visible ou invisible — les trois mêmes modes que partout ailleurs, voir les modes du widget.
Comportement en cas de panne Si CaptchaCore est injoignable, le plugin laisse passer le formulaire par défaut et journalise l’erreur. Désactivable si vous préférez bloquer en cas de doute.

Un seul paquet pour 6.1 et 6.2. Le handler vérifie chaque jeton côté serveur via l’API V2 ; la vérification supplémentaire (« step_up ») est prise en charge par le widget lui-même et le visiteur reçoit un message dédié plutôt qu’une erreur.

Créer votre propre intégration

Vous voulez intégrer CaptchaCore dans un système sans paquet existant, comme Shopware, Joomla ou votre propre framework ? Comptez quelques heures. Cet ordre a fait ses preuves.

01

Diffuser le script

Incluez captchacore-v2.min.js depuis le point d'accès UE et transmettez la clé de site et l'URL du service comme attributs data. Dans un CMS, cela relève du hook qui enregistre les scripts du frontend. Ne chargez le script que sur les pages contenant réellement un formulaire protégé.

02

Marquer les formulaires

Placez data-captchacore sur le formulaire et insérez un conteneur vide avec data-captchacore-widget. data-form-type décide de la politique appliquée. Plusieurs formulaires sur une page ne posent pas de problème, chacun reçoit son propre widget.

03

Vérifier le jeton côté serveur

Lisez le champ captchacore_token de la requête et envoyez-le avec votre clé secrète à /api/v2/verify. Vérifiez decision et ne traitez pas step_up comme block.

04

Gérer les pannes

Fixez un délai d'attente d'environ trois secondes et décidez délibérément si une panne laisse passer ou bloque. Rendez-le configurable dans l'extension, la réponse diffère selon le formulaire.

05

Prendre en compte le chargement différé

Si votre système remplace des formulaires via AJAX, appelez ensuite CaptchaCoreV2.attach().

06

Proposer des réglages

Clé de site, clé secrète, choix des formulaires protégés, apparence et comportement en cas de panne ont leur place dans l'écran de configuration de votre extension.

Modèles dont s'inspirer

Toutes les intégrations fournies suivent le même schéma. Pour un système basé sur Symfony comme Shopware 6, le bundle Symfony est le modèle le plus proche ; pour un CMS classique, l'extension WordPress.

À vérifier avant publication

  • Formulaire envoyé sans JavaScript — votre règle de repli s'applique-t-elle ?
  • Jeton utilisé deux fois — la seconde vérification doit échouer, le nonce est à usage unique.
  • Jeton envoyé après plus de cinq minutes — le widget le renouvelle de lui-même, vérifiez-le lors d'un test de longue durée.
  • Deux formulaires protégés sur une page — les deux doivent fonctionner indépendamment.
  • Formulaire chargé via AJAX — le widget apparaît-il ?
  • La clé secrète n'apparaît nulle part dans le HTML ni en JavaScript.
  • Une réponse avec decision=step_up — l'utilisateur ne doit pas se retrouver dans une impasse.

Vous développez une intégration et souhaitez que nous la référencions ici ou que nous participions à sa maintenance ? Contactez-nous — nous fournissons des accès de test et un interlocuteur.

← Retour Prise en main Suivant → Widget