diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe3047a..851c055a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ You can find and compare releases at the [GitHub release page](https://github.co Please see (https://github.com/PHP-Open-Source-Saver/jwt-auth/releases/tag/2.8.0) ### Added +- #268 Implement config variable to allow iat to remain unchanged claim when refreshing a token - Adds support for Laravel 12 - Adds CI testing for PHP 8.4 - Don't show jwt secret if show option is false even if the key is updated diff --git a/config/config.php b/config/config.php index ab0e53fa..5f817b7d 100644 --- a/config/config.php +++ b/config/config.php @@ -96,10 +96,19 @@ | Refresh time to live |-------------------------------------------------------------------------- | - | Specify the length of time (in minutes) that the token can be refreshed - | within. I.E. The user can refresh their token within a 2 week window of - | the original token being created until they must re-authenticate. - | Defaults to 2 weeks. + | Specify the length of time (in minutes) that the token can be refreshed within. + | This defines the refresh window, during which the user can refresh their token + | before re-authentication is required. + | + | By default, a refresh will NOT issue a new "iat" (issued at) timestamp. If changed + | to true, each refresh will issue a new "iat" timestamp, extending the refresh + | period from the most recent refresh. This results in a rolling refresh + | + | To retain a fluid refresh window from the last refresh action (i.e., the behavior between + | version 2.5.0 and 2.8.2), set "refresh_iat" to true. With this setting, the refresh + | window will renew with each subsequent refresh. + | + | The refresh ttl defaults to 2 weeks. | | You can also set this to null, to yield an infinite refresh time. | Some may want this instead of never expiring tokens for e.g. a mobile app. @@ -108,6 +117,7 @@ | */ + 'refresh_iat' => env('JWT_REFRESH_IAT', false), 'refresh_ttl' => (int) env('JWT_REFRESH_TTL', 20160), /* diff --git a/src/Manager.php b/src/Manager.php index f584a0ca..5d3a0224 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -52,6 +52,13 @@ class Manager */ protected $blacklistEnabled = true; + /** + * The refresh iat flag. + * + * @var bool + */ + protected $refreshIat = false; + /** * the persistent claims. * @@ -182,7 +189,7 @@ protected function buildRefreshClaims(Payload $payload) $persistentClaims, [ 'sub' => $payload['sub'], - 'iat' => Utils::now()->timestamp, + 'iat' => $this->refreshIat ? Utils::now()->timestamp : $payload['iat'], ] ); } @@ -267,4 +274,18 @@ public function setPersistentClaims(array $claims) return $this; } + + /** + * Set whether the refresh iat is enabled. + * + * @param bool $enabled + * + * @return $this + */ + public function setRefreshIat($refreshIat) + { + $this->refreshIat = $refreshIat; + + return $this; + } } diff --git a/src/Providers/AbstractServiceProvider.php b/src/Providers/AbstractServiceProvider.php index c1d730fa..45899ce6 100644 --- a/src/Providers/AbstractServiceProvider.php +++ b/src/Providers/AbstractServiceProvider.php @@ -213,6 +213,7 @@ protected function registerManager() ); return $instance->setBlacklistEnabled((bool) $app->make('config')->get('jwt.blacklist_enabled')) + ->setRefreshIat((bool) $app->make('config')->get('jwt.refresh_iat', false)) ->setPersistentClaims($app->make('config')->get('jwt.persistent_claims')) ->setBlackListExceptionEnabled((bool) $app->make('config')->get('jwt.show_black_list_exception', 0)); }); diff --git a/tests/.ManagerTest.php.swp b/tests/.ManagerTest.php.swp new file mode 100644 index 00000000..571f5241 Binary files /dev/null and b/tests/.ManagerTest.php.swp differ diff --git a/tests/DefaultConfigValuesTest.php b/tests/DefaultConfigValuesTest.php index 40dd94ae..a59f2cca 100644 --- a/tests/DefaultConfigValuesTest.php +++ b/tests/DefaultConfigValuesTest.php @@ -44,6 +44,11 @@ public function testTtlShouldBeSet() $this->assertEquals(60, $this->configuration['ttl']); } + public function testRefreshIatShouldBeSet() + { + $this->assertEquals(false, $this->configuration['refresh_iat']); + } + public function testRefreshTtlShouldBeSet() { $this->assertEquals(20160, $this->configuration['refresh_ttl']); diff --git a/tests/ManagerTest.php b/tests/ManagerTest.php index e9c6ab85..b9175c10 100644 --- a/tests/ManagerTest.php +++ b/tests/ManagerTest.php @@ -203,6 +203,7 @@ public function testBuildRefreshClaimsMethodWillRefreshTheIAT() $buildRefreshClaimsMethod = $managerClass->getMethod('buildRefreshClaims'); $buildRefreshClaimsMethod->setAccessible(true); $managerInstance = new Manager($this->jwt, $this->blacklist, $this->factory); + $managerInstance->setRefreshIat(true); $firstResult = $buildRefreshClaimsMethod->invokeArgs($managerInstance, [$payload]); Carbon::setTestNow(Carbon::now()->addMinutes(2)); @@ -220,6 +221,42 @@ public function testBuildRefreshClaimsMethodWillRefreshTheIAT() $this->assertNotEquals($firstResult['iat'], $secondResult['iat']); } + public function testBuildRefreshClaimsMethodWillNotRefreshTheIAT() + { + $claims = [ + new Subject(1), + new Issuer('http://example.com'), + new Expiration($this->testNowTimestamp - 3600), + new NotBefore($this->testNowTimestamp), + new IssuedAt($this->testNowTimestamp), + new JwtId('foo'), + ]; + $collection = Collection::make($claims); + + $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); + $payload = new Payload($collection, $this->validator); + + $managerClass = new \ReflectionClass(Manager::class); + $buildRefreshClaimsMethod = $managerClass->getMethod('buildRefreshClaims'); + $buildRefreshClaimsMethod->setAccessible(true); + $managerInstance = new Manager($this->jwt, $this->blacklist, $this->factory); + + $firstResult = $buildRefreshClaimsMethod->invokeArgs($managerInstance, [$payload]); + Carbon::setTestNow(Carbon::now()->addMinutes(2)); + $secondResult = $buildRefreshClaimsMethod->invokeArgs($managerInstance, [$payload]); + + $this->assertIsInt($firstResult['iat']); + $this->assertIsInt($secondResult['iat']); + + $carbonTimestamp = Carbon::createFromTimestamp($firstResult['iat']); + $this->assertInstanceOf(Carbon::class, $carbonTimestamp); + + $carbonTimestamp = Carbon::createFromTimestamp($secondResult['iat']); + $this->assertInstanceOf(Carbon::class, $carbonTimestamp); + + $this->assertEquals($firstResult['iat'], $secondResult['iat']); + } + /** * @throws InvalidClaimException */