Skip to content

FOSUserBundle on Symfony 7 and 8: what replaced it, piece by piece (2026)

FOSUserBundle still installs on Symfony 7.4, not on Symfony 8, and its own maintainers tell new projects not to use it. Here is what replaces each of its features, and how to move an existing users table without resetting a single password.

Short answer: FOSUserBundle installs on Symfony 7.3 and 7.4, not on Symfony 8, and its maintainers say new projects should not use it. Every feature it gave you now exists in Symfony itself or in a small, maintained bundle. The migration is mostly deleting code, with one trap: the password hashes already in your database.

Everything below was checked on Packagist and GitHub on September 25, 2026. Versions and dates are written down so you can re-check them. Disclosure: I sell ShipAnvil, a Symfony SaaS kit whose user system is described at the end. Read accordingly.

Where FOSUserBundle stands in 2026

  • Latest release: friendsofsymfony/user-bundle v4.1.0, February 13, 2026. It requires symfony/framework-bundle ^7.3 and PHP ^8.2. The 4.1 changelog is maintenance: XML config converted, deprecations fixed, support for Symfony below 7.3 removed.
  • Symfony 8 is not supported. The constraint ^7.3 excludes 8.0, and the pull request "Add support for Symfony 8" (#3090) has been open since December 11, 2025. If you plan to leave the 7.4 LTS one day, FOSUserBundle is on the critical path.
  • The README says it in bold: "New projects should not use this bundle." The package "only receives minimal maintenance to allow existing projects to upgrade. Existing projects are expected to plan a migration away from this bundle."
  • It still has a large installed base, which is why it keeps getting those maintenance releases. That is a reason it will keep installing on 7.4, not a reason to start with it.

So the question is no longer "is FOSUserBundle dead?". It is "what do I replace each piece with?". The README answers most of it itself.

What FOSUserBundle did, and what replaces it

  • Base User model: your own Doctrine entity implementing UserInterface and PasswordAuthenticatedUserInterface (make:user).
  • User provider: Symfony's built-in entity provider.
  • Login form: Symfony's form_login (make:security:form-login).
  • Registration: a plain controller and form (make:registration-form).
  • Email confirmation: symfonycasts/verify-email-bundle (v1.18.0, Symfony 5.4 to 8).
  • Password reset ("resetting"): symfonycasts/reset-password-bundle (v1.25.0, Symfony 5.4 to 8).
  • Change password: a form with Symfony's UserPassword constraint.
  • Profile page: your own controller; it was always app-specific.
  • The enabled flag: a UserChecker on the firewall.

All three make: commands ship with symfony/maker-bundle (v1.68.0, September 12, 2026). Both SymfonyCasts bundles declare Symfony 8 support in their composer.json.

What you gain is not only Symfony 8. The replacements are safer by default: verify-email-bundle confirms addresses with signed URLs instead of a confirmationToken column, and reset-password-bundle stores a selector and a hashed token in its own table, with throttling of repeated requests, instead of a plain token on the user row.

What about a drop-in fork?

nucleos/user-bundle is a fork of FOSUserBundle. Version 4.1.0 (April 9, 2026) requires Symfony ^7.4 || ^8.0, so it does install on Symfony 8. But it is not a drop-in: it deliberately dropped registration and profile management (those moved to a separate nucleos/profile-bundle), and its README still says "Only symfony 4.4 / 5.x support", which its current composer.json contradicts. Useful if you want to keep a bundle-shaped user model; registration means a second bundle or your own controller anyway.

sonata-project/user-bundle (5.19.0, December 2025) makes sense only if you already run Sonata Admin: it pulls symfony/security-acl and is built around that admin.

For a new project, neither beats an entity you own plus the two SymfonyCasts bundles.

Migrating an existing users table

The code side is quick: generate or write the new entity, map it to your existing table, delete the FOS configuration, routes and templates, and wire the two SymfonyCasts bundles. Four details decide whether anyone notices.

1. Keep the old password hashes working. Old FOSUserBundle setups often hashed with a salted legacy algorithm (the 1.x documentation used sha512), and the salt column is still filled. Do not force a reset. Declare the old hasher and let Symfony migrate on the next login:

# config/packages/security.yaml
security:
    password_hashers:
        legacy:
            algorithm: sha512   # whatever your FOS config used
        App\Entity\User:
            algorithm: auto
            migrate_from: [legacy]

The entity implements LegacyPasswordAuthenticatedUserInterface as long as it still has salted hashes (getSalt() returns the column), and the repository implements PasswordUpgraderInterface: on every successful login, Symfony rehashes with the current algorithm and calls upgradePassword(). When no salt is left, drop the column.

2. Replace the canonical columns. FOS kept usernameCanonical and emailCanonical to make lookups case-insensitive. Lowercase the email on write, put a unique constraint on it, lowercase the lookup, and drop the canonical columns after one migration that copies them.

3. Move enabled into a UserChecker. Register it with user_checker: on the firewall and throw an account status exception for disabled users. One subtlety: the checker runs when someone signs in, not when a user is reloaded from an existing session. If disabling an account must end open sessions too, re-check on each request.

4. Recreate lastLogin if you used it. Listen to LoginSuccessEvent and set the date yourself.

Test the four with functional tests before switching production: log in with an old hash, check it was rehashed, log in with a disabled account, reset a password end to end.

If you are starting a new SaaS instead

Then there is nothing to migrate, and the real work is everything around the user: email verification that actually blocks sign-in, rate limits on every endpoint that sends an email, sessions that end when an account is disabled, 2FA, and the organization the user belongs to.

ShipAnvil is not a bundle: it is a complete Symfony 7.4 application you start your SaaS from, and its user system is plain application code built on exactly the replacements above. What it contains:

  • A User entity you own, email as identifier (lowercased, unique), with the repository implementing PasswordUpgraderInterface.
  • Registration with mandatory email verification (verify-email-bundle): a UserChecker refuses sign-in until the address is confirmed, and resending the link never reveals whether an account exists.
  • Password reset with reset-password-bundle, form login with remember-me and login throttling, and passwordless magic links on Symfony's login_link.
  • Rate limits on sign-up, magic-link, verification and reset requests.
  • Open sessions re-checked on every request against the same UserChecker, so an account that stops passing it is logged out on its next request, remember-me cookie included.
  • TOTP two-factor authentication with single-use backup codes (how it is built).
  • Every user gets an organization at sign-up, with owner, admin and member roles and email invitations, and tenant-scoped queries through a Doctrine filter.
  • Functional tests for login, registration, reset, magic links, 2FA, account status and rate limits, shipped with the code.

It does not include social login (OAuth): add knpuniversity/oauth2-client-bundle or hwi/oauth-bundle if you need it.

The live demo lets you sign in and look around. Price and license terms are on the pricing page. If you are comparing whole kits, the Symfony SaaS boilerplate comparison checks every living option the same way.