0

I have a Symfony 7.1 app and I would like to integrate AltCha via API in my contact form

I always get AltCha API returned status code 403:

{"error":"Invalid API Key.","statusCode":403}

First I though, the key is just wrong. After I checked this (and the key was right), I created a new one, just to be sure.

But still the same issue. I checked with the AltCha documentation and there is written

403 Forbidden: The API Key may not match the Referer header. Ensure you send a valid Referer header with an appropriate origin, and that the API Key was created for the same domain.

I have the referer in the header. I just can't find the problem and I really would appreciate any help.

The API- and SecretKey are stored in the .env-File and are correctly read.

Maybe the issue is, that I am trying to do this in my development enviroment with localhost?

This is my KontaktController

<?php

namespace App\Controller;

use App\Entity\Kontakt;
use App\Form\KontaktType;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class KontaktController extends AbstractController
{
    private EntityManagerInterface $em;
    private HttpClientInterface $httpClient;
    private string $altchaSecretKey;
    private string $altchaApiKey;
    private LoggerInterface $logger;

    public function __construct(
        EntityManagerInterface $em,
        HttpClientInterface    $httpClient,
        string                 $altchaSecretKey,
        string                 $altchaApiKey,
        LoggerInterface        $logger
    )
    {
        $this->em = $em;
        $this->httpClient = $httpClient;
        $this->altchaSecretKey = $altchaSecretKey;
        $this->altchaApiKey = $altchaApiKey;
        $this->logger = $logger;
    }

    #[Route('/kontakt', name: 'app_kontakt')]
    public function index(Request $request, MailerInterface $mailer): Response
    {
        $title = 'Markus Michalski - Kontakt';
        $contact = new Kontakt();
        $form = $this->createForm(KontaktType::class, $contact);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $altchaToken = $form->get('altchaToken')->getData();

            // AltCha Verifizierung
            try {
                $altchaResponse = $this->httpClient->request('POST', 'https://eu.altcha.org/api/v1/challenge/verify', [
                    'headers' => [
                        'Content-Type' => 'application/json',
                        'Authorization' => 'Bearer ' . $this->altchaSecretKey,
                        'Referer' => $request->getSchemeAndHttpHost(),
                    ],
                    'json' => [
                        'payload' => $altchaToken,
                    ],
                ]);

                $statusCode = $altchaResponse->getStatusCode();
                $content = $altchaResponse->getContent(false);

                if ($statusCode !== 200) {
                    $this->logger->error("AltCha API returned status code $statusCode: $content");
                    throw new \Exception("AltCha verification failed");
                }

                $altchaResult = $altchaResponse->toArray();

                if (!$altchaResult['verified']) {
                    $this->addFlash('error', 'AltCha-Verifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.');
                    return $this->renderFormWithData($form, $title);
                }
            } catch (\Exception $e) {
                $this->logger->error('AltCha verification failed: ' . $e->getMessage());
                $this->addFlash('error', 'Ein Fehler ist bei der Verifizierung aufgetreten. Bitte versuchen Sie es später erneut.');
                return $this->renderFormWithData($form, $title);
            }

            // Form data processing

            $contact = $form->getData();
            $contact->setTimestamp(new \DateTime('now'));
            $this->em->persist($contact);
            $this->em->flush();

            $email = new TemplatedEmail();
            $email
                ->from(new Address('XXXX', 'Kontaktformular'))
                ->to('XXXXX')
                ->subject('Anfrage über das Kontaktformular')
                ->htmlTemplate('email/kontaktSelf.html.twig')
                ->locale('de')
                ->context(['contact' => $contact, 'title' => $title]);
            try {
                $mailer->send($email);
            } catch (TransportExceptionInterface $exception) {
                $rep = $this->em->getRepository(Kontakt::class);
                $updateContact = $rep->findOneBy(['id' => $contact->getId()]);
                $updateContact->setExeption($exception->getMessage());
                $this->em->persist($updateContact);
                $this->em->flush();
            }

            return $this->render('kontakt/success.html.twig', [
                'title' => $title,
                'contact' => $contact,
            ]);
        }

        return $this->renderFormWithData($form, $title);
    }

    private function renderFormWithData($form, $title): Response
    {
        return $this->render('kontakt/index.html.twig', [
            'title' => $title,
            'form' => $form->createView(),
            'altchaApiKey' => $this->altchaApiKey,
        ]);
    }
}

My Twig-Template looks like this:

{% extends 'base.html.twig' %}

{% block body %}
    <main>
        
        <section class="section">
            <h1>Kontaktformular</h1>
            {% for label, messages in app.flashes %}
                {% for message in messages %}
                    <div class="alert alert-{{ label == 'error' ? 'danger' : label }}">
                        {{ message }}
                    </div>
                {% endfor %}
            {% endfor %}
            {{ form_start(form) }}
            {{ form_errors(form) }}

            <label for="name">{{ field_label(form.name) }}</label>
            <input type="text" id="name" name="{{ field_name(form.name) }}" required value="{{ field_value(form.name) }}">
            {% if form.name.vars.errors|length > 0 %}
                <div class="contact_error">
                    {{ form_errors(form.name) }}
                </div>
            {% endif %}
            <label for="email">{{ field_label(form.email) }}</label>
            <input type="email" id="email" name="{{ field_name(form.email) }}" required value="{{ field_value(form.email) }}">
            {% if form.email.vars.errors|length > 0 %}
                <div class="contact_error">
                    {{ form_errors(form.email) }}
                </div>
            {% endif %}
            <label for="subject">{{ field_label(form.subject) }}</label>
            <input type="text" id="subject" name="{{ field_name(form.subject) }}" required value="{{ field_value(form.subject) }}">
            {% if form.subject.vars.errors|length > 0 %}
                <div class="contact_error">
                    {{ form_errors(form.subject) }}
                </div>
            {% endif %}
            <label for="message">{{ field_label(form.message) }}</label>
            <textarea id="message" name="{{ field_name(form.message) }}" rows="5" required>{{ field_value(form.message) }}</textarea>
            {% if form.message.vars.errors|length > 0 %}
                <div class="contact_error">
                    {{ form_errors(form.message) }}
                </div>
            {% endif %}
            <div class="agree-terms checkbox-container">
                {{ form_widget(form.agreeTerms, {'attr': {'class': 'checkbox-input'}}) }}
                <span class="checkbox-label">Ich habe die <a href="{{ path('app_datenschutz') }}" target="_blank">Datenschutzbestimmungen</a> zur Kenntnis genommen. </span>
            </div>

            {# AltCha Widget hinzufügen #}
            <altcha-widget
                    challengeurl="https://eu.altcha.org/api/v1/challenge?apiKey={{ altchaApiKey }}"
                    spamfilter
            ></altcha-widget>

            {{ form_widget(form.send) }}
            {{ form_rest(form) }}
            {{ form_end(form) }}
            <div class="contact-required-text">Alle Felder sind Pflichtfelder!</div>
        </section>

    </main>
{% endblock %}

{% block javascripts %}
    {{ parent() }}
    <script async defer type="module" src="https://eu.altcha.org/js/latest/altcha.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const form = document.querySelector('form[name="kontakt"]');
            const altchaWidget = document.querySelector('altcha-widget');

            form.addEventListener('submit', function(event) {
                event.preventDefault();

                altchaWidget.verify().then(function(token) {
                    document.getElementById('kontakt_altchaToken').value = token;
                    form.submit();
                }).catch(function(error) {
                    console.error('AltCha verification failed:', error);
                    // TODO ErrorMessage
                });
            });
        });
    </script>
{% endblock %}
2
  • Was the key created for localhost?
    – Olivier
    Commented Oct 13 at 14:13
  • Yes! I used localhost for creation.
    – Mark
    Commented Oct 13 at 14:18

1 Answer 1

0

The API key must be registered with the full host, including the port if using a non-standard port. If you're using localhost:8080, enter this whole host during the API key registration.

2
  • I am using a standard port. I even switched to my stage environment and tried it. Didn't work .I implemented AltCha as Self-Hosted and this works like a charm. No clue, why API AltCha is not working for me.
    – Mark
    Commented Oct 20 at 10:25
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Oct 21 at 3:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.