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