@@ -536,6 +536,103 @@ and add the following logic::
536536 // ...
537537 }
538538
539+ Avoid Authenticating the Browser on Every Request
540+ -------------------------------------------------
541+
542+ If you create a Guard login system that's used by a browser and you're experiencing
543+ problems with your session or CSRF tokens, the cause could be bad behavior by your
544+ authenticator. When a Guard authenticator is meant to be used by a browser, you
545+ should *not * authenticate the user on *every * request. In other words, you need to
546+ make sure the ``getCredentials() `` method *only * returns a non-null value when
547+ you actually *need * to authenticate the user. Why? Because, when ``getCredentials() ``
548+ returns a non-null value, for security purposes, the user's session is "migrated"
549+ to a new session id.
550+
551+ This is an edge-case, and unless you're having session or CSRF token issues, you
552+ can ignore this. Here is an example of good and bad behavior::
553+
554+ public function getCredentials(Request $request)
555+ {
556+ // GOOD behavior: only authenticate on a specific route
557+ if ($request->attributes->get('_route') !== 'login_route' || !$request->isMethod('POST')) {
558+ return null;
559+ }
560+
561+ // e.g. your login system authenticates by the user's IP address
562+ // BAD behavior: authentication will now execute on every request
563+ // even if the user is already authenticated (due to the session)
564+ return array('ip' => $request->getClientIp());
565+ }
566+
567+ The problem occurs when your browser-based authenticator tries to authenticate
568+ the user on *every * request - like in the IP address-based example above. There
569+ are two possible fixes:
570+
571+ 1) If you do *not * need authentication to be stored in the session, set ``stateless: true ``
572+ under your firewall.
573+
574+ 2) Update your authenticator to avoid authentication if the user is already authenticated:
575+
576+ .. code-block :: diff
577+
578+ // src/Security/MyIpAuthenticator.php
579+ // ...
580+
581+ + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
582+
583+ class MyIpAuthenticator
584+ {
585+ + private $tokenStorage;
586+
587+ + public function __construct(TokenStorageInterface $tokenStorage)
588+ + {
589+ + $this->tokenStorage = $tokenStorage;
590+ + }
591+
592+ public function getCredentials(Request $request)
593+ {
594+ + // if there is already an authenticated user (likely due to the session)
595+ + // then return null and skip authentication: there is no need.
596+ + $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
597+ + if (is_object($user)) {
598+ + return null;
599+ + }
600+
601+ return array('ip' => $request->getClientIp());
602+ }
603+ }
604+
605+ You'll also need to update your service configuration to pass the token storage:
606+
607+ .. configuration-block ::
608+
609+ .. code-block :: yaml
610+
611+ # app/config/services.yml
612+ services :
613+ app.token_authenticator :
614+ class : AppBundle\Security\TokenAuthenticator
615+ arguments : ['@security.token_storage']
616+
617+ .. code-block :: xml
618+
619+ <!-- app/config/services.xml -->
620+ <services >
621+ <service id =" app.token_authenticator" class =" AppBundle\Security\TokenAuthenticator" >
622+ <argument type =" service" id =" security.token_storage" />
623+ </service >
624+ </services >
625+
626+ .. code-block :: php
627+
628+ // app/config/services.php
629+ use AppBundle\Security\TokenAuthenticator;
630+ use Symfony\Component\DependencyInjection\Definition;
631+ use Symfony\Component\DependencyInjection\Reference;
632+
633+ $container->register('app.token_authenticator', TokenAuthenticator::class)
634+ ->addArgument(new Reference('security.token_storage'));
635+
539636 Frequently Asked Questions
540637--------------------------
541638
0 commit comments