8000 [Security/Http] Hash Persistent RememberMe token · wucdbm/symfony@e2425b9 · GitHub
[go: up one dir, main page]

Skip to content

Commit e2425b9

Browse files
Guillaume Pédelagrabechalasr
Guillaume Pédelagrabe
authored andcommitted
[Security/Http] Hash Persistent RememberMe token
1 parent 7c90c8b commit e2425b9

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