-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Security] Lazily load the user during the check passport event #37846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security] Lazily load the user during the check passport event #37846
Conversation
I like this very much. |
4099e8f
to
8220186
Compare
Thank you @fabpot 😃 This PR is ready for a review now, I've added some functional tests to make sure things are working. There are 2 future considerations here:
|
I think you 2 additional changes you mentioned are worth investigating. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made a small suggestion, not sure if that makes sense. What do you think about it?
src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php
Outdated
Show resolved
Hide resolved
private $userLoader; | ||
private $user; | ||
|
||
public function __construct(?string $username, ?callable $userLoader = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably document both of these, because:
- We need to communicate that
$username
is not necessarily a username... but just some "identifier". We probably need to communicate its purpose. For example, suppose I have a login form that has anemail
field AND acompany
drop-down. So, when I query the database for the user, the query is something like:
SELECT * FROM user WHERE email = :email AND company_id = :companyId
Obviously, I would probably make the query and load the user by passing a 2nd argument to new UserBadge()
with a callback that makes the custom query.
The question is: what value should I pass as the $username
? Should it just be $email
? Probably not. Should it be ['email' => $email, 'companyId' => $companyId]
? Well, right now, the first argument must be a string... and probably (?) we want to keep it that way. So, should the user pass $email'._'.$companyId
... because the purpose of this argument is to be some "unique string identifier" of the user?
- We should document that the 2nd arg is optional, must return
UserInterface
ornull
and will be passed the$username
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe $username
should be renamed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some PHPdocs to this method (a bit too much to my liking, happy to accept more brief suggestions).
I've also renamed $username
to $userIdentifier
, please let me known if that doesn't make sense :)
src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php
Show resolved
Hide resolved
->args([ | ||
service('security.user_providers'), | ||
]) | ||
->tag('kernel.event_listener', ['event' => CheckPassportEvent::class, 'priority' => 1024, 'method' => 'checkPassport']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this first service used? I may just be missing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the idea is:
- Always have this service, which chains all configured user providers and runs at 1024
- If a firewall has
provider
set, register the same listener for that specific firewall with only the configured firewall at priority 2048
The listener does not configure the user provider if a user loader is already set (e.g. by the authenticator using a closure or by the firewall specific listener).
src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php
Outdated
Show resolved
Hide resolved
return; | ||
} | ||
|
||
$badge->setUserLoader([$this->userProvider, 'loadUserByUsername']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a nice implementation
5154f9b
to
42c1a7e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice one, 1 minor comment. Should we add a CHANGELOG entry?
src/Symfony/Component/Security/Http/Authenticator/Passport/SelfValidatingPassport.php
Outdated
Show resolved
Hide resolved
src/Symfony/Component/Security/Http/Authenticator/Passport/Passport.php
Outdated
Show resolved
Hide resolved
42c1a7e
to
e61adff
Compare
e61adff
to
907ef31
Compare
Thank you @wouterj. |
…ith the new UserBadge (wouterj) This PR was merged into the 5.2 branch. Discussion ---------- [Security] Update password upgrader listener to work with the new UserBadge | Q | A | ------------- | --- | Branch? | 5.2 | Bug fix? | yes | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - While working on a new amazing `make:auth` maker, @jrushlow discovered that we forgot to update the `PasswordUpgradeBadge` with the 5.2 `UserBadge` changes (ref #37846). This PR fixes it, by making the password upgrader optional and falling back to the user provider instead. Without these changes, each authenticator still needs to know the user repository/user provider in order to pass it to `PasswordUpgradeBadge`. I'm sorry for catching this soo late in the release cycle. There is a BC break involved here, but it's (a) very unlikely to impact application code and (b) in an experimental class. Commits ------- e39e844 Default to user provider, if available, in password upgrader
…rInterface implementation to Passport (wouterj) This PR was merged into the 5.3-dev branch. Discussion ---------- [Security] Remove deprecated support for passing a UserInterface implementation to Passport | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - In #37846 (review) , we agreed to have a deprecation path of only one minor release as the `Passport` feature is still experimental. Commits ------- 99cf2a3 [Security] Disallow passing a UserInterface to Passport
Before
After
Doing it this way has a couple advantages (some of which are already mentioned in the issue):
CheckPassportEvent
need to execute before loading the user - to reduce resources (e.g. CSRF protection, if CSRF fails, no DB call should be made to load user - and also login throttling);UserProviderListener
allows to remove yet another centralized action in the authentication process from the authenticator class to the Symfony framework.Automatic User Provider integration
Instead of passing the credentials and a closure to
UserBadge
, you can also just pass a (string) username. The user provider will then be used to load the user. This only works forcustom_authenticators
as of this moment.user_providers
will be used as the user provider;provider
for that firewall, that provider will be used (using a listener with higher priority).