Merge pull request #874 from lookyman/access-token-jwt

Generalized access token format
This commit is contained in:
Andrew Millington 2018-05-24 11:00:29 +01:00 committed by GitHub
commit ca6be0577c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 58 additions and 15 deletions

View File

@ -30,3 +30,4 @@ after_script:
branches: branches:
only: only:
- master - master
- 8.0.0

View File

@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased] ## [Unreleased]
### Changed ### Changed
- Replace `convertToJWT()` interface with a more generic `__toString()` to improve extensibility (PR #874)
- The `invalidClient()` function accepts a PSR-7 compliant `$serverRequest` argument to avoid accessing the `$_SERVER` global variable and improve testing (PR #899) - The `invalidClient()` function accepts a PSR-7 compliant `$serverRequest` argument to avoid accessing the `$_SERVER` global variable and improve testing (PR #899)
## [7.1.1] - released 2018-05-21 ## [7.1.1] - released 2018-05-21

View File

@ -9,17 +9,17 @@
namespace League\OAuth2\Server\Entities; namespace League\OAuth2\Server\Entities;
use Lcobucci\JWT\Token;
use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptKey;
interface AccessTokenEntityInterface extends TokenInterface interface AccessTokenEntityInterface extends TokenInterface
{ {
/** /**
* Generate a JWT from the access token * Set a private key used to encrypt the access token.
*
* @param CryptKey $privateKey
*
* @return Token
*/ */
public function convertToJWT(CryptKey $privateKey); public function setPrivateKey(CryptKey $privateKey);
/**
* Generate a string representation of the access token.
*/
public function __toString();
} }

View File

@ -19,6 +19,19 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface;
trait AccessTokenTrait trait AccessTokenTrait
{ {
/**
* @var CryptKey
*/
private $privateKey;
/**
* Set the private key used to encrypt this access token.
*/
public function setPrivateKey(CryptKey $privateKey)
{
$this->privateKey = $privateKey;
}
/** /**
* Generate a JWT from the access token * Generate a JWT from the access token
* *
@ -26,7 +39,7 @@ trait AccessTokenTrait
* *
* @return Token * @return Token
*/ */
public function convertToJWT(CryptKey $privateKey) private function convertToJWT(CryptKey $privateKey)
{ {
return (new Builder()) return (new Builder())
->setAudience($this->getClient()->getIdentifier()) ->setAudience($this->getClient()->getIdentifier())
@ -40,6 +53,14 @@ trait AccessTokenTrait
->getToken(); ->getToken();
} }
/**
* Generate a string representation from the access token
*/
public function __toString()
{
return (string) $this->convertToJWT($this->privateKey);
}
/** /**
* @return ClientEntityInterface * @return ClientEntityInterface
*/ */

View File

@ -361,6 +361,7 @@ abstract class AbstractGrant implements GrantTypeInterface
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setUserIdentifier($userIdentifier); $accessToken->setUserIdentifier($userIdentifier);
$accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL)); $accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL));
$accessToken->setPrivateKey($this->privateKey);
foreach ($scopes as $scope) { foreach ($scopes as $scope) {
$accessToken->addScope($scope); $accessToken->addScope($scope);

View File

@ -216,7 +216,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$this->makeRedirectUri( $this->makeRedirectUri(
$finalRedirectUri, $finalRedirectUri,
[ [
'access_token' => (string) $accessToken->convertToJWT($this->privateKey), 'access_token' => (string) $accessToken,
'token_type' => 'Bearer', 'token_type' => 'Bearer',
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(), 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(),
'state' => $authorizationRequest->getState(), 'state' => $authorizationRequest->getState(),

View File

@ -24,12 +24,10 @@ class BearerTokenResponse extends AbstractResponseType
{ {
$expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp(); $expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp();
$jwtAccessToken = $this->accessToken->convertToJWT($this->privateKey);
$responseParams = [ $responseParams = [
'token_type' => 'Bearer', 'token_type' => 'Bearer',
'expires_in' => $expireDateTime - (new \DateTime())->getTimestamp(), 'expires_in' => $expireDateTime - (new \DateTime())->getTimestamp(),
'access_token' => (string) $jwtAccessToken, 'access_token' => (string) $this->accessToken,
]; ];
if ($this->refreshToken instanceof RefreshTokenEntityInterface) { if ($this->refreshToken instanceof RefreshTokenEntityInterface) {

View File

@ -3,6 +3,7 @@
namespace LeagueTests\Grant; namespace LeagueTests\Grant;
use League\Event\Emitter; use League\Event\Emitter;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
@ -353,6 +354,7 @@ class AbstractGrantTest extends TestCase
/** @var AbstractGrant $grantMock */ /** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grantMock->setAccessTokenRepository($accessTokenRepoMock); $grantMock->setAccessTokenRepository($accessTokenRepoMock);
$abstractGrantReflection = new \ReflectionClass($grantMock); $abstractGrantReflection = new \ReflectionClass($grantMock);

View File

@ -2,6 +2,7 @@
namespace LeagueTests\Grant; namespace LeagueTests\Grant;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
@ -40,7 +41,7 @@ class AuthCodeGrantTest extends TestCase
public function setUp() public function setUp()
{ {
$this->cryptStub = new CryptTraitStub; $this->cryptStub = new CryptTraitStub();
} }
public function testGetIdentifier() public function testGetIdentifier()
@ -608,6 +609,7 @@ class AuthCodeGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$request = new ServerRequest( $request = new ServerRequest(
[], [],
@ -676,6 +678,7 @@ class AuthCodeGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$request = new ServerRequest( $request = new ServerRequest(
[], [],
@ -747,6 +750,7 @@ class AuthCodeGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$request = new ServerRequest( $request = new ServerRequest(
[], [],
@ -1537,6 +1541,7 @@ class AuthCodeGrantTest extends TestCase
new \DateInterval('PT10M') new \DateInterval('PT10M')
); );
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
} }
@ -1624,6 +1629,7 @@ class AuthCodeGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$request = new ServerRequest( $request = new ServerRequest(
[], [],
@ -1695,6 +1701,7 @@ class AuthCodeGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$request = new ServerRequest( $request = new ServerRequest(
[], [],
@ -1766,6 +1773,7 @@ class AuthCodeGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$request = new ServerRequest( $request = new ServerRequest(
[], [],

View File

@ -2,6 +2,7 @@
namespace LeagueTests\Grant; namespace LeagueTests\Grant;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Grant\ClientCredentialsGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
@ -44,6 +45,7 @@ class ClientCredentialsGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest(); $serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody( $serverRequest = $serverRequest->withParsedBody(

View File

@ -2,6 +2,7 @@
namespace LeagueTests\Grant; namespace LeagueTests\Grant;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Grant\PasswordGrant; use League\OAuth2\Server\Grant\PasswordGrant;
@ -60,6 +61,7 @@ class PasswordGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest(); $serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody( $serverRequest = $serverRequest->withParsedBody(

View File

@ -29,8 +29,9 @@ class ResourceServerMiddlewareTest extends TestCase
$accessToken->setUserIdentifier(123); $accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $token = (string) $accessToken;
$request = new ServerRequest(); $request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $token)); $request = $request->withHeader('authorization', sprintf('Bearer %s', $token));
@ -64,8 +65,9 @@ class ResourceServerMiddlewareTest extends TestCase
$accessToken->setUserIdentifier(123); $accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $token = (string) $accessToken;
$request = new ServerRequest(); $request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $token)); $request = $request->withHeader('authorization', sprintf('Bearer %s', $token));

View File

@ -35,6 +35,7 @@ class BearerResponseTypeTest extends TestCase
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->addScope($scope); $accessToken->addScope($scope);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity(); $refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef'); $refreshToken->setIdentifier('abcdef');
@ -77,6 +78,7 @@ class BearerResponseTypeTest extends TestCase
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->addScope($scope); $accessToken->addScope($scope);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity(); $refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef'); $refreshToken->setIdentifier('abcdef');
@ -119,6 +121,7 @@ class BearerResponseTypeTest extends TestCase
$accessToken->setUserIdentifier(123); $accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity(); $refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef'); $refreshToken->setIdentifier('abcdef');
@ -164,6 +167,7 @@ class BearerResponseTypeTest extends TestCase
$accessToken->setUserIdentifier(123); $accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity(); $refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef'); $refreshToken->setIdentifier('abcdef');
@ -206,6 +210,7 @@ class BearerResponseTypeTest extends TestCase
$accessToken->setUserIdentifier(123); $accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity(); $refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef'); $refreshToken->setIdentifier('abcdef');