Skip to content

Commit 5c3120d

Browse files
committed
Added remember me functionality
1 parent 9782b52 commit 5c3120d

8 files changed

+164
-47
lines changed

Authenticator/AbstractLoginFormAuthenticator.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @experimental in 5.1
2727
*/
28-
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
28+
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, RememberMeAuthenticatorInterface
2929
{
3030
/**
3131
* Return the URL to the login page.
@@ -46,11 +46,6 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
4646
return new RedirectResponse($url);
4747
}
4848

49-
public function supportsRememberMe(): bool
50-
{
51-
return true;
52-
}
53-
5449
/**
5550
* Override to control what happens when the user hits a secure page
5651
* but isn't logged in yet.
@@ -61,4 +56,9 @@ public function start(Request $request, AuthenticationException $authException =
6156

6257
return new RedirectResponse($url);
6358
}
59+
60+
public function supportsRememberMe(): bool
61+
{
62+
return true;
63+
}
6464
}

Authenticator/AnonymousAuthenticator.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,4 @@ public function onAuthenticationSuccess(Request $request, TokenInterface $token,
7575
{
7676
return null;
7777
}
78-
79-
public function supportsRememberMe(): bool
80-
{
81-
return false;
82-
}
8378
}

Authenticator/AuthenticatorInterface.php

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,4 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
102102
* will be authenticated. This makes sense, for example, with an API.
103103
*/
104104
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response;
105-
106-
/**
107-
* Does this method support remember me cookies?
108-
*
109-
* Remember me cookie will be set if *all* of the following are met:
110-
* A) This method returns true
111-
* B) The remember_me key under your firewall is configured
112-
* C) The "remember me" functionality is activated. This is usually
113-
* done by having a _remember_me checkbox in your form, but
114-
* can be configured by the "always_remember_me" and "remember_me_parameter"
115-
* parameters under the "remember_me" firewall key
116-
* D) The onAuthenticationSuccess method returns a Response object
117-
*/
118-
public function supportsRememberMe(): bool;
119105
}

Authenticator/HttpBasicAuthenticator.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,4 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
9494

9595
return $this->start($request, $exception);
9696
}
97-
98-
public function supportsRememberMe(): bool
99-
{
100-
return false;
101-
}
10297
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator\Token;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
17+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
18+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
20+
use Symfony\Component\Security\Core\User\UserInterface;
21+
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
22+
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
23+
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
24+
25+
/**
26+
* The RememberMe *Authenticator* performs remember me authentication.
27+
*
28+
* This authenticator is executed whenever a user's session
29+
* expired and a remember me cookie was found. This authenticator
30+
* then "re-authenticates" the user using the information in the
31+
* cookie.
32+
*
33+
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
34+
* @author Wouter de Jong <wouter@wouterj.nl>
35+
*
36+
* @final
37+
*/
38+
class RememberMeAuthenticator implements AuthenticatorInterface
39+
{
40+
private $rememberMeServices;
41+
private $secret;
42+
private $tokenStorage;
43+
private $options;
44+
private $sessionStrategy;
45+
46+
public function __construct(AbstractRememberMeServices $rememberMeServices, string $secret, TokenStorageInterface $tokenStorage, array $options, ?SessionAuthenticationStrategy $sessionStrategy = null)
47+
{
48+
$this->rememberMeServices = $rememberMeServices;
49+
$this->secret = $secret;
50+
$this->tokenStorage = $tokenStorage;
51+
$this->options = $options;
52+
$this->sessionStrategy = $sessionStrategy;
53+
}
54+
55+
public function supports(Request $request): ?bool
56+
{
57+
// do not overwrite already stored tokens (i.e. from the session)
58+
if (null !== $this->tokenStorage->getToken()) {
59+
return false;
60+
}
61+
62+
if (($cookie = $request->attributes->get(AbstractRememberMeServices::COOKIE_ATTR_NAME)) && null === $cookie->getValue()) {
63+
return false;
64+
}
65+
66+
if (!$request->cookies->has($this->options['name'])) {
67+
return false;
68+
}
69+
70+
// the `null` return value indicates that this authenticator supports lazy firewalls
71+
return null;
72+
}
73+
74+
public function getCredentials(Request $request)
75+
{
76+
return [
77+
'cookie_parts' => explode(AbstractRememberMeServices::COOKIE_DELIMITER, base64_decode($request->cookies->get($this->options['name']))),
78+
'request' => $request,
79+
];
80+
}
81+
82+
/**
83+
* @param array $credentials
84+
*/
85+
public function getUser($credentials): ?UserInterface
86+
{
87+
return $this->rememberMeServices->performLogin($credentials['cookie_parts'], $credentials['request']);
88+
}
89+
90+
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
91+
{
92+
return new RememberMeToken($user, $providerKey, $this->secret);
93+
}
94+
95+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
96+
{
97+
$this->rememberMeServices->loginFail($request, $exception);
98+
99+
return null;
100+
}
101+
102+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
103+
{
104+
if ($request->hasSession() && $request->getSession()->isStarted()) {
105+
$this->sessionStrategy->onAuthentication($request, $token);
106+
}
107+
108+
return null;
109+
}
110+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Authenticator;
13+
14+
/**
15+
* This interface must be extended if the authenticator supports remember me functionality.
16+
*
17+
* Remember me cookie will be set if *all* of the following are met:
18+
* A) SupportsRememberMe() returns true in the successful authenticator
19+
* B) The remember_me key under your firewall is configured
20+
* C) The "remember me" functionality is activated. This is usually
21+
* done by having a _remember_me checkbox in your form, but
22+
* can be configured by the "always_remember_me" and "remember_me_parameter"
23+
* parameters under the "remember_me" firewall key
24+
* D) The onAuthenticationSuccess method returns a Response object
25+
*
26+
* @author Wouter de Jong <wouter@wouterj.nl>
27+
*/
28+
interface RememberMeAuthenticatorInterface
29+
{
30+
public function supportsRememberMe(): bool;
31+
}

EventListener/RememberMeListener.php

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,38 @@
55
use Psr\Log\LoggerInterface;
66
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
77
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
8+
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticatorInterface;
89
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
910
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
1011
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
1112

1213
/**
14+
* The RememberMe *listener* creates and deletes remember me cookies.
15+
*
16+
* Upon login success or failure and support for remember me
17+
* in the firewall and authenticator, this listener will create
18+
* a remember me cookie.
19+
* Upon login failure, all remember me cookies are removed.
20+
*
1321
* @author Wouter de Jong <wouter@wouterj.nl>
1422
*
1523
* @final
1624
* @experimental in 5.1
1725
*/
1826
class RememberMeListener implements EventSubscriberInterface
1927
{
28+
private $rememberMeServices;
2029
private $providerKey;
2130
private $logger;
22-
/** @var RememberMeServicesInterface|null */
23-
private $rememberMeServices;
2431

25-
public function __construct(string $providerKey, ?LoggerInterface $logger = null)
32+
public function __construct(RememberMeServicesInterface $rememberMeServices, string $providerKey, ?LoggerInterface $logger = null)
2633
{
34+
$this->rememberMeServices = $rememberMeServices;
2735
$this->providerKey = $providerKey;
2836
$this->logger = $logger;
2937
}
3038

3139

32-
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices): void
33-
{
34-
$this->rememberMeServices = $rememberMeServices;
35-
}
36-
3740
public function onSuccessfulLogin(LoginSuccessEvent $event): void
3841
{
3942
if (!$this->isRememberMeEnabled($event->getAuthenticator(), $event->getProviderKey())) {
@@ -59,15 +62,7 @@ private function isRememberMeEnabled(AuthenticatorInterface $authenticator, stri
5962
return false;
6063
}
6164

62-
if (null === $this->rememberMeServices) {
63-
if (null !== $this->logger) {
64-
$this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($authenticator)]);
65-
}
66-
67-
return false;
68-
}
69-
70-
if (!$authenticator->supportsRememberMe()) {
65+
if (!$authenticator instanceof RememberMeAuthenticatorInterface || !$authenticator->supportsRememberMe()) {
7166
if (null !== $this->logger) {
7267
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($authenticator)]);
7368
}

RememberMe/AbstractRememberMeServices.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ public function getSecret()
8989
return $this->secret;
9090
}
9191

92+
public function performLogin(array $cookieParts, Request $request): UserInterface
93+
{
94+
return $this->processAutoLoginCookie($cookieParts, $request);
95+
}
96+
9297
/**
9398
* Implementation of RememberMeServicesInterface. Detects whether a remember-me
9499
* cookie was set, decodes it, and hands it to subclasses for further processing.

0 commit comments

Comments
 (0)