@@ -491,6 +491,103 @@ egg to return a custom message if someone tries this:
491491 curl -H " X-AUTH-TOKEN: ILuvAPIs" http://localhost:8000/
492492 # {"message":"ILuvAPIs is not a real API key: it's just a silly phrase"}
493493
494+ Avoid Authenticating the Browser on Every Request
495+ -------------------------------------------------
496+
497+ If you create a Guard login system that's used by a browser and you're experiencing
498+ problems with your session or CSRF tokens, the cause could be bad behavior by your
499+ authenticator. When a Guard authenticator is meant to be used by a browser, you
500+ should *not * authenticate the user on *every * request. In other words, you need to
501+ make sure the ``getCredentials() `` method *only * returns a non-null value when
502+ you actually *need * to authenticate the user. Why? Because, when ``getCredentials() ``
503+ returns a non-null value, for security purposes, the user's session is "migrated"
504+ to a new session id.
505+
506+ This is an edge-case, and unless you're having session or CSRF token issues, you
507+ can ignore this. Here is an example of good and bad behavior::
508+
509+ public function getCredentials(Request $request)
510+ {
511+ // GOOD behavior: only authenticate on a specific route
512+ if ($request->attributes->get('_route') !== 'login_route' || !$request->isMethod('POST')) {
513+ return null;
514+ }
515+
516+ // e.g. your login system authenticates by the user's IP address
517+ // BAD behavior: authentication will now execute on every request
518+ // even if the user is already authenticated (due to the session)
519+ return array('ip' => $request->getClientIp());
520+ }
521+
522+ The problem occurs when your browser-based authenticator tries to authenticate
523+ the user on *every * request - like in the IP address-based example above. There
524+ are two possible fixes:
525+
526+ 1) If you do *not * need authentication to be stored in the session, set ``stateless: true ``
527+ under your firewall.
528+
529+ 2) Update your authenticator to avoid authentication if the user is already authenticated:
530+
531+ .. code-block :: diff
532+
533+ // src/Security/MyIpAuthenticator.php
534+ // ...
535+
536+ + use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
537+
538+ class MyIpAuthenticator
539+ {
540+ + private $tokenStorage;
541+
542+ + public function __construct(TokenStorageInterface $tokenStorage)
543+ + {
544+ + $this->tokenStorage = $tokenStorage;
545+ + }
546+
547+ public function getCredentials(Request $request)
548+ {
549+ + // if there is already an authenticated user (likely due to the session)
550+ + // then return null and skip authentication: there is no need.
551+ + $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
552+ + if (is_object($user)) {
553+ + return null;
554+ + }
555+
556+ return array('ip' => $request->getClientIp());
557+ }
558+ }
559+
560+ You'll also need to update your service configuration to pass the token storage:
561+
562+ .. configuration-block ::
563+
564+ .. code-block :: yaml
565+
566+ # app/config/services.yml
567+ services :
568+ app.token_authenticator :
569+ class : AppBundle\Security\TokenAuthenticator
570+ arguments : ['@security.token_storage']
571+
572+ .. code-block :: xml
573+
574+ <!-- app/config/services.xml -->
575+ <services >
576+ <service id =" app.token_authenticator" class =" AppBundle\Security\TokenAuthenticator" >
577+ <argument type =" service" id =" security.token_storage" />
578+ </service >
579+ </services >
580+
581+ .. code-block :: php
582+
583+ // app/config/services.php
584+ use AppBundle\Security\TokenAuthenticator;
585+ use Symfony\Component\DependencyInjection\Definition;
586+ use Symfony\Component\DependencyInjection\Reference;
587+
588+ $container->register('app.token_authenticator', TokenAuthenticator::class)
589+ ->addArgument(new Reference('security.token_storage'));
590+
494591 Frequently Asked Questions
495592--------------------------
496593
0 commit comments