Skip to content

Commit

Permalink
Merge branch 'feature/refresh-token-rotation' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbilbie committed May 10, 2013
2 parents 8c40196 + fdb89fb commit e591fbb
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 17 deletions.
56 changes: 41 additions & 15 deletions src/League/OAuth2/Server/Grant/RefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ class RefreshToken implements GrantTypeInterface {
*/
protected $refreshTokenTTL = 604800;

/**
* Rotate refresh tokens
* @var boolean
*/
protected $rotateRefreshTokens = false;

/**
* Constructor
* @param Authorization $authServer Authorization server instance
Expand Down Expand Up @@ -111,6 +117,16 @@ public function getRefreshTokenTTL()
return $this->refreshTokenTTL;
}

/**
* When a new access is token, expire the refresh token used and issue a new one.
* @param boolean $rotateRefreshTokens Set to true to enable (default = false)
* @return void
*/
public function rotateRefreshTokens($rotateRefreshTokens = false)
{
$this->rotateRefreshTokens = $rotateRefreshTokens;
}

/**
* Complete the refresh token grant
* @param null|array $inputParams
Expand Down Expand Up @@ -160,24 +176,32 @@ public function completeFlow($inputParams = null)
$accessTokenExpiresIn = ($this->accessTokenTTL !== null) ? $this->accessTokenTTL : $this->authServer->getAccessTokenTTL();
$accessTokenExpires = time() + $accessTokenExpiresIn;

// Generate a new refresh token
$refreshToken = SecureKey::make();
$refreshTokenExpires = time() + $this->getRefreshTokenTTL();

// Revoke the old refresh token
$this->authServer->getStorage('session')->removeRefreshToken($authParams['refresh_token']);

// Associate the new access token with the session
$newAccessTokenId = $this->authServer->getStorage('session')->associateAccessToken($accessTokenDetails['session_id'], $accessToken, $accessTokenExpires);

// There isn't a request for reduced scopes so assign the original ones
if ($this->rotateRefreshTokens === true) {

// Generate a new refresh token
$refreshToken = SecureKey::make();
$refreshTokenExpires = time() + $this->getRefreshTokenTTL();

// Revoke the old refresh token
$this->authServer->getStorage('session')->removeRefreshToken($authParams['refresh_token']);

// Associate the new refresh token with the new access token
$this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']);
}

// There isn't a request for reduced scopes so assign the original ones (or we're not rotating scopes)
if ( ! isset($authParams['scope'])) {

foreach ($scopes as $scope) {
$this->authServer->getStorage('session')->associateScope($newAccessTokenId, $scope['id']);
}
} else {

// The request is asking for reduced scopes
} elseif ( isset($authParams['scope']) && $this->rotateRefreshTokens === true) {

// The request is asking for reduced scopes and rotate tokens is enabled
$reqestedScopes = explode($this->authServer->getScopeDelimeter(), $authParams['scope']);

for ($i = 0; $i < count($reqestedScopes); $i++) {
Expand All @@ -202,16 +226,18 @@ public function completeFlow($inputParams = null)
}
}

// Associate the new refresh token with the new access token
$this->authServer->getStorage('session')->associateRefreshToken($newAccessTokenId, $refreshToken, $refreshTokenExpires, $authParams['client_id']);

return array(
$response = array(
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'token_type' => 'bearer',
'expires' => $accessTokenExpires,
'expires_in' => $accessTokenExpiresIn
);

if ($this->rotateRefreshTokens === true) {
$response['refresh_token'] = $refreshToken;
}

return $response;
}

}
46 changes: 44 additions & 2 deletions tests/authorization/RefreshTokenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ public function test_issueAccessToken_refreshTokenGrant_passedInput()
$this->assertArrayHasKey('token_type', $v);
$this->assertArrayHasKey('expires', $v);
$this->assertArrayHasKey('expires_in', $v);
$this->assertArrayHasKey('refresh_token', $v);

$this->assertEquals($a->getAccessTokenTTL(), $v['expires_in']);
$this->assertEquals(time()+$a->getAccessTokenTTL(), $v['expires']);
Expand Down Expand Up @@ -240,6 +239,48 @@ public function test_issueAccessToken_refreshTokenGrant()
'refresh_token' => 'abcdef',
));

$this->assertArrayHasKey('access_token', $v);
$this->assertArrayHasKey('token_type', $v);
$this->assertArrayHasKey('expires', $v);
$this->assertArrayHasKey('expires_in', $v);

$this->assertEquals($a->getAccessTokenTTL(), $v['expires_in']);
$this->assertEquals(time()+$a->getAccessTokenTTL(), $v['expires']);
}

public function test_issueAccessToken_refreshTokenGrant_rotateTokens()
{
$this->client->shouldReceive('getClient')->andReturn(array(
'client_id' => 1234,
'client_secret' => 5678,
'redirect_uri' => 'http://foo/redirect',
'name' => 'Example Client'
));

$this->session->shouldReceive('validateRefreshToken')->andReturn(1);
$this->session->shouldReceive('validateAuthCode')->andReturn(1);
$this->session->shouldReceive('updateSession')->andReturn(null);
$this->session->shouldReceive('updateRefreshToken')->andReturn(null);
$this->session->shouldReceive('getAccessToken')->andReturn(null);
$this->session->shouldReceive('getScopes')->andReturn(array('id' => 1));
$this->session->shouldReceive('associateAccessToken')->andReturn(1);
$this->session->shouldReceive('associateRefreshToken')->andReturn(1);
$this->session->shouldReceive('removeRefreshToken')->andReturn(1);
$this->session->shouldReceive('associateScope')->andReturn(null);

$a = $this->returnDefault();

$rt = new League\OAuth2\Server\Grant\RefreshToken($a);
$rt->rotateRefreshTokens(true);
$a->addGrantType($rt);

$v = $a->issueAccessToken(array(
'grant_type' => 'refresh_token',
'client_id' => 1234,
'client_secret' => 5678,
'refresh_token' => 'abcdef',
));

$this->assertArrayHasKey('access_token', $v);
$this->assertArrayHasKey('token_type', $v);
$this->assertArrayHasKey('expires', $v);
Expand Down Expand Up @@ -286,7 +327,6 @@ public function test_issueAccessToken_refreshTokenGrant_customExpiresIn()
$this->assertArrayHasKey('token_type', $v);
$this->assertArrayHasKey('expires', $v);
$this->assertArrayHasKey('expires_in', $v);
$this->assertArrayHasKey('refresh_token', $v);

$this->assertNotEquals($a->getAccessTokenTTL(), $v['expires_in']);
$this->assertNotEquals(time()+$a->getAccessTokenTTL(), $v['expires']);
Expand Down Expand Up @@ -318,6 +358,7 @@ public function test_issueAccessToken_refreshTokenGrant_newScopes()
$a = $this->returnDefault();
$grant = new League\OAuth2\Server\Grant\RefreshToken($a);
$grant->setAccessTokenTTL(30);
$grant->rotateRefreshTokens(true);
$a->addGrantType($grant);

$v = $a->issueAccessToken(array(
Expand Down Expand Up @@ -368,6 +409,7 @@ public function test_issueAccessToken_refreshTokenGrant_badNewScopes()
$a = $this->returnDefault();
$grant = new League\OAuth2\Server\Grant\RefreshToken($a);
$grant->setAccessTokenTTL(30);
$grant->rotateRefreshTokens(true);
$a->addGrantType($grant);

$a->issueAccessToken(array(
Expand Down

0 comments on commit e591fbb

Please sign in to comment.