@@ -536,6 +536,103 @@ and add the following logic::
536
536
// ...
537
537
}
538
538
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
+
539
636
Frequently Asked Questions
540
637
--------------------------
541
638
0 commit comments