If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeIf you've used Symfony for a while, you probably know that Symfony dispatches events during the request-response process and that you can listen to them. To see these events and their listeners, we can run:
symfony console debug:event
I'm not going to go too deeply, but, this kernel.request
event is dispatched on every request before the controller is called. This means that all of these listeners are executed before our controller. Listeners to this kernel.response
event are called after our controller.
These two events have... nothing to do with the security system. But it turns out that our firewall also dispatches several events during the authentication process. And, we can also listen to those.
To see a list of all of the listeners to these events, we can run debug:event
again, but with a special --dispatcher=
set to security.event_dispatcher.main
:
symfony console debug:event --dispatcher=security.event_dispatcher.main
I know, that looks a little funny... but this allows us to list the event listeners for the event dispatcher that's specific to the main
firewall.
And... awesome! A totally different set of events and listeners. This is so cool. Look back at our custom LoginFormAuthenticator
class. We're not using this anymore, but it can help us understand which events are dispatched through the process.
We know that, in our authenticate()
method, our job is to return the Passport
:
... lines 1 - 26 | |
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator | |
{ | |
... lines 29 - 39 | |
public function authenticate(Request $request): PassportInterface | |
{ | |
$email = $request->request->get('email'); | |
$password = $request->request->get('password'); | |
return new Passport( | |
new UserBadge($email, function($userIdentifier) { | |
// optionally pass a callback to load the User manually | |
$user = $this->userRepository->findOneBy(['email' => $userIdentifier]); | |
if (!$user) { | |
throw new UserNotFoundException(); | |
} | |
return $user; | |
}), | |
new PasswordCredentials($password), | |
[ | |
new CsrfTokenBadge( | |
'authenticate', | |
$request->request->get('_csrf_token') | |
), | |
(new RememberMeBadge())->enable(), | |
] | |
); | |
} | |
... lines 66 - 81 | |
} |
Then, after the authenticate()
method is called - on any authenticator - Symfony dispatches CheckPassportEvent
. There are a bunch of cool listeners to this.
For example, UserProviderListener
is basically responsible for loading the User
object, CheckCredentialsListener
is responsible for checking the password, CsrfProtectionListener
validates the CSRF token and LoginThrottlingListener
checks... the login throttling.
If we fail authentication, there's a different event for that: LoginFailureEvent
. Right now, our app has just one listener - RememberMeListener
- which clears the "remember me" cookie if the user had one.
When login is successful, Symfony dispatches LoginSuccessEvent
. This already has 5 listeners in our app, including the listener that sets the "remember me" cookie.
There's also an event that's dispatched when you log out... so you can run code or even control what happens - like where the user is redirected to.
This next one - TokenDeauthenticatedEvent
- is a bit more subtle. It's dispatched if the user "loses" authentication... but didn't log out. It's basically dispatched if certain data changes on the user. For example, imagine you're logged in on two computers and then you change your password on the first. When you refresh the site on the second computer, you will be "deauthenticated" because your password changed on another machine. In that case, this event is dispatched.
Oh, and this security.authentication.success
isn't too important, it's very similar to LoginSuccessEvent
.
Knowing about these events is critical because I want to make it so that if the user tries to log in using an email that has not been verified, we prevent that and show them a nice message.
Let's do that next by bootstrapping our very own shiny event listener that has the ability to cause authentication to fail.
Hey Trafficmanagertech
Inside your `security.yaml` file do you have a "main" key defined as your firewall?
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.3", // v3.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^2.1", // 2.6.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
"doctrine/orm": "^2.7", // 2.10.1
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.1
"pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
"pagerfanta/twig": "^3.3", // v3.3.0
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"scheb/2fa-bundle": "^5.12", // v5.12.1
"scheb/2fa-qr-code": "^5.12", // v5.12.1
"scheb/2fa-totp": "^5.12", // v5.12.1
"sensio/framework-extra-bundle": "^6.0", // v6.2.0
"stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
"symfony/asset": "5.3.*", // v5.3.4
"symfony/console": "5.3.*", // v5.3.7
"symfony/dotenv": "5.3.*", // v5.3.8
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/form": "5.3.*", // v5.3.8
"symfony/framework-bundle": "5.3.*", // v5.3.8
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/property-access": "5.3.*", // v5.3.8
"symfony/property-info": "5.3.*", // v5.3.8
"symfony/rate-limiter": "5.3.*", // v5.3.4
"symfony/runtime": "5.3.*", // v5.3.4
"symfony/security-bundle": "5.3.*", // v5.3.8
"symfony/serializer": "5.3.*", // v5.3.8
"symfony/stopwatch": "5.3.*", // v5.3.4
"symfony/twig-bundle": "5.3.*", // v5.3.4
"symfony/ux-chartjs": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.8
"symfony/webpack-encore-bundle": "^1.7", // v1.12.0
"symfony/yaml": "5.3.*", // v5.3.6
"symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
"twig/extra-bundle": "^2.12|^3.0", // v3.3.3
"twig/string-extra": "^3.3", // v3.3.3
"twig/twig": "^2.12|^3.0" // v3.3.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.3.*", // v5.3.4
"symfony/maker-bundle": "^1.15", // v1.34.0
"symfony/var-dumper": "5.3.*", // v5.3.8
"symfony/web-profiler-bundle": "5.3.*", // v5.3.8
"zenstruck/foundry": "^1.1" // v1.13.3
}
}
In SF 5.3, `symfony console debug:event --dispatcher=security.event_dispatcher.main` doesn't work anymore (got "[ERROR] Event dispatcher "security.event_dispatcher.main" is not available.")
In my case this worked: `symfony console debug:event-dispatcher security`