From f63f1421fb375d19f9cbd2a515eecb2ce178e6b9 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 8 Oct 2026 10:49:03 -0300 Subject: [PATCH 1/5] feat(oauth2): add jitter to access token lifetime on refresh_token grant Tokens issued by the refresh_token grant now get lifetime - random_int(0, OAuth2.AccessToken.RefreshJitter), so clients that refresh in the same minute spread out instead of hitting /oauth2/token in synchronized bursts. The jittered value feeds expires_in, the access token DB row and the Redis TTL. Other grants are unchanged. The jitter defaults to 720s (server.OAuth2_AccessToken_RefreshJitter), is clamped so the issued lifetime stays >= 60s and <= the configured lifetime, and is editable from the admin server-config screen (0 disables). --- app/Http/Controllers/AdminController.php | 3 + app/Services/OAuth2/TokenService.php | 26 +++- .../Utils/ServerConfigurationService.php | 2 + resources/views/admin/server-config.blade.php | 4 + tests/OAuth2ProtocolTest.php | 121 ++++++++++++++++++ tests/StubServerConfigurationService.php | 4 + 6 files changed, 159 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index e9e36739e..c885633f2 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -496,6 +496,7 @@ public function listServerConfig(){ 'OpenId.Nonce.Lifetime', 'OAuth2.AuthorizationCode.Lifetime', 'OAuth2.AccessToken.Lifetime', + 'OAuth2.AccessToken.RefreshJitter', 'OAuth2.IdToken.Lifetime', 'OAuth2.RefreshToken.Lifetime', 'OAuth2.AccessToken.Revoked.Lifetime', @@ -535,6 +536,7 @@ public function saveServerConfig(){ 'oauth2-auth-code-lifetime' => 'required|integer', 'oauth2-refresh-token-lifetime' => 'required|integer', 'oauth2-access-token-lifetime' => 'required|integer', + 'oauth2-access-token-refresh-jitter' => 'required|integer|min:0', 'oauth2-id-token-lifetime' => 'required|integer', 'oauth2-id-access-token-revoked-lifetime' => 'required|integer', 'oauth2-id-access-token-void-lifetime' => 'required|integer', @@ -555,6 +557,7 @@ public function saveServerConfig(){ 'openid-nonce-lifetime' => 'OpenId.Nonce.Lifetime', 'oauth2-auth-code-lifetime' => 'OAuth2.AuthorizationCode.Lifetime', 'oauth2-access-token-lifetime' => 'OAuth2.AccessToken.Lifetime', + 'oauth2-access-token-refresh-jitter' => 'OAuth2.AccessToken.RefreshJitter', 'oauth2-id-token-lifetime' => 'OAuth2.IdToken.Lifetime', 'oauth2-refresh-token-lifetime' => 'OAuth2.RefreshToken.Lifetime', 'oauth2-id-access-token-revoked-lifetime' => 'OAuth2.AccessToken.Revoked.Lifetime', diff --git a/app/Services/OAuth2/TokenService.php b/app/Services/OAuth2/TokenService.php index 442013185..d99067c1e 100644 --- a/app/Services/OAuth2/TokenService.php +++ b/app/Services/OAuth2/TokenService.php @@ -106,6 +106,11 @@ final class TokenService extends AbstractService implements ITokenService const ClientAccessTokensQty = '.atokens.qty'; const ClientAccessTokensQtyLifetime = 86400; + /** + * floor (seconds) for the jittered lifetime of access tokens issued on refresh + */ + const MinRefreshedAccessTokenLifetime = 60; + const ClientRefreshTokensQty = '.rtokens.qty'; const ClientRefreshTokensQtyLifetime = 86400; @@ -604,7 +609,7 @@ public function createAccessTokenFromRefreshToken(RefreshToken $refresh_token, $ ( $refresh_token, $scope, - $this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime') + $this->getRefreshedAccessTokenLifetime() ) ); @@ -653,6 +658,25 @@ public function createAccessTokenFromRefreshToken(RefreshToken $refresh_token, $ }); } + /** + * Lifetime for access tokens issued by the refresh_token grant: + * configured lifetime minus a random reduction in [0, jitter], so clients that refresh + * together drift apart. Never above the configured lifetime, never below + * MinRefreshedAccessTokenLifetime (unless the configured lifetime itself is lower). + * @return int + */ + private function getRefreshedAccessTokenLifetime(): int + { + $lifetime = intval($this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime')); + $jitter = intval($this->configuration_service->getConfigValue('OAuth2.AccessToken.RefreshJitter')); + $jitter = min(max(0, $jitter), max(0, $lifetime - self::MinRefreshedAccessTokenLifetime)); + + $issued_lifetime = $jitter > 0 ? $lifetime - random_int(0, $jitter) : $lifetime; + + Log::debug(sprintf("TokenService::getRefreshedAccessTokenLifetime lifetime %s jitter %s issued %s", $lifetime, $jitter, $issued_lifetime)); + return $issued_lifetime; + } + /** * @param AccessToken $access_token * @return bool diff --git a/app/Services/Utils/ServerConfigurationService.php b/app/Services/Utils/ServerConfigurationService.php index 70870f628..74782560d 100644 --- a/app/Services/Utils/ServerConfigurationService.php +++ b/app/Services/Utils/ServerConfigurationService.php @@ -144,6 +144,8 @@ public function __construct( $this->default_config_params["OAuth2.AuthorizationCode.Lifetime"] = Config::get('server.OAuth2_AuthorizationCode_Lifetime', 240); $this->default_config_params["OAuth2.AccessToken.Lifetime"] = Config::get('server.OAuth2_AccessToken_Lifetime', 3600); + //max random reduction (seconds) applied to access tokens issued by the refresh_token grant, 0 disables + $this->default_config_params["OAuth2.AccessToken.RefreshJitter"] = Config::get('server.OAuth2_AccessToken_RefreshJitter', 720); $this->default_config_params["OAuth2.IdToken.Lifetime"] = Config::get('server.OAuth2_IdToken_Lifetime', 3600); //infinite by default $this->default_config_params["OAuth2.RefreshToken.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Lifetime', 0); diff --git a/resources/views/admin/server-config.blade.php b/resources/views/admin/server-config.blade.php index 3daa9b68a..9aab097af 100644 --- a/resources/views/admin/server-config.blade.php +++ b/resources/views/admin/server-config.blade.php @@ -40,6 +40,10 @@ +
+ + +
diff --git a/tests/OAuth2ProtocolTest.php b/tests/OAuth2ProtocolTest.php index bcabbe651..0c45b574e 100644 --- a/tests/OAuth2ProtocolTest.php +++ b/tests/OAuth2ProtocolTest.php @@ -700,6 +700,127 @@ public function testRefreshToken() } } + /** + * Runs auth code -> token and returns the decoded token response (access + refresh token). + * @param string $client_id + * @param string $client_secret + * @return object + */ + private function getTokensFromAuthCode(string $client_id, string $client_secret) + { + Session::put("openid.authorization.response", IAuthService::AuthorizationResponse_AllowOnce); + + $response = $this->action("POST", "OAuth2\OAuth2ProviderController@auth", [ + OAuth2Protocol::OAuth2Protocol_ClientId => $client_id, + OAuth2Protocol::OAuth2Protocol_RedirectUri => 'https://www.test.com/oauth2', + OAuth2Protocol::OAuth2Protocol_ResponseType => OAuth2Protocol::OAuth2Protocol_ResponseType_Code, + OAuth2Protocol::OAuth2Protocol_Scope => sprintf('%s/resource-server/read', $this->current_realm), + OAuth2Protocol::OAuth2Protocol_AccessType => OAuth2Protocol::OAuth2Protocol_AccessType_Offline + ], [], [], []); + + $output = []; + parse_str(@parse_url($response->getTargetUrl())['query'], $output); + + $response = $this->action("POST", "OAuth2\OAuth2ProviderController@token", [ + 'code' => $output['code'], + 'redirect_uri' => 'https://www.test.com/oauth2', + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode, + ], [], [], [], + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + + $this->assertResponseStatus(200); + return json_decode($response->getContent()); + } + + /** + * Refreshes $count times, each time with the newest refresh token (they rotate). + * @return array list of decoded refresh responses + */ + private function refreshTokens(object $tokens, int $count, string $client_id, string $client_secret): array + { + $res = []; + $refresh_token = $tokens->refresh_token; + for ($i = 0; $i < $count; $i++) { + $response = $this->action("POST", "OAuth2\OAuth2ProviderController@token", [ + 'refresh_token' => $refresh_token, + 'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_RefreshToken, + ], [], [], [], + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + $this->assertResponseStatus(200); + $json = json_decode($response->getContent()); + $refresh_token = $json->refresh_token; + $res[] = $json; + } + return $res; + } + + /** + * refresh grant lifetime is lifetime - random_int(0, jitter); other grants stay unjittered + * @throws Exception + */ + public function testRefreshTokenJitterBounds() + { + $client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + $lifetime = 3600; + $jitter = 720; + + try { + $_ENV['access.token.lifetime'] = $lifetime; + $_ENV['access.token.refresh.jitter'] = $jitter; + + $tokens = $this->getTokensFromAuthCode($client_id, $client_secret); + // authorization code grant is never jittered + $this->assertEquals($lifetime, $tokens->expires_in); + + $refreshed = $this->refreshTokens($tokens, 5, $client_id, $client_secret); + + $access_token_repository = app(\OAuth2\Repositories\IAccessTokenRepository::class); + $cache_service = app(UtilsServiceCatalog::CacheService); + $values = []; + foreach ($refreshed as $json) { + $expires_in = $json->expires_in; + $values[] = $expires_in; + $this->assertGreaterThanOrEqual($lifetime - $jitter, $expires_in); + $this->assertLessThanOrEqual($lifetime, $expires_in); + + // same value stored on DB and as redis ttl + $hashed = \Laminas\Crypt\Hash::compute('sha256', $json->access_token); + $access_token_db = $access_token_repository->getByValue($hashed); + $this->assertNotNull($access_token_db); + $this->assertEquals($expires_in, $access_token_db->getLifetime()); + $ttl = $cache_service->ttl($hashed); + $this->assertLessThanOrEqual($expires_in, $ttl); + $this->assertGreaterThanOrEqual($expires_in - 5, $ttl); + } + $this->assertGreaterThan(1, count(array_unique($values)), 'refresh lifetimes are not jittered'); + } finally { + unset($_ENV['access.token.lifetime'], $_ENV['access.token.refresh.jitter']); + } + } + + /** + * jitter = 0 keeps the current behaviour + * @throws Exception + */ + public function testRefreshTokenJitterZero() + { + $client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + + try { + $_ENV['access.token.lifetime'] = 3600; + $_ENV['access.token.refresh.jitter'] = 0; + + $tokens = $this->getTokensFromAuthCode($client_id, $client_secret); + foreach ($this->refreshTokens($tokens, 2, $client_id, $client_secret) as $json) { + $this->assertEquals(3600, $json->expires_in); + } + } finally { + unset($_ENV['access.token.lifetime'], $_ENV['access.token.refresh.jitter']); + } + } + /** * test refresh token replay attack * @throws Exception diff --git a/tests/StubServerConfigurationService.php b/tests/StubServerConfigurationService.php index d5a808c7d..ce10af156 100644 --- a/tests/StubServerConfigurationService.php +++ b/tests/StubServerConfigurationService.php @@ -24,6 +24,10 @@ public function getConfigValue($value) return intval($_ENV['access.token.lifetime']); } + if ($value === 'OAuth2.AccessToken.RefreshJitter' && isset($_ENV['access.token.refresh.jitter'])) { + return intval($_ENV['access.token.refresh.jitter']); + } + if ($value === 'OAuth2.IdToken.Lifetime' && isset($_ENV['id.token.lifetime'])) { return intval($_ENV['id.token.lifetime']); } From 284d4d5ab5ade1899b85932a0e06715751838b85 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 8 Oct 2026 11:25:00 -0300 Subject: [PATCH 2/5] test(oauth2): assert refreshed token ttl right after each refresh The redis ttl starts counting down as soon as the access token is stored, so reading it only after all five refreshes made the 5s lower bound depend on how long the remaining refreshes took. Run the DB lifetime and ttl checks in a per-refresh callback instead. --- tests/OAuth2ProtocolTest.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/OAuth2ProtocolTest.php b/tests/OAuth2ProtocolTest.php index 0c45b574e..38fd49887 100644 --- a/tests/OAuth2ProtocolTest.php +++ b/tests/OAuth2ProtocolTest.php @@ -734,9 +734,11 @@ private function getTokensFromAuthCode(string $client_id, string $client_secret) /** * Refreshes $count times, each time with the newest refresh token (they rotate). + * $after_each runs with the decoded response right after every refresh, while its access token is + * still the newest one (rotating the refresh token invalidates the previous access token). * @return array list of decoded refresh responses */ - private function refreshTokens(object $tokens, int $count, string $client_id, string $client_secret): array + private function refreshTokens(object $tokens, int $count, string $client_id, string $client_secret, ?callable $after_each = null): array { $res = []; $refresh_token = $tokens->refresh_token; @@ -750,6 +752,9 @@ private function refreshTokens(object $tokens, int $count, string $client_id, st $json = json_decode($response->getContent()); $refresh_token = $json->refresh_token; $res[] = $json; + if (!is_null($after_each)) { + $after_each($json); + } } return $res; } @@ -773,12 +778,12 @@ public function testRefreshTokenJitterBounds() // authorization code grant is never jittered $this->assertEquals($lifetime, $tokens->expires_in); - $refreshed = $this->refreshTokens($tokens, 5, $client_id, $client_secret); - $access_token_repository = app(\OAuth2\Repositories\IAccessTokenRepository::class); $cache_service = app(UtilsServiceCatalog::CacheService); $values = []; - foreach ($refreshed as $json) { + // checked right after each refresh: the redis ttl starts counting down as soon as the + // token is stored, so reading it after the remaining refreshes would drift out of bounds + $this->refreshTokens($tokens, 5, $client_id, $client_secret, function (object $json) use ($lifetime, $jitter, $access_token_repository, $cache_service, &$values) { $expires_in = $json->expires_in; $values[] = $expires_in; $this->assertGreaterThanOrEqual($lifetime - $jitter, $expires_in); @@ -792,7 +797,7 @@ public function testRefreshTokenJitterBounds() $ttl = $cache_service->ttl($hashed); $this->assertLessThanOrEqual($expires_in, $ttl); $this->assertGreaterThanOrEqual($expires_in - 5, $ttl); - } + }); $this->assertGreaterThan(1, count(array_unique($values)), 'refresh lifetimes are not jittered'); } finally { unset($_ENV['access.token.lifetime'], $_ENV['access.token.refresh.jitter']); From ea4c4bf4ed0e6e07342dfe478f1e445de1c98965 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 8 Oct 2026 11:29:17 -0300 Subject: [PATCH 3/5] fix(oauth2): load access tokens with their stored lifetime TokenService::getAccessToken rebuilt the token with the configured OAuth2.AccessToken.Lifetime instead of the lifetime it was issued with, which is already present on the redis hash and on DB. Access tokens issued by the refresh_token grant now carry a jittered lifetime, so introspection reported a remaining lifetime up to the jitter longer than the real one and resource servers that cache the introspection result (summit-api) kept accepting the token after the IDP had expired it. Use the stored lifetime when reloading the token and assert on the test that introspection expires_in matches the refresh response. --- app/Services/OAuth2/TokenService.php | 6 +++++- tests/OAuth2ProtocolTest.php | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/Services/OAuth2/TokenService.php b/app/Services/OAuth2/TokenService.php index d99067c1e..2fed73c44 100644 --- a/app/Services/OAuth2/TokenService.php +++ b/app/Services/OAuth2/TokenService.php @@ -837,6 +837,10 @@ public function getAccessToken($value, $is_hashed = false) 'refresh_token' ]); + // the lifetime this token was issued with (jittered on refresh); it must win over the + // configured one so the remaining lifetime reported matches the DB value and the redis ttl + $access_token_lifetime = intval($payload['lifetime']); + // reload auth code ... $payload['value'] = $payload['auth_code']; @@ -854,7 +858,7 @@ public function getAccessToken($value, $is_hashed = false) $value, $auth_code, $payload['issued'], - $this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime'), + $access_token_lifetime, ); $refresh_token_value = $payload['refresh_token']; diff --git a/tests/OAuth2ProtocolTest.php b/tests/OAuth2ProtocolTest.php index 38fd49887..bedfeb084 100644 --- a/tests/OAuth2ProtocolTest.php +++ b/tests/OAuth2ProtocolTest.php @@ -759,6 +759,19 @@ private function refreshTokens(object $tokens, int $count, string $client_id, st return $res; } + /** + * @return object decoded introspection response for $access_token + */ + private function introspect(string $access_token, string $client_id, string $client_secret): object + { + $response = $this->action("POST", "OAuth2\OAuth2ProviderController@introspection", [ + 'token' => $access_token, + ], [], [], [], + array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret))); + $this->assertResponseStatus(200); + return json_decode($response->getContent()); + } + /** * refresh grant lifetime is lifetime - random_int(0, jitter); other grants stay unjittered * @throws Exception @@ -783,7 +796,7 @@ public function testRefreshTokenJitterBounds() $values = []; // checked right after each refresh: the redis ttl starts counting down as soon as the // token is stored, so reading it after the remaining refreshes would drift out of bounds - $this->refreshTokens($tokens, 5, $client_id, $client_secret, function (object $json) use ($lifetime, $jitter, $access_token_repository, $cache_service, &$values) { + $this->refreshTokens($tokens, 5, $client_id, $client_secret, function (object $json) use ($lifetime, $jitter, $client_id, $client_secret, $access_token_repository, $cache_service, &$values) { $expires_in = $json->expires_in; $values[] = $expires_in; $this->assertGreaterThanOrEqual($lifetime - $jitter, $expires_in); @@ -797,6 +810,11 @@ public function testRefreshTokenJitterBounds() $ttl = $cache_service->ttl($hashed); $this->assertLessThanOrEqual($expires_in, $ttl); $this->assertGreaterThanOrEqual($expires_in - 5, $ttl); + + // introspection must report the jittered lifetime, not the configured one + $introspection = $this->introspect($json->access_token, $client_id, $client_secret); + $this->assertLessThanOrEqual($expires_in, $introspection->expires_in); + $this->assertGreaterThanOrEqual($expires_in - 5, $introspection->expires_in); }); $this->assertGreaterThan(1, count(array_unique($values)), 'refresh lifetimes are not jittered'); } finally { From 4d6d878df3cead6f12c45b24ffd09b4098d686f2 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 8 Oct 2026 11:53:55 -0300 Subject: [PATCH 4/5] test(oauth2): cover the refresh jitter clamp at the 60s floor With a configured lifetime 40s above MinRefreshedAccessTokenLifetime and a 720s jitter the issued lifetime stays within [60, lifetime]; with the lifetime at the floor the jitter is disabled and the lifetime is issued unchanged. --- tests/OAuth2ProtocolTest.php | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/OAuth2ProtocolTest.php b/tests/OAuth2ProtocolTest.php index bedfeb084..94f711505 100644 --- a/tests/OAuth2ProtocolTest.php +++ b/tests/OAuth2ProtocolTest.php @@ -822,6 +822,43 @@ public function testRefreshTokenJitterBounds() } } + /** + * jitter larger than lifetime - 60 is clamped so the issued lifetime never drops below 60s, + * and a lifetime at the floor is issued unchanged + * @throws Exception + */ + public function testRefreshTokenJitterClampedToMinLifetime() + { + $client_id = '.-_~87D8/Vcvr6fvQbH4HyNgwTlfSyQ3x.openstack.client'; + $client_secret = 'ITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhgITc/6Y5N7kOtGKhg'; + $min_lifetime = \Services\OAuth2\TokenService::MinRefreshedAccessTokenLifetime; + + try { + // headroom above the floor is 40s, jitter asks for 720s + $lifetime = $min_lifetime + 40; + $_ENV['access.token.lifetime'] = $lifetime; + $_ENV['access.token.refresh.jitter'] = 720; + + $tokens = $this->getTokensFromAuthCode($client_id, $client_secret); + $refreshed = $this->refreshTokens($tokens, 5, $client_id, $client_secret); + foreach ($refreshed as $json) { + $this->assertGreaterThanOrEqual($min_lifetime, $json->expires_in); + $this->assertLessThanOrEqual($lifetime, $json->expires_in); + } + + // no headroom at all: the jitter is disabled and the lifetime is issued as configured. + // keep refreshing the same chain: a new auth code would not get a refresh token + // (consent was already given), the refresh grant rotates it. + $_ENV['access.token.lifetime'] = $min_lifetime; + + foreach ($this->refreshTokens(end($refreshed), 2, $client_id, $client_secret) as $json) { + $this->assertEquals($min_lifetime, $json->expires_in); + } + } finally { + unset($_ENV['access.token.lifetime'], $_ENV['access.token.refresh.jitter']); + } + } + /** * jitter = 0 keeps the current behaviour * @throws Exception From 73c3c0db78714db5e7ac6aa533af71ce4a9622b4 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 8 Oct 2026 12:17:26 -0300 Subject: [PATCH 5/5] fix(oauth2): raise the refreshed access token lifetime floor to 300s openstack-uicore-foundation subtracts a 60s skew from expires_in before deciding to refresh, so a jittered lifetime at the previous 60s floor would have made summit-admin and event-site refresh on every API call. Keep the floor well above that skew; the clamp test follows the constant. --- app/Services/OAuth2/TokenService.php | 7 +++++-- tests/OAuth2ProtocolTest.php | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/Services/OAuth2/TokenService.php b/app/Services/OAuth2/TokenService.php index 2fed73c44..cbdd79233 100644 --- a/app/Services/OAuth2/TokenService.php +++ b/app/Services/OAuth2/TokenService.php @@ -107,9 +107,12 @@ final class TokenService extends AbstractService implements ITokenService const ClientAccessTokensQtyLifetime = 86400; /** - * floor (seconds) for the jittered lifetime of access tokens issued on refresh + * floor (seconds) for the jittered lifetime of access tokens issued on refresh. + * Kept well above the 60s skew the js clients (openstack-uicore-foundation) subtract from + * expires_in before refreshing: a lifetime at or near that skew would make them refresh on + * every request. */ - const MinRefreshedAccessTokenLifetime = 60; + const MinRefreshedAccessTokenLifetime = 300; const ClientRefreshTokensQty = '.rtokens.qty'; const ClientRefreshTokensQtyLifetime = 86400; diff --git a/tests/OAuth2ProtocolTest.php b/tests/OAuth2ProtocolTest.php index 94f711505..f22b341e1 100644 --- a/tests/OAuth2ProtocolTest.php +++ b/tests/OAuth2ProtocolTest.php @@ -823,8 +823,8 @@ public function testRefreshTokenJitterBounds() } /** - * jitter larger than lifetime - 60 is clamped so the issued lifetime never drops below 60s, - * and a lifetime at the floor is issued unchanged + * jitter larger than lifetime - MinRefreshedAccessTokenLifetime is clamped so the issued + * lifetime never drops below the floor, and a lifetime at the floor is issued unchanged * @throws Exception */ public function testRefreshTokenJitterClampedToMinLifetime()