Skip to content

Commit fa30cd6

Browse files
committed
Merge branch '10.x'
# Conflicts: # CHANGELOG.md # src/Passport.php
2 parents 9d4aed7 + 1c69a01 commit fa30cd6

File tree

8 files changed

+151
-13
lines changed

8 files changed

+151
-13
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
fail-fast: true
1515
matrix:
16-
php: [7.3, 7.4, 8.0]
16+
php: [7.3, 7.4, 8.0, 8.1]
1717
laravel: [^8.0]
1818

1919
name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# Release Notes
22

3-
## [Unreleased](https://github.com/laravel/passport/compare/v10.1.4...master)
3+
## [Unreleased](https://github.com/laravel/passport/compare/v10.2.0...master)
4+
5+
6+
## [v10.2.0 (2021-11-02)](https://github.com/laravel/passport/compare/v10.1.4...v10.2.0)
7+
8+
### Added
9+
- Add custom encryption key for JWT tokens ([#1501](https://github.com/laravel/passport/pull/1501))
10+
11+
### Changed
12+
- Refactor expiry dates to intervals ([#1500](https://github.com/laravel/passport/pull/1500))
413

514

615
## [v10.1.4 (2021-10-19)](https://github.com/laravel/passport/compare/v10.1.3...v10.1.4)

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
beStrictAboutTestsThatDoNotTestAnything="false"
55
bootstrap="vendor/autoload.php"
66
colors="true"
7+
convertDeprecationsToExceptions="true"
78
convertErrorsToExceptions="true"
89
convertNoticesToExceptions="true"
910
convertWarningsToExceptions="true"

src/ApiTokenCookieFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,6 @@ protected function createToken($userId, $csrfToken, Carbon $expiration)
7777
'sub' => $userId,
7878
'csrf' => $csrfToken,
7979
'expiry' => $expiration->getTimestamp(),
80-
], $this->encrypter->getKey());
80+
], Passport::tokenEncryptionKey($this->encrypter));
8181
}
8282
}

src/Guards/TokenGuard.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ protected function decodeJwtTokenCookie($request)
249249
{
250250
return (array) JWT::decode(
251251
CookieValuePrefix::remove($this->encrypter->decrypt($request->cookie(Passport::cookie()), Passport::$unserializesCookies)),
252-
$this->encrypter->getKey(),
252+
Passport::tokenEncryptionKey($this->encrypter),
253253
['HS256']
254254
);
255255
}

src/Passport.php

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Carbon\Carbon;
66
use DateInterval;
77
use DateTimeInterface;
8+
use Illuminate\Contracts\Encryption\Encrypter;
89
use League\OAuth2\Server\ResourceServer;
910
use Mockery;
1011
use Psr\Http\Message\ServerRequestInterface;
@@ -38,23 +39,50 @@ class Passport
3839
* The date when access tokens expire.
3940
*
4041
* @var \DateTimeInterface|null
42+
*
43+
* @deprecated Will be removed in the next major Passport release.
4144
*/
4245
public static $tokensExpireAt;
4346

47+
/**
48+
* The interval when access tokens expire.
49+
*
50+
* @var \DateInterval|null
51+
*/
52+
public static $tokensExpireIn;
53+
4454
/**
4555
* The date when refresh tokens expire.
4656
*
4757
* @var \DateTimeInterface|null
58+
*
59+
* @deprecated Will be removed in the next major Passport release.
4860
*/
4961
public static $refreshTokensExpireAt;
5062

63+
/**
64+
* The date when refresh tokens expire.
65+
*
66+
* @var \DateInterval|null
67+
*/
68+
public static $refreshTokensExpireIn;
69+
5170
/**
5271
* The date when personal access tokens expire.
5372
*
5473
* @var \DateTimeInterface|null
74+
*
75+
* @deprecated Will be removed in the next major Passport release.
5576
*/
5677
public static $personalAccessTokensExpireAt;
5778

79+
/**
80+
* The date when personal access tokens expire.
81+
*
82+
* @var \DateInterval|null
83+
*/
84+
public static $personalAccessTokensExpireIn;
85+
5886
/**
5987
* The name for API token cookies.
6088
*
@@ -133,10 +161,19 @@ class Passport
133161
public static $unserializesCookies = false;
134162

135163
/**
164+
* Indicates if client secrets will be hashed.
165+
*
136166
* @var bool
137167
*/
138168
public static $hashesClientSecrets = false;
139169

170+
/**
171+
* The callback that should be used to generate JWT encryption keys.
172+
*
173+
* @var callable
174+
*/
175+
public static $tokenEncryptionKeyCallback;
176+
140177
/**
141178
* Indicates the scope should inherit its parent scope.
142179
*
@@ -235,12 +272,11 @@ public static function tokensCan(array $scopes)
235272
public static function tokensExpireIn(DateTimeInterface $date = null)
236273
{
237274
if (is_null($date)) {
238-
return static::$tokensExpireAt
239-
? Carbon::now()->diff(static::$tokensExpireAt)
240-
: new DateInterval('P1Y');
275+
return static::$tokensExpireIn ?? new DateInterval('P1Y');
241276
}
242277

243278
static::$tokensExpireAt = $date;
279+
static::$tokensExpireIn = Carbon::now()->diff($date);
244280

245281
return new static;
246282
}
@@ -254,12 +290,11 @@ public static function tokensExpireIn(DateTimeInterface $date = null)
254290
public static function refreshTokensExpireIn(DateTimeInterface $date = null)
255291
{
256292
if (is_null($date)) {
257-
return static::$refreshTokensExpireAt
258-
? Carbon::now()->diff(static::$refreshTokensExpireAt)
259-
: new DateInterval('P1Y');
293+
return static::$refreshTokensExpireIn ?? new DateInterval('P1Y');
260294
}
261295

262296
static::$refreshTokensExpireAt = $date;
297+
static::$refreshTokensExpireIn = Carbon::now()->diff($date);
263298

264299
return new static;
265300
}
@@ -273,12 +308,11 @@ public static function refreshTokensExpireIn(DateTimeInterface $date = null)
273308
public static function personalAccessTokensExpireIn(DateTimeInterface $date = null)
274309
{
275310
if (is_null($date)) {
276-
return static::$personalAccessTokensExpireAt
277-
? Carbon::now()->diff(static::$personalAccessTokensExpireAt)
278-
: new DateInterval('P1Y');
311+
return static::$personalAccessTokensExpireIn ?? new DateInterval('P1Y');
279312
}
280313

281314
static::$personalAccessTokensExpireAt = $date;
315+
static::$personalAccessTokensExpireIn = Carbon::now()->diff($date);
282316

283317
return new static;
284318
}
@@ -590,6 +624,32 @@ public static function hashClientSecrets()
590624
return new static;
591625
}
592626

627+
/**
628+
* Specify the callback that should be invoked to generate encryption keys for encrypting JWT tokens.
629+
*
630+
* @param callable $callback
631+
* @return static
632+
*/
633+
public static function encryptTokensUsing($callback)
634+
{
635+
static::$tokenEncryptionKeyCallback = $callback;
636+
637+
return new static;
638+
}
639+
640+
/**
641+
* Generate an encryption key for encrypting JWT tokens.
642+
*
643+
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
644+
* @return string
645+
*/
646+
public static function tokenEncryptionKey(Encrypter $encrypter)
647+
{
648+
return is_callable(static::$tokenEncryptionKeyCallback) ?
649+
(static::$tokenEncryptionKeyCallback)($encrypter) :
650+
$encrypter->getKey();
651+
}
652+
593653
/**
594654
* Configure Passport to not register its migrations.
595655
*

tests/Unit/ApiTokenCookieFactoryTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
namespace Laravel\Passport\Tests\Unit;
44

55
use Illuminate\Contracts\Config\Repository;
6+
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
67
use Illuminate\Encryption\Encrypter;
78
use Laravel\Passport\ApiTokenCookieFactory;
9+
use Laravel\Passport\Passport;
810
use Mockery as m;
911
use PHPUnit\Framework\TestCase;
1012
use Symfony\Component\HttpFoundation\Cookie;
@@ -33,4 +35,29 @@ public function test_cookie_can_be_successfully_created()
3335

3436
$this->assertInstanceOf(Cookie::class, $cookie);
3537
}
38+
39+
public function test_cookie_can_be_successfully_created_when_using_a_custom_encryption_key()
40+
{
41+
Passport::encryptTokensUsing(function (EncrypterContract $encrypter) {
42+
return $encrypter->getKey().'.mykey';
43+
});
44+
45+
$config = m::mock(Repository::class);
46+
$config->shouldReceive('get')->with('session')->andReturn([
47+
'lifetime' => 120,
48+
'path' => '/',
49+
'domain' => null,
50+
'secure' => true,
51+
'same_site' => 'lax',
52+
]);
53+
$encrypter = new Encrypter(str_repeat('a', 16));
54+
$factory = new ApiTokenCookieFactory($config, $encrypter);
55+
56+
$cookie = $factory->make(1, 'token');
57+
58+
$this->assertInstanceOf(Cookie::class, $cookie);
59+
60+
// Revert to the default encryption method
61+
Passport::encryptTokensUsing(null);
62+
}
3663
}

tests/Unit/TokenGuardTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Firebase\JWT\JWT;
77
use Illuminate\Container\Container;
88
use Illuminate\Contracts\Debug\ExceptionHandler;
9+
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
910
use Illuminate\Cookie\CookieValuePrefix;
1011
use Illuminate\Encryption\Encrypter;
1112
use Illuminate\Http\Request;
@@ -229,6 +230,46 @@ public function test_cookie_xsrf_is_verified_against_xsrf_token_header()
229230
$this->assertNull($guard->user($request));
230231
}
231232

233+
public function test_users_may_be_retrieved_from_cookies_with_xsrf_token_header_when_using_a_custom_encryption_key()
234+
{
235+
Passport::encryptTokensUsing(function (EncrypterContract $encrypter) {
236+
return $encrypter->getKey().'.mykey';
237+
});
238+
239+
$resourceServer = m::mock(ResourceServer::class);
240+
$userProvider = m::mock(PassportUserProvider::class);
241+
$tokens = m::mock(TokenRepository::class);
242+
$clients = m::mock(ClientRepository::class);
243+
$encrypter = new Encrypter(str_repeat('a', 16));
244+
245+
$clients->shouldReceive('findActive')
246+
->with(1)
247+
->andReturn(new TokenGuardTestClient);
248+
249+
$guard = new TokenGuard($resourceServer, $userProvider, $tokens, $clients, $encrypter);
250+
251+
$request = Request::create('/');
252+
$request->headers->set('X-XSRF-TOKEN', $encrypter->encrypt(CookieValuePrefix::create('X-XSRF-TOKEN', $encrypter->getKey()).'token', false));
253+
$request->cookies->set('laravel_token',
254+
$encrypter->encrypt(CookieValuePrefix::create('laravel_token', $encrypter->getKey()).JWT::encode([
255+
'sub' => 1,
256+
'aud' => 1,
257+
'csrf' => 'token',
258+
'expiry' => Carbon::now()->addMinutes(10)->getTimestamp(),
259+
], Passport::tokenEncryptionKey($encrypter)), false)
260+
);
261+
262+
$userProvider->shouldReceive('retrieveById')->with(1)->andReturn($expectedUser = new TokenGuardTestUser);
263+
$userProvider->shouldReceive('getProviderName')->andReturn(null);
264+
265+
$user = $guard->user($request);
266+
267+
$this->assertEquals($expectedUser, $user);
268+
269+
// Revert to the default encryption method
270+
Passport::encryptTokensUsing(null);
271+
}
272+
232273
public function test_xsrf_token_cookie_without_a_token_header_is_not_accepted()
233274
{
234275
$resourceServer = m::mock(ResourceServer::class);

0 commit comments

Comments
 (0)