Migrating from FOSUserBundle to Symfony Security Bundle (with ResetPasswordBundle & VerifyEmailBundle)

Migrating from FOSUserBundle to Symfony Security Bundle (with ResetPasswordBundle & VerifyEmailBundle)

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

FeatureFOSUserBundleSymfony Native Security System
MaintenanceDeprecatedActively maintained by Symfony
FlexibilityHard to overrideFull control over logic & templates
Custom Authenticator SupportNot supportedFully supported
Symfony Flex / MakerBundle SupportLimitedFull integration
Email VerificationDIYWith VerifyEmailBundle
Password ResetBasic / DIYWith ResetPasswordBundle
Developer ExperienceMagic-heavyExplicit 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.

 

Ankit Radhanpura

Author

Web developer and tech lead with 9+ years of experience building scalable applications. Skilled in frontend, backend, and system design. Passionate about clean code, performance, and mentoring developers.

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,...

newslatter_bg_image
newslatter_image

Keep up-to-date with our newsletter.

Sign up for our newsletter to receive weekly updates and news directly to your inbox.