Guide complet pour débutants
Carte d'identité
Annuaire
Garde de sécurité
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
// Propriétés de l'utilisateur
}
→ Contient toutes les informations de l'utilisateur (email, mot de passe, rôles...)
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
→ Dit à Symfony où et comment trouver les utilisateurs
firewalls:
main:
provider: app_user_provider
form_login:
login_path: app_login
→ Définit les règles de sécurité et les méthodes d'authentification
php bin/console make:user
Question 1: The name of the security user class
→ Réponse: User
Question 2: Do you want to store user data in the database?
→ Réponse: yes
Question 3: Enter a property name for the unique "display" name
→ Réponse: email
Question 4: Will this app need to hash/check user passwords?
→ Réponse: yes
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private ?string $password = null;
}
public function getUserIdentifier(): string
{
return (string) $this->email;
}
💡 Rôle : Retourne l'identifiant unique de l'utilisateur (ici l'email)
Symfony demande : "Comment identifier cet utilisateur ?" → On répond : "Par son email"
public function getRoles(): array
{
$roles = $this->roles;
// Garantir que tout utilisateur a au moins ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
💡 Rôle : Retourne les permissions de l'utilisateur
Tout utilisateur a automatiquement ROLE_USER
public function eraseCredentials(): void
{
// Effacer les données sensibles temporaires
// $this->plainPassword = null;
}
💡 Rôle : Nettoie les données sensibles après l'authentification
Important pour ne pas garder le mot de passe en clair en mémoire
security:
password_hashers:
App\Entity\User:
algorithm: auto # Symfony choisit le meilleur algorithme
⚠️ JAMAIS stocker les mots de passe en clair !
Le hashage transforme "motdepasse123" en "$2y$13$xGxC..." (irréversible)
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email # Chercher par email
firewalls:
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
default_target_path: app_dashboard
enable_csrf: true
logout:
path: app_logout
target: app_home
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 semaine
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/register, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
✓ Tout le monde
🔐 Connecté
🛡️ Admin seul
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
💡 Un ROLE_ADMIN a automatiquement ROLE_USER
Un ROLE_SUPER_ADMIN a tous les rôles inférieurs
php bin/console make:auth
→ Choisir : 1 (Login form authenticator)
#[Route('/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// Si déjà connecté, rediriger
if ($this->getUser()) {
return $this->redirectToRoute('app_dashboard');
}
// Récupérer l'erreur de connexion s'il y en a une
$error = $authenticationUtils->getLastAuthenticationError();
// Dernier nom d'utilisateur saisi
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
#[Route('/logout', name: 'app_logout')]
public function logout(): void
{
// Cette méthode peut rester vide
// Symfony intercepte la route automatiquement
}
💡 Le firewall gère automatiquement la déconnexion !
Pas besoin de code, Symfony fait tout le travail
{% extends 'base.html.twig' %}
{% block body %}
{# Affichage des erreurs #}
{% if error %}
{{ error.messageKey|trans(error.messageData, 'security') }}
{% endif %}
{# Message de bienvenue si déjà connecté #}
{% if app.user %}
Vous êtes connecté en tant que {{ app.user.userIdentifier }}
{% endif %}
{% endblock %}
php bin/console make:registration-form
#[Route('/register', name: 'app_register')]
public function register(
Request $request,
UserPasswordHasherInterface $userPasswordHasher,
EntityManagerInterface $entityManager
): Response {
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Encoder le mot de passe
$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
// Sauvegarder en base
$entityManager->persist($user);
$entityManager->flush();
// Message de succès
$this->addFlash('success', 'Compte créé avec succès !');
return $this->redirectToRoute('app_login');
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
Données saisies
Vérification
Sécurisation
Sauvegarde
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
'label' => 'Adresse email',
'attr' => ['class' => 'form-control']
])
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'mapped' => false,
'first_options' => [
'label' => 'Mot de passe',
'attr' => ['class' => 'form-control']
],
'second_options' => [
'label' => 'Confirmer le mot de passe',
'attr' => ['class' => 'form-control']
],
'invalid_message' => 'Les mots de passe ne correspondent pas.',
'constraints' => [
new NotBlank(['message' => 'Entrez un mot de passe']),
new Length([
'min' => 6,
'minMessage' => 'Le mot de passe doit contenir au moins {{ limit }} caractères',
'max' => 4096,
]),
],
])
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'label' => 'J\'accepte les conditions d\'utilisation',
'constraints' => [
new IsTrue(['message' => 'Vous devez accepter nos conditions.']),
],
]);
}
}
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/admin', name: 'app_admin')]
#[IsGranted('ROLE_ADMIN')]
public function admin(): Response
{
return $this->render('admin/index.html.twig');
}
public function dashboard(): Response
{
// Vérifier et lancer une exception si pas autorisé
$this->denyAccessUnlessGranted('ROLE_USER');
// Récupérer l'utilisateur connecté
$user = $this->getUser();
return $this->render('dashboard/index.html.twig', [
'user' => $user
]);
}
public function profile(): Response
{
if (!$this->isGranted('ROLE_USER')) {
throw $this->createAccessDeniedException('Accès refusé');
}
// Suite du code...
}
✅ Préférez l'attribut #[IsGranted()] : plus propre et lisible !
{# Vérifier si l'utilisateur est connecté #}
{% if app.user %}
{% else %}
{% endif %}
{# Afficher le menu admin uniquement aux admins #}
{% if is_granted('ROLE_ADMIN') %}
{% endif %}
{# Contenu différent selon le rôle #}
{% if is_granted('ROLE_PREMIUM') %}
{% else %}
Passez en Premium pour accéder à plus de fonctionnalités !
{% endif %}
Cause : Votre classe User n'implémente pas les bonnes interfaces
// ❌ Incorrect
class User {
// ...
}
// ✅ Correct
class User implements UserInterface, PasswordAuthenticatedUserInterface {
// ...
}
Symptôme : "Trop de redirections"
Solution : Autoriser l'accès public aux pages de connexion
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS } # IMPORTANT !
- { path: ^/register, roles: PUBLIC_ACCESS }
Danger : Mot de passe stocké en clair !
// ❌ JAMAIS faire ça !
$user->setPassword($plainPassword);
// ✅ Toujours hasher
$hashedPassword = $userPasswordHasher->hashPassword($user, $plainPassword);
$user->setPassword($hashedPassword);
# Dans .env ou .env.local
DATABASE_URL="mysql://root:password@127.0.0.1:3306/symfony_auth"
# Créer la base de données
php bin/console doctrine:database:create
# Générer le fichier de migration
php bin/console make:migration
# Exécuter la migration
php bin/console doctrine:migrations:migrate
# Voir le statut des migrations
php bin/console doctrine:migrations:status
# Voir le SQL qui sera exécuté
php bin/console doctrine:migrations:migrate --dry-run
# Annuler la dernière migration (attention !)
php bin/console doctrine:migrations:migrate prev
/register
/login
/dashboard
/logout
# Démarrer le serveur de développement
php bin/console server:start
# Ouvrir dans le navigateur
# http://localhost:8000/register
📖 Le guide complet et officiel. Commencez par les sections "Authentication" et "Authorization" pour comprendre les bases.
🎥 Tutoriels vidéo en anglais très bien expliqués. Parfait pour apprendre visuellement avec des exemples pratiques.
📝 Tutorial pas à pas pour créer un formulaire de connexion. Idéal pour comprendre chaque étape en détail.
🔍 Explique comment Symfony trouve vos utilisateurs. Utile si vous voulez charger les users depuis une API ou autre source.
🚦 Pour approfondir la gestion des permissions et comprendre qui peut accéder à quoi dans votre application.
ℹ️ Qu'est-ce qu'un Bundle ? C'est comme un plugin qui ajoute des fonctionnalités à Symfony. À installer une fois que vous maîtrisez les bases !
🔐 Pour créer une API REST sécurisée. Utilise des tokens JWT au lieu des sessions. Niveau : Avancé
📱 Ajoute la double authentification (comme Google Authenticator). Renforce la sécurité. Niveau : Intermédiaire
🌐 Permet la connexion via Google, Facebook, GitHub... "Se connecter avec Google". Niveau : Intermédiaire
📧 Système complet "Mot de passe oublié ?" avec envoi d'email sécurisé. Niveau : Débutant-Intermédiaire
💡 Conseil : Commencez par maîtriser l'authentification de base avant d'ajouter ces bundles. Une fois à l'aise, le ResetPasswordBundle est un excellent premier ajout !
Vous maîtrisez maintenant les bases de l'authentification Symfony !
Jamais de mots de passe en clair
Toujours protéger vos formulaires
Vérifier les permissions partout
Bon développement avec Symfony ! 🚀