-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[WIP] 4373 - document security events #5303
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
Changes from 4 commits
286f00c
9b2e9d8
5bdfd71
f15bea4
a14bd07
69c0089
0dbabfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -267,5 +267,55 @@ in) is correct, you can use:: | |
$user->getSalt() | ||
); | ||
|
||
Authentication Events | ||
--------------------- | ||
|
||
The security component provides 4 related authentication events: | ||
|
||
=============================== ================================================ ========================================================================= | ||
Name Event Constant Argument Passed to the Listener | ||
=============================== ================================================ ========================================================================= | ||
security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\Component\Security\Core\Event\AuthenticationEvent` | ||
security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\Component\Security\Core\Event\AuthenticationFailureEvent` | ||
security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\Component\Security\Http\Event\InteractiveLoginEvent` | ||
security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\Component\Security\Http\Event\SwitchUserEvent` | ||
=============================== ================================================ ========================================================================= | ||
|
||
Authentication Success and Failure Events | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
When a provider authenticates the user, a ``security.authentication.success`` | ||
event is dispatched. Likewise, when no providers authenticate the user, | ||
a ``security.authentication.failure`` event is dispatched. You | ||
could listen on the ``security.authentication.failure`` event, for example, | ||
in order to log failed login attempts. | ||
|
||
.. tip:: | ||
|
||
One of the authentication events is always triggered when a request points | ||
to a secured area. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this true? I see these events thrown only in This stuff is tricky - all the more reason why I'd like to merge this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I can tell, I don't think this is true in all cases. I just tried with Symfony 3 and an authenticator implementing |
||
|
||
Security Events | ||
~~~~~~~~~~~~~~~ | ||
|
||
The ``security.interactive_login`` event is triggered after a user has actively | ||
logged into your website. It is important to distinguish this action from | ||
non-interactive authentication methods, such as: | ||
|
||
* authentication based on a "remember me" cookie. | ||
* authentication based on your session. | ||
* authentication using a HTTP basic or HTTP digest header. | ||
|
||
You could listen on the ``security.interactive_login`` event, for example, in | ||
order to give your user a welcome flash message every time they log in. | ||
|
||
The ``security.switch_user`` event is triggered every time you activate | ||
the ``switch_user`` firewall listener. | ||
|
||
.. seealso:: | ||
|
||
For more information on switching users, see | ||
:doc:`/cookbook/security/impersonating_user`. | ||
|
||
.. _`CVE-2013-5750`: http://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form | ||
.. _`BasePasswordEncoder::checkPasswordLength`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/BasePasswordEncoder.php |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,4 @@ Security | |
csrf_in_login_form | ||
access_control | ||
multiple_user_providers | ||
throttle_failed_login |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
.. index:: | ||
single: Security; Throttle Failed Login | ||
|
||
How to Throttle Failed Login Attempts | ||
===================================== | ||
|
||
Sometimes, it's useful to throttle login attempts when you encounter multiple | ||
recent failed logins. This can be useful in combatting brute-force | ||
password-guessing login attacks. This can be done by implementing a | ||
``security.authentication.failure`` listener to log failed login attempts, and | ||
a security provider decorator to deny login attempts from the same IP address. | ||
|
||
Authentication Failure Listener | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we use |
||
|
||
The purpose of this authentication failure listener is to persist the ip address | ||
and any other data of the client. The example here uses a doctrine table to | ||
persist the data, but you could use another method (Redis, for example). | ||
|
||
.. code-block:: php | ||
|
||
namespace AppBundle\Security\Authentication\Listener; | ||
|
||
use Doctrine\ORM\EntityRepositoryInterface; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent; | ||
|
||
class LogFailedLoginAttempt | ||
{ | ||
/** | ||
* The doctrine repository to which the failed login attempt will be | ||
* persisted. | ||
* | ||
* @var EntityRepositoryInterface | ||
*/ | ||
private $repository; | ||
|
||
/** | ||
* The current request contains the ip address and other $_SERVER globals | ||
* to persist. | ||
* | ||
* @var RequestStack | ||
*/ | ||
private $requestStack; | ||
|
||
public function __construct(RequestStack $requestStack, EntityRepositoryInterface $repository) | ||
{ | ||
$this->repository = $repository; | ||
$this->requestStack = $requestStack; | ||
} | ||
|
||
public function onAuthenticationFailureEvent(AuthenticationFailureEvent $event) | ||
{ | ||
$this->repository->logAuthenicationFailure( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo on logAuthenticationFailure :) |
||
$event->getToken(), | ||
$this->requestStack->getCurrentRequest() | ||
); | ||
} | ||
} | ||
|
||
Now you are logging all the relevant details about every failed login attempt. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'll be easier to follow if you inject the EntityManager, then fetch some fake "AppBundle:LoginAttempt" entity (or something like that). Then we should also show this registered as a service. Finally, we can add a note that we'll leave the LoginAttempt entity logic code to the user. |
||
|
||
Security Provider Decorator | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here! |
||
|
||
This security provider will decorate another security provider and will reject | ||
any authentication attempts it needs to throttle. | ||
|
||
.. code-block:: php | ||
|
||
namespace AppBundle\Security\Authentication\Provider; | ||
|
||
use Symfony\Component\Security\Core\Exception\AuthenticationException; | ||
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; | ||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||
|
||
class LoginThrottleProvider implements AuthenticationProviderInterface | ||
{ | ||
/** | ||
* @var AuthenticationProviderInterface | ||
*/ | ||
private $decoratedProvider; | ||
|
||
/** | ||
* The throttle service decides whether to throttle a certain | ||
* ip address or not. | ||
*/ | ||
private $throttleService; | ||
|
||
public function __construct(AuthenticationProviderInterface $provider, $throttleService) | ||
{ | ||
$this->decoratedProvider = $provider; | ||
$this->throttleService = $throttleService; | ||
} | ||
|
||
public function authenticate(TokenInterface $token) | ||
{ | ||
if ($this->throttleService->isThrottled($token)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'll be more clear if we don't inject some un-shown "throttleService", but instead put in a little "fake" logic to show what they should do. And on that note, what would be a good example? It seems like the "Token" is kind of useless. If I'm throttling, wouldn't it be based on something like the IP address? Perhaps we need to inject the request_stack here to make this more realistic. |
||
throw new AuthenticationException( | ||
'Too many failed authentication attempts.' | ||
); | ||
} | ||
|
||
return $this->decoratedProvider->authenticate($token); | ||
} | ||
|
||
public function supports(TokenInterface $token) | ||
{ | ||
return $this->decoratedProvider->supports($token); | ||
} | ||
} | ||
|
||
The implementation of the throttle service is outside the scope of this | ||
documentation and will depend on your application's needs. It is common | ||
for an application to require additional means of authentication when | ||
multiple failed logins are detected, such as the addition of a CAPTCHA | ||
to the login page, or requiring two-factor authentication. |
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.
This sentence kind of makes it sound like this event will happen when no providers authentication versus "1 authenticator fails authentication". How about: