
Summary:
FOSUserBundle was once the go-to solution for user authentication in Symfony projects. But with the evolution of Symfony’s core security features, it’s now deprecated in Symfony 6+. Modern Symfony offers a cleaner, more flexible, and better-maintained native security system.
In this comprehensive guide, you’ll migrate away from FOSUserBundle to Symfony’s native security, ResetPasswordBundle, and VerifyEmailBundle.
June 6, 2025
If you’re still using FOSUserBundle in your Symfony project, it’s time to upgrade. FOSUserBundle was once the standard solution for user authentication and management in Symfony, but it is now deprecated and unsupported in Symfony 6 and beyond.
The good news? Symfony’s modern security system, combined with official bundles like ResetPasswordBundle and VerifyEmailBundle, provides a more powerful, flexible, and future-proof way to manage user authentication.
1. Why You Should Stop Using FOSUserBundle
FOSUserBundle is Deprecated
- No longer maintained.
- Not compatible with Symfony 6+.
- Depends on outdated practices.
- Lacks support for new authentication and password reset standards.
Symfony Native Security is Better Because
Feature | FOSUserBundle | Symfony Native Security System |
Maintenance | Deprecated | Actively maintained by Symfony |
Flexibility | Hard to override | Full control over logic & templates |
Custom Authenticator Support | Not supported | Fully supported |
Symfony Flex / MakerBundle Support | Limited | Full integration |
Email Verification | DIY | With VerifyEmailBundle |
Password Reset | Basic / DIY | With ResetPasswordBundle |
Developer Experience | Magic-heavy | Explicit and clean |
2. Replace FOSUserBundle with Symfony Security Bundle
Install Symfony’s Security Bundle
If it’s not already installed:
composer require symfony/security-bundle
This brings in core components like:
- Authenticator system
- UserInterface
- Password hasher
- Firewall/access control support
Remove FOSUserBundle
composer remove friendsofsymfony/user-bundle
3. Deep Dive: security.yaml Migration
FOSUserBundle came with its opinionated configuration. In modern Symfony (5.3+), everything is handled through security.yaml with full control and clarity.
Let’s walk through the new config step-by-step.
Full Example: config/packages/security.yaml
security: enable_authenticator_manager: true password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' providers: app_user_provider: entity: class: App\Entity\User property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true provider: app_user_provider custom_authenticator: App\Security\LoginFormAuthenticator logout: path: app_logout target: app_login remember_me: secret: '%kernel.secret%' lifetime: 604800 # 1 week in seconds path: / always_remember_me: false access_control: - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/profile, roles: ROLE_USER } - { path: ^/login, roles: PUBLIC_ACCESS } - { path: ^/register, roles: PUBLIC_ACCESS } - { path: ^/verify/email, roles: PUBLIC_ACCESS } - { path: ^/reset-password, roles: PUBLIC_ACCESS } role_hierarchy: ROLE_ADMIN: ROLE_USER
Explanation
- enable_authenticator_manager: true: Enables the new security system based on custom authenticators.
- password_hashers: Replaces the old encoders section. Automatically picks the best hashing algorithm.
- providers: Configures the user entity source (usually from Doctrine).
- firewalls.main: The main firewall uses a custom authenticator class.
- logout: Defines how and where users are logged out.
- remember_me: Optional; for persistent login cookies.
- access_control: Protects routes based on user roles.
- role_hierarchy: Let’s admin roles inherit user permissions.
Use php bin/console make: Auth to generate the LoginFormAuthenticator and starter templates.
4. Update the User Entity
FOSUserBundle provided a base class for the user. Now, you need to implement it yourself with the interfaces required by Symfony.
The easiest way to generate a user class is to use the make: user command from the MakerBundle:
php bin/console make:user The name of the security user class (e.g., User) [User]: > User Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]: > yes Enter a property name that will be the unique "display" name for the user (e.g. email, username, uuid) [email]: > email Will this app need to hash/check user passwords? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server). Does this app need to hash/check user passwords? (yes/no) [yes]: > yes created: src/Entity/User.php created: src/Repository/UserRepository.php updated: src/Entity/User.php updated: config/packages/security.yaml
Here’s a clean, modern version of your user entity:
namespace App\Entity; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(type: 'string', length: 180, unique: true)] #[Assert\NotBlank] #[Assert\Email] private string $email; #[ORM\Column(type: 'json')] private array $roles = []; #[ORM\Column(type: 'string')] private string $password; #[ORM\Column(type: 'boolean')] private bool $isVerified = false; public function getId(): ?int { return $this->id; } public function getEmail(): string { return $this->email; } public function setEmail(string $email): self { $this->email = $email; return $this; } public function getUserIdentifier(): string { return $this->email; } public function getUsername(): string { return $this->email; } public function getRoles(): array { $roles = $this->roles; if (!in_array('ROLE_USER', $roles)) { $roles[] = 'ROLE_USER'; } return array_unique($roles); } public function setRoles(array $roles): self { $this->roles = $roles; return $this; } public function getPassword(): string { return $this->password; } public function setPassword(string $password): self { $this->password = $password; return $this; } public function eraseCredentials(): void { // Clear temporary sensitive data } public function isVerified(): bool { return $this->isVerified; } public function setIsVerified(bool $isVerified): self { $this->isVerified = $isVerified; return $this; } }
Key Points:
- Implements UserInterface and PasswordAuthenticatedUserInterface
- Uses email as the username
- Has a boolean is Verified field for email confirmation
- The password is hashed manually using Symfony’s password hasher
- No base class magic – fully customizable
5. Template Overriding
FOSUserBundle came with its own Twig templates. Now you control everything.
Login Template Example
{# templates/security/login.html.twig #} {% extends 'base.html.twig' %} {% block body %} <form method="post" action="{{ path('app_login') }}"> {% if error %} <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div> {% endif %} <label>Email:</label> <input type="email" name="email" value="{{ last_username }}" required autofocus> <label>Password:</label> <input type="password" name="password" required> <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}"> <button type="submit">Login</button> </form> {% endblock %}
You’ll also need to create templates for:
- Registration
- Reset password
- Email verification
6. Redirect After Login/Logout
In your LoginFormAuthenticator.php:
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { return new RedirectResponse($this->urlGenerator->generate('dashboard')); } In the security.yaml for logout: logout: path: app_logout target: app_login
7. Reset Password with ResetPasswordBundle
Install the bundle
composer require symfonycasts/reset-password-bundle
Configuration
# config/packages/reset_password.yaml symfonycasts_reset_password: request_password_repository: App\Repository\ResetPasswordRequestRepository lifetime: 3600 throttle_limit: 3600 enable_garbage_collection: true
Run:
php bin/console make:reset-password
This scaffolds everything needed for:
- Token generation
- Reset password form
- Email template
- Routing
8. Email Verification with VerifyEmailBundle
Install the bundle
composer require symfonycasts/verify-email-bundle
Setup
Configuration
You can change the default configuration parameters for the bundle by creating a config/packages/verify_email.yaml config file:
symfonycasts_verify_email: lifetime: 3600
In your user entity:
#[ORM\Column(type: 'boolean')] private bool $isVerified = false; public function isVerified(): bool { return $this->isVerified; } public function setIsVerified(bool $isVerified): self { $this->isVerified = $isVerified; return $this; }
During registration, trigger email verification:
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, (new TemplatedEmail()) ->from(new Address('noreply@example.com', 'Acme App')) ->to($user->getEmail()) ->subject('Please Confirm your Email') ->htmlTemplate('registration/confirmation_email.html.twig') );
Handle confirmation via VerifyEmailController:
/** * @Route("/verify/email", name="app_verify_email") */ public function verifyUserEmail(Request $request): Response { $user = $this->getUser(); try { $this->emailVerifier->handleEmailConfirmation($request, $user); } catch (VerifyEmailExceptionInterface $exception) { $this->addFlash('verify_email_error', $exception->getReason()); return $this->redirectToRoute('app_register'); } $this->addFlash('success', 'Your email address has been verified.'); return $this->redirectToRoute('app_home'); }
Final Cleanup
- Remove any references to FOSUserBundle from config files.
- Remove unused templates.
- Clean up old routes or override templates.
- Test each flow: registration, login, reset, and verify.
Useful Links
Related Blog
Symfony
Unleash Symfony API Testing with PHPUnit
Building robust APIs in Symfony 7.2 requires thorough testing to ensure reliability and security. In this guide, we’ll use PHPUnit to test the API endpoints from JWT authentication setup -/api/register, /api/login_check, /api/users/{id}, /api/users/me, and /api/profile. You’ll learn to set up...
Symfony
Securing Symfony APIs with JWT Authentication
This guide shows you how to secure a Symfony 7.2 REST API with JWT authentication using lexik/jwt-authentication-bundle. You’ll set up user registration with validation, a rate-limited login endpoint, and protect routes like /api/users/{id} and /api/profile.
Symfony
Build Symfony REST API with OpenAPI and Swagger UI Integration
This guide shows you how to build your first REST API endpoint (/api/users/{id}) in Symfony 7.2, document it with OpenAPI YAML, and integrate Swagger UI for interactive testing. Using nelmio/api-doc-bundle and a hybrid approach, we’ll keep code clean, generate openapi.yaml,...

Keep up-to-date with our newsletter.
Sign up for our newsletter to receive weekly updates and news directly to your inbox.