Login-Formular implementieren
Login-Formular implementieren
~15 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Mit dem Grundgerüst aus Kapitel 26 bauen wir jetzt den tatsächlichen Login-Authenticator – Symfonys eingebauten form_login-Mechanismus.
Den Login-Authenticator generieren
php bin/console make:security:form-loginFragt nach dem Controller-Namen, den Routennamen für Login/Logout und ob nach erfolgreichem Login zu einer festen Seite weitergeleitet werden soll. Erzeugt einen Controller UND passt security.yaml automatisch an.
Der generierte Login-Controller
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
#[Route('/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
$fehler = $authenticationUtils->getLastAuthenticationError();
$letzteEmail = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $letzteEmail,
'error' => $fehler,
]);
}
#[Route('/logout', name: 'app_logout')]
public function logout(): void
{
throw new \LogicException('Wird automatisch vom Security-System abgefangen.');
}
}logout() wird NIEMALS tatsächlich ausgeführt – Symfonys Security-System fängt die Anfrage an /logout bereits VOR dem Controller ab (konfiguriert in security.yaml). Die Exception dient als klares Signal: "landet dieser Code jemals in der Ausführung, ist etwas falsch konfiguriert".
Das Login-Template
{% extends 'base.html.twig' %}
{% block body %}
<h1>Anmelden</h1>
{% if error %}
<div class="alert alert-error">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<form method="post">
<label for="email">E-Mail</label>
<input type="email" id="email" name="email" value="{{ last_username }}" required autofocus>
<label for="password">Passwort</label>
<input type="password" id="password" name="password" required>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<button type="submit">Anmelden</button>
</form>
{% endblock %}WICHTIG: Dieses Formular ist KEIN Symfony Form (Kapitel 15) – form_login erwartet ein KLASSISCHES HTML-Formular mit den Feld-name-Attributen email/password, direkt von der Firewall verarbeitet, NICHT vom Controller. Der CSRF-Token (Kapitel 18) wird deshalb HÄNDISCH per csrf_token('authenticate') eingefügt.
security.yaml: form_login konfigurieren
security:
# ... providers wie in Kapitel 26 ...
firewalls:
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
enable_csrf: true
logout:
path: app_logout
target: app_loginlogin_path zeigt das Formular AN, check_path verarbeitet den POST-Request – meist DIESELBE Route (beide GET UND POST auf /login, GENAU wie das Formular-Muster aus Kapitel 9). enable_csrf: true aktiviert die Prüfung des _csrf_token-Felds von oben.
Login/Logout in der Navigation
<nav>
<a href="{{ path('project_index') }}">Projekte</a>
{% if app.user %}
<span>Angemeldet als {{ app.user.name }}</span>
<a href="{{ path('app_logout') }}">Abmelden</a>
{% else %}
<a href="{{ path('app_login') }}">Anmelden</a>
{% endif %}
</nav>app.user (aus Kapitel 13 bereits bekannt) ist JETZT tatsächlich befüllt – null für nicht eingeloggte Besucher, sonst die User-Entity des eingeloggten Nutzers.
Tipp: Ein Login-Versuch schlägt aktuell IMMER fehl, da noch kein Nutzer mit korrekt gehashtem Passwort existiert – Kapitel 28 baut die Registrierung, die das behebt.