Skip to content

[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

Merged
merged 7 commits into from
Jan 10, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions components/security/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

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:

Likewise, when a provider attempts authentication but fails (i.e. throws an
``AuthenticationException``), a ``security.authentication.failure`` event is dispatched.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? I see these events thrown only in AuthenticationProviderManager. Is this called if no authentication listener is activated on a request - like if I'm already logged in, but am not at /login_check (assuming form login is my only method)? Also, if anonymous is activated, does that cause security.authentication.success to be dispatched on every request?

This stuff is tricky - all the more reason why I'd like to merge this PR.

Choose a reason for hiding this comment

The 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 SimplePreAuthenticatorInterface. security.authentication.success is called only the first time you are authenticated. Thus, if you are already logged in, none of the authentication events will fire.


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
1 change: 1 addition & 0 deletions cookbook/security/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ Security
csrf_in_login_form
access_control
multiple_user_providers
throttle_failed_login
117 changes: 117 additions & 0 deletions cookbook/security/throttle_failed_login.rst
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use ----- for the first non-title headline


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(
Copy link
Member

Choose a reason for hiding this comment

The 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.
Copy link
Member

Choose a reason for hiding this comment

The 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copy link
Member

Choose a reason for hiding this comment

The 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)) {
Copy link
Member

Choose a reason for hiding this comment

The 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.