Open-source project
SymfonyCasts/reset-password-bundle avatar
SymfonyCasts/reset-password-bundle

reset-password-bundle turns a Symfony forgot-password form into one console command

Need a killer reset password feature for your Symfony? Us too!

528 stars76 forksPHPMIT

At a glance

What is it?
The SymfonyCasts reset-password-bundle provides a secure out of the box password reset flow for Symfony, with a Maker command that generates the configuration, templates, controllers and entity in one go. It suits a Symfony application that needs self-service password recovery, and it wants your own repository class and mailer already in place.
Who is it for?
reset-password-bundle is worth adding to a Symfony application that has authentication and a mailer but no self-service password recovery, because the Maker command produces the configuration, the templates, the controllers and the entity in one step, and because the throttle and lifetime settings are explicit rather than buried in your own controller code.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One console command scaffolds the whole flow

The installation is a single Composer requirement:

bash
composer require symfonycasts/reset-password-bundle

After that the recommended path is Symfony's MakerBundle, and the documentation is clear that this is the easiest and preferred way to get started. The Maker will take care of everything from creating configuration to generating your templates, controllers and entities, and the command is `bin/console make:reset-password`, run once, answering a couple of questions. The documentation still recommends using the Maker command even if you prefer the manual route, because the point of the manual guide is to show how the bundle is meant to be used. A manual setup guide is linked for anyone who would rather do the leg work themselves. The finished flow is a route at `https://your-apps-domain/reset-password` where the user fills out a form, clicks the link sent to their email and changes their password, with the bundle handling the rest.

The bundle assumes you already have authentication and a mailer

There is a precondition in the documentation that is easy to skim and expensive to miss. The flow assumes you have already set up authentication with a registered user account and configured Symfony's mailer in your app. That is a real constraint on adoption, because the reset flow is not self-contained. It does not create a user, it does not authenticate anyone, and it does not send mail on its own; it needs an existing user entity to attach a request to and a working mailer to deliver the link. The consequence is that the bundle is a component of an authentication setup rather than a replacement for one. A project that has a user table but no mailer will find the maker command produces templates and controllers that go nowhere, because the message never leaves the application. The bundle also assumes a persistence layer, since the reset requests are stored as entities and the configuration requires you to name a repository class for them.

Four parameters decide the behaviour, and one of them is required

The configuration lives in `config/packages/reset_password.yaml`, the file created by the Maker, and the defaults are readable in one block:

yaml
symfonycasts_reset_password:
    request_password_repository: App\Repository\ResetPasswordRequestRepository
    lifetime: 3600
    throttle_limit: 3600
    enable_garbage_collection: true

Only `request_password_repository` is required, and it is the complete namespace of the repository for the `ResetPasswordRequest` entity, which will be `App\Repository\ResetPasswordRequestRepository` if you used the maker. The `lifetime` parameter is the length of time in seconds that a reset password request is valid for after it has been created, defaulting to 3600. The `enable_garbage_collection` flag, defaulting to true, turns the Reset Password Cleaner on, which handles expired reset password requests that may have been left in persistence. A PHP configuration variant is also documented for projects that keep their configuration in PHP rather than YAML, using the same four keys under the `symfonycasts_reset_password` extension.

throttle_limit and lifetime are two dials on the same security decision

The relationship between the two timing parameters is the part of the documentation that is actually about security rather than configuration. The `throttle_limit` is the length of time in seconds that must pass before a user can request a subsequent reset request, and it also defaults to 3600. Setting it equal to or higher than `lifetime` will prevent a user from requesting a password reset before a previous reset attempt has either been successfully completed or has expired. Setting it lower than `lifetime` will allow a user to make several reset password requests, even if previous requests have not been completed or have not expired, and the documentation gives the reason: that would allow for cases such as a user never receiving the reset password request email. So the two knobs are not redundant. A deployment that wants to close the window between a request and its expiry sets them equal, and a deployment that wants a forgiving user experience, where a lost email does not lock the user out, sets the throttle lower. The defaults happen to be equal, which is the stricter of the two configurations.

removeRequests is the tool for a compromised email address

The advanced usage section covers a case the automatic cleanup does not handle. The `ResetPasswordRequestRepositoryInterface::removeRequests()` method, implemented in `ResetPasswordRequestRepositoryTrait`, can be used to remove all request objects from persistence for a single user. That differs from the garbage collection mechanism, which only removes expired request objects for all users automatically. The documented situation is a user who changed their email address due to suspicious activity and potentially has valid request objects in persistence with their old, compromised email address. The example shows the call in a profile controller, where the original email is captured before the form is handled and the repository is asked to remove the old requests when the address changed:

php
        if ($originalEmail !== $user->getEmail()) {
            // The user changed their email address.
            // Remove any old reset requests for the user.
            $repository->removeRequests($user);
        }

That is the interesting part of the design. The automatic cleaner only ever removes expired rows, so an outstanding request aimed at an address the user no longer controls would otherwise stay valid until it timed out, and the point of this method is to let application code withdraw it immediately.

The production URI has to be set or the emailed link points at localhost

There is one environment-specific configuration step that produces a broken feature if you skip it. The production environment may require the `default_uri` to be defined in the `config/packages/routing.yaml` to prevent the URI in emails from pointing to localhost. The block is short:

yaml
# config/packages/routing.yaml
when@prod:
    framework:
        router:
            # ...
            default_uri: '<your project's root URI>'

The `when@prod` environment block means the setting only applies in production, which is the right place for it, because in development the local URL is what you want to see. A PHP configuration equivalent is documented for projects that configure the framework extension that way. This is a Symfony-level setting rather than a bundle setting, which is worth noting, because it means the fix lives in your application's routing configuration and not in the bundle's own configuration file, and it is the kind of detail that gets missed when a feature is developed on a laptop and deployed later.

The selector column inherits your table collation, which matters on MySQL

The last section of the documentation is about a database portability problem that is easy to miss. The `selector` column mapped in `ResetPasswordRequestTrait` does not set an explicit collation, so it inherits the default collation of your table. On MySQL an inherited default collation can be case-insensitive, and the documentation was in the middle of explaining what that means for selector lookups when the visible text cuts off. The practical consequence is knowable from the column definition alone. The selector is the token in the reset link, and a lookup on a case-insensitive collation will match strings that differ only in case, so a token that should be unique can collide with another one. The fix is to give the column an explicit binary or case-sensitive collation in your own migration, which is a change to your schema rather than to the bundle. This is the sort of detail that separates a bundle that assumes PostgreSQL defaults from one that has been run against MySQL and MariaDB, and it is worth checking before you rely on the flow in production.

Where a reset-password bundle is the wrong shape

Two limits are worth naming. The first is that the bundle resets the password rather than running it through your application's password policy, so if you have rules beyond what the framework's user entity enforces, the reset path is a way around them unless you extend the flow. The second is that it assumes the single most common architecture for the feature, a token in an emailed link backed by a database row, and it is not a framework for account recovery in general. A user who has lost access to their email entirely, or a support-driven reset for a locked account, or a flow that needs an administrator to approve the change, needs something else. The alternative to weigh against is implementing the flow in your own controller with Symfony's security tokens, which gives you full control over the policy and the audit trail at the cost of writing the token storage, the expiry, the throttling and the mail templating yourself, which is exactly the set of things this bundle already implements. The difference in approach is that the bundle is a working implementation of the standard design with sensible defaults and a maker to assemble it, while a hand-written flow is a set of decisions you own. For an application that already has the security configuration and the mailer, the bundle is the smaller commitment. For one that needs recovery to mean something other than an emailed link, it is not the right shape.

Editorial conclusion

reset-password-bundle is worth adding to a Symfony application that has authentication and a mailer but no self-service password recovery, because the Maker command produces the configuration, the templates, the controllers and the entity in one step, and because the throttle and lifetime settings are explicit rather than buried in your own controller code. It is a poor fit if you need password reset without a configured mailer, since the whole flow depends on the email being delivered, and a poor fit if your password policy is unusual, since the bundle resets the password rather than sending the user through your own strength rules unless you extend it. Before adopting it, set `throttle_limit` deliberately, because the documentation explains that setting it equal to or higher than `lifetime` prevents a new request before the previous one completed or expired, and note that the last push to the repository was on 2026-10-20 with release v1.25.0 published on 2026-03-26, which is the version that uses Symfony Clock internally and allows Doctrine v3.

Frequently asked questions

How do I install the SymfonyCasts reset-password-bundle?

Run composer require symfonycasts/reset-password-bundle, then run bin/console make:reset-password and answer a couple of questions. The Maker takes care of creating the configuration and generating the templates, controllers and entities, and a manual setup guide is linked for anyone who prefers to do the work themselves.

What does the throttle_limit parameter in reset-password-bundle do?

It is the length of time in seconds that must pass before a user can request a subsequent reset request, and it defaults to 3600. Setting it equal to or higher than the lifetime prevents a new request before the previous one has completed or expired, while setting it lower allows several requests, which the documentation says covers cases such as a user never receiving the email.

What does reset-password-bundle need before it works?

The documentation states that the flow assumes you have already set up authentication with a registered user account and configured Symfony's mailer in your app. The bundle also requires a repository for the ResetPasswordRequest entity, named in the request_password_repository parameter, and in production the default_uri has to be defined in config/packages/routing.yaml so the emailed link does not point to localhost.

How do I invalidate outstanding reset requests when a user changes their email?

Call the removeRequests() method on ResetPasswordRequestRepositoryInterface, which is implemented in ResetPasswordRequestRepositoryTrait, to remove all request objects for that one user. That differs from the garbage collection mechanism, which only removes expired request objects for all users automatically.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. SymfonyCasts/reset-password-bundle on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/symfonycasts-reset-password-bundle.svg)](https://hysenlabs.com/projects/symfonycasts-reset-password-bundle)

Was this useful?