10000 feature #35960 [Security/Http] Hash Persistent RememberMe token (guil… · wucdbm/symfony@45c4ffa · GitHub
[go: up one dir, main page]

Skip to content

Commit 45c4ffa

Browse files
committed
feature symfony#35960 [Security/Http] Hash Persistent RememberMe token (guillbdx)
This PR was squashed before being merged into the 5.1-dev branch (closes symfony#35960). Discussion ---------- [Security/Http] Hash Persistent RememberMe token | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix symfony#27910 | License | MIT | Doc PR | Not sure this enhancement needs documentation The purpose of this PR is to enhance the Remember Me persistent token feature: instead of storing cleared token value in DB, the values will be hashed. To make sure that existing remember me cookies will keep being valid after this change, we prefix the new token values with 'hash_'. In case the token value doesn't match this prefix, we keep validating it the old way. Commits ------- e2425b9 [Security/Http] Hash Persistent RememberMe token
2 parents 7c90c8b + e2425b9 commit 45c4ffa

File tree

3 files changed

+35
-7
lines changed

3 files changed

+35
-7
lines changed

src/Symfony/Component/Security/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Added access decision strategy to override access decisions by voter service priority
88
* Added `IS_ANONYMOUS`, `IS_REMEMBERED`, `IS_IMPERSONATOR`
9+
* Hash the persistent RememberMe token value in database.
910

1011
5.0.0
1112
-----

src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\HttpFoundation\Request;
1616
use Symfony\Component\HttpFoundation\Response;
1717
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
18+
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface;
1819
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
1920
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2021
use Symfony\Component\Security\Core\Exception\AuthenticationException;
@@ -29,6 +30,8 @@
2930
*/
3031
class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
3132
{
33+
private const HASHED_TOKEN_PREFIX = 'sha256_';
34+
3235
/** @var TokenProviderInterface */
3336
private $tokenProvider;
3437

@@ -66,7 +69,7 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request)
6669
list($series, $tokenValue) = $cookieParts;
6770
$persistentToken = $this->tokenProvider->loadTokenBySeries($series);
6871

69-
if (!hash_equals($persistentToken->getTokenValue(), $tokenValue)) {
72+
if (!$this->isTokenValueValid($persistentToken, $tokenValue)) {
7073
throw new CookieTheftException('This token was already used. The account is possibly compromised.');
7174
}
7275

@@ -75,7 +78,7 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request)
7578
}
7679

7780
$tokenValue = base64_encode(random_bytes(64));
78-
$this->tokenProvider->updateToken($series, $tokenValue, new \DateTime());
81+
$this->tokenProvider->updateToken($series, $this->generateHash($tokenValue), new \DateTime());
7982
$request->attributes->set(self::COOKIE_ATTR_NAME,
8083
new Cookie(
8184
$this->options['name'],
@@ -106,7 +109,7 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt
106109
\get_class($user = $token->getUser()),
107110
$user->getUsername(),
108111
$series,
109-
$tokenValue,
112+
$this->generateHash($tokenValue),
110113
new \DateTime()
111114
)
112115
);
@@ -125,4 +128,18 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt
125128
)
126129
);
127130
}
131+
132+
private function generateHash(string $tokenValue): string
133+
{
134+
return self::HASHED_TOKEN_PREFIX.hash_hmac('sha256', $tokenValue, $this->getSecret());
135+
}
136+
137+
private function isTokenValueValid(PersistentTokenInterface $persistentToken, string $tokenValue): bool
138+
{
139+
if (0 === strpos($persistentToken->getTokenValue(), self::HASHED_TOKEN_PREFIX)) {
140+
return hash_equals($persistentToken->getTokenValue(), $this->generateHash($tokenValue));
141+
}
142+
143+
return hash_equals($persistentToken->getTokenValue(), $tokenValue);
144+
}
128145
}

src/Symfony/Component/Security/Http/Tests/RememberMe/PersistentTokenBasedRememberMeServicesTest.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function testAutoLoginReturnsNullOnNonExistentUser()
8585
$tokenProvider
8686
->expects($this->once())
8787
->method('loadTokenBySeries')
88-
->willReturn(new PersistentToken('fooclass', 'fooname', 'fooseries', 'foovalue', new \DateTime()))
88+
->willReturn(new PersistentToken('fooclass', 'fooname', 'fooseries', $this->generateHash('foovalue'), new \DateTime()))
8989
;
9090
$service->setTokenProvider($tokenProvider);
9191

@@ -142,15 +142,19 @@ public function testAutoLoginDoesNotAcceptAnExpiredCookie()
142142
->expects($this->once())
143143
->method('loadTokenBySeries')
144144
->with($this->equalTo('fooseries'))
145-
->willReturn(new PersistentToken('fooclass', 'username', 'fooseries', 'foovalue', new \DateTime('yesterday')))
145+
->willReturn(new PersistentToken('fooclass', 'username', 'fooseries', $this->generateHash('foovalue'), new \DateTime('yesterday')))
146146
;
147147
$service->setTokenProvider($tokenProvider);
148148

149149
$this->assertNull($service->autoLogin($request));
150150
$this->assertTrue($request->attributes->has(RememberMeServicesInterface::COOKIE_ATTR_NAME));
151151
}
152152

153-
public function testAutoLogin()
153+
/**
154+
* @testWith [true]
155+
* [false]
156+
*/
157+
public function testAutoLogin(bool $hashTokenValue)
154158
{
155159
$user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock();
156160
$user
@@ -172,11 +176,12 @@ public function testAutoLogin()
172176
$request->cookies->set('foo', $this->encodeCookie(['fooseries', 'foovalue']));
173177

174178
$tokenProvider = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface')->getMock();
179+
$tokenValue = $hashTokenValue ? $this->generateHash('foovalue') : 'foovalue';
175180
$tokenProvider
176181
->expects($this->once())
177182
->method('loadTokenBySeries')
178183
->with($this->equalTo('fooseries'))
179-
->willReturn(new PersistentToken('fooclass', 'foouser', 'fooseries', 'foovalue', new \DateTime()))
184+
->willReturn(new PersistentToken('fooclass', 'foouser', 'fooseries', $tokenValue, new \DateTime()))
180185
;
181186
$service->setTokenProvider($tokenProvider);
182187

@@ -338,4 +343,9 @@ protected function getProvider()
338343

339344
return $provider;
340345
}
346+
347+
protected function generateHash(string $tokenValue): string
348+
{
349+
return 'sha256_'.hash_hmac('sha256', $tokenValue, $this->getService()->getSecret());
350+
}
341351
}

0 commit comments

Comments
 (0)
0