CakePHP Formulare mit hCaptcha absichern

4 May 2020

In diesem Blogartikel wird beschrieben, wie Ihr CakePHP Formulare mit dem Dienst hCaptcha absichern könnt.

Sicherlich kennt ihr CAPTCHAS, und auch wenn diese mittlerweile durch Machine Learning relativ simple gelöst [1][2][3] werden können, kann es Sinn machen eure Html Formulare (wie z.B. Registrierungs- und Login Formulare) damit abzusichern.

Zunächst solltet ihr einen Account bei hCaptcha erstellen. Dort erhaltet ihr einen Sitekey und einen Secretkey. Es macht Sinn, beide Keys in eure lokale CakePHP App Config einzutragen:

// config/app_local.php
'hCaptcha' => [
    'sitekey' => 'YOUR_SITE_KEY',
    'secret' => 'YOUR_SECRET_KEY',
],

Als nächstes solltet ihr die Javascript Bibliothek von hCaptcha in eurem Layout Template einbinden:

// templates/layout/default.php
// im head oder am Ende des body tags:
<script src="https://hcaptcha.com/1/api.js" async defer></script>

In dem abzusichernden Formular fügt ihr nun das CAPTCHA an sich ein und lest den Sitekey aus der Config aus:

// z.B. templates/Articles/add.php
<?= $this->Form->create($article) ?>
<fieldset>
    <legend><?= __('Add Article') ?></legend>
    <?= $this->Form->control('user_id', ['options' => $users]) ?>
    <?= $this->Form->control('title') ?>
    <?= $this->Form->control('body') ?>
    <?= $this->Form->control('published') ?>
    <div class="h-captcha" data-sitekey="<?= Configure::read('hCaptcha.sitekey') ?>"></div>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>

Durch das Lösen des CAPTCHAs und senden des Formulars wird dem entsprechenden Controller der POST Parameter h-captcha-response in Form eines Tokens übergeben. Dieser Token muss wiederrum mit dem Secretkey an den hCaptcha API Endpoint (https://hcaptcha.com/siteverify) mit einem POST Request übergeben werden, um das Ergebnis zu verifizieren. Dazu nutzen wir den HTTP Client von CakePHP:

// z.B. src/Controller/ArticlesController.php
use Cake\Core\Configure;
use Cake\Http\Client;

...

public function add()
{
    $article = $this->Articles->newEmptyEntity();
    if ($this->request->is('post')) {
        // Token aus den Requestdaten
        $token = $this->request->getData('h-captcha-response');
        // httpClient instanziieren und POST request an API Endpoint
        $httpClient = new Client();
        $response = $httpClient->post('https://hcaptcha.com/siteverify', [
            'secret' => Configure::read('hCaptcha.secret'),
            'response' => $token,
        ]);
        // die Responsedaten von hCaptcha erhalten wir als JSON
        $hCaptchaResult = $response->getJson();

        // Das Ergebnis prüfen wir auf *success* und speichern den Artikel
        if ($hCaptchaResult['success']) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {
                $this->Flash->success(__('The article has been saved.'));

                return $this->redirect(['action' => 'index']);
            }
        } else {
          // Die API gibt Fehlercodes zurück, falls z.B. das CAPTCHA nicht richtig gelöst wurde.
          // alle Fehlercodes unter https://docs.hcaptcha.com/#server
        }

        $this->Flash->error(__('The article could not be saved. Please, try again.'));
    }
    $users = $this->Articles->Users->find('list', ['limit' => 200]);
    $this->set(compact('article', 'users'));
}

Um hCaptcha in mehreren Controllern zu verwenden kann der Code in eine Component ausgelagert werden.


  1. Solving CAPTCHAs — Machine learning vs online services
  2. Breaking CAPTCHA Using Machine Learning in 0.05 Seconds
  3. How to break a CAPTCHA system in 15 minutes with Machine Learning
  4. hCaptcha Docs