Skip to content

Commit c9639be

Browse files
encrypt / decrypt the device code
formatting formatting simplify tests revert example changes formatting fix style fix sa encrypt device code Revert "no encryption on this grant" This reverts commit 7522a46.
1 parent 7522a46 commit c9639be

File tree

6 files changed

+179
-57
lines changed

6 files changed

+179
-57
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99
- Support for PHP 8.4 (PR #1454)
1010

1111
### Fixed
12-
- Fixed bug where scopes were not set on access token when using device authorization grant (PR #1412)
12+
- Fixed device code encryption / decryption and bug where scopes were not set on access token when using device authorization grant (PR #1412)
1313

1414
## [9.0.1] - released 2024-10-14
1515
### Fixed

examples/src/Repositories/DeviceCodeRepository.php

-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface;
1818
use OAuth2ServerExamples\Entities\ClientEntity;
1919
use OAuth2ServerExamples\Entities\DeviceCodeEntity;
20-
use OAuth2ServerExamples\Entities\ScopeEntity;
2120

2221
class DeviceCodeRepository implements DeviceCodeRepositoryInterface
2322
{
@@ -50,14 +49,6 @@ public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceCodeEntityI
5049
$deviceCodeEntity->setIdentifier($deviceCode);
5150
$deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('now +1 hour'));
5251
$deviceCodeEntity->setClient($clientEntity);
53-
$deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());
54-
55-
$scopes = [];
56-
foreach ($scopes as $scope) {
57-
$scopeEntity = new ScopeEntity();
58-
$scopeEntity->setIdentifier($scope);
59-
$deviceCodeEntity->addScope($scopeEntity);
60-
}
6152

6253
// The user identifier should be set when the user authenticates on the
6354
// OAuth server, along with whether they approved the request

src/Grant/DeviceCodeGrant.php

+34-12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use League\OAuth2\Server\RequestEvent;
2929
use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse;
3030
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
31+
use LogicException;
3132
use Psr\Http\Message\ServerRequestInterface;
3233
use TypeError;
3334

@@ -96,6 +97,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ
9697
);
9798

9899
$response = new DeviceCodeResponse();
100+
$response->setEncryptionKey($this->encryptionKey);
99101

100102
if ($this->includeVerificationUriComplete === true) {
101103
$response->includeVerificationUriComplete();
@@ -177,38 +179,58 @@ public function respondToAccessTokenRequest(
177179
*/
178180
protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client): DeviceCodeEntityInterface
179181
{
180-
$deviceCode = $this->getRequestParameter('device_code', $request);
182+
$encryptedDeviceCode = $this->getRequestParameter('device_code', $request);
181183

182-
if (is_null($deviceCode)) {
184+
if (is_null($encryptedDeviceCode)) {
183185
throw OAuthServerException::invalidRequest('device_code');
184186
}
185187

186-
$deviceCodeEntity = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(
187-
$deviceCode
188-
);
189-
190-
if ($deviceCodeEntity instanceof DeviceCodeEntityInterface === false) {
191-
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
188+
try {
189+
$deviceCodePayload = json_decode($this->decrypt($encryptedDeviceCode));
190+
} catch (LogicException $e) {
191+
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the device code', $e);
192+
}
192193

193-
throw OAuthServerException::invalidGrant();
194+
if (!property_exists($deviceCodePayload, 'device_code_id')) {
195+
throw OAuthServerException::invalidRequest('device_code', 'Device code malformed');
194196
}
195197

196-
if (time() > $deviceCodeEntity->getExpiryDateTime()->getTimestamp()) {
198+
if (time() > $deviceCodePayload->expire_time) {
197199
throw OAuthServerException::expiredToken('device_code');
198200
}
199201

200-
if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCode) === true) {
202+
if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) {
201203
throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked');
202204
}
203205

204-
if ($deviceCodeEntity->getClient()->getIdentifier() !== $client->getIdentifier()) {
206+
if ($deviceCodePayload->client_id !== $client->getIdentifier()) {
205207
throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client');
206208
}
207209

210+
$deviceCodeEntity = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(
211+
$deviceCodePayload->device_code_id
212+
);
213+
214+
if ($deviceCodeEntity instanceof DeviceCodeEntityInterface === false) {
215+
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
216+
217+
throw OAuthServerException::invalidGrant();
218+
}
219+
208220
if ($this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt()) === true) {
209221
throw OAuthServerException::slowDown();
210222
}
211223

224+
$deviceCodeEntity->setIdentifier($deviceCodePayload->device_code_id);
225+
$deviceCodeEntity->setClient($client);
226+
$deviceCodeEntity->setExpiryDateTime((new DateTimeImmutable())->setTimestamp($deviceCodePayload->expire_time));
227+
228+
$scopes = $this->validateScopes($deviceCodePayload->scopes);
229+
230+
foreach ($scopes as $scope) {
231+
$deviceCodeEntity->addScope($scope);
232+
}
233+
212234
return $deviceCodeEntity;
213235
}
214236

src/ResponseTypes/DeviceCodeResponse.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,21 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter
3434
{
3535
$expireDateTime = $this->deviceCodeEntity->getExpiryDateTime()->getTimestamp();
3636

37+
$payload = [
38+
'client_id' => $this->deviceCodeEntity->getClient()->getIdentifier(),
39+
'device_code_id' => $this->deviceCodeEntity->getIdentifier(),
40+
'scopes' => $this->deviceCodeEntity->getScopes(),
41+
'expire_time' => $expireDateTime,
42+
];
43+
44+
$jsonPayload = json_encode($payload);
45+
46+
if ($jsonPayload === false) {
47+
throw new LogicException('An error was encountered when JSON encoding the device code request response');
48+
}
49+
3750
$responseParams = [
38-
'device_code' => $this->deviceCodeEntity->getIdentifier(),
51+
'device_code' => $this->encrypt($jsonPayload),
3952
'user_code' => $this->deviceCodeEntity->getUserCode(),
4053
'verification_uri' => $this->deviceCodeEntity->getVerificationUri(),
4154
'expires_in' => $expireDateTime - time(),

0 commit comments

Comments
 (0)