-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support token revocation and introspection #1473
base: master
Are you sure you want to change the base?
Support token revocation and introspection #1473
Conversation
@Sephster , as mentioned #1453 (comment), it would be great to have more maintainers for this repository! @hafezdivandari is an excellent professional with extensive knowledge of OAuth2. Having more people to support the project would certainly help its growth and maintenance! 🚀 |
use EmitterAwarePolyfill; | ||
use CryptTrait; | ||
|
||
protected ClientRepositoryInterface $clientRepository; | ||
|
||
protected AccessTokenRepositoryInterface $accessTokenRepository; | ||
|
||
protected RefreshTokenRepositoryInterface $refreshTokenRepository; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From AbstractGrant
class.
public function setClientRepository(ClientRepositoryInterface $clientRepository): void | ||
{ | ||
$this->clientRepository = $clientRepository; | ||
} | ||
|
||
public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void | ||
{ | ||
$this->accessTokenRepository = $accessTokenRepository; | ||
} | ||
|
||
public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void | ||
{ | ||
$this->refreshTokenRepository = $refreshTokenRepository; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From AbstractGrant
class.
/** | ||
* Validate the client. | ||
* | ||
* @throws OAuthServerException | ||
*/ | ||
protected function validateClient(ServerRequestInterface $request): ClientEntityInterface | ||
{ | ||
[$clientId, $clientSecret] = $this->getClientCredentials($request); | ||
|
||
$client = $this->getClientEntityOrFail($clientId, $request); | ||
|
||
if ($client->isConfidential()) { | ||
if ($clientSecret === '') { | ||
throw OAuthServerException::invalidRequest('client_secret'); | ||
} | ||
|
||
if ( | ||
$this->clientRepository->validateClient( | ||
$clientId, | ||
$clientSecret, | ||
$this instanceof GrantTypeInterface ? $this->getIdentifier() : null | ||
) === false | ||
) { | ||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); | ||
|
||
throw OAuthServerException::invalidClient($request); | ||
} | ||
} | ||
|
||
return $client; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved from AbstractGrant
class.
$this->clientRepository->validateClient( | ||
$clientId, | ||
$clientSecret, | ||
$this instanceof GrantTypeInterface ? $this->getIdentifier() : null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is the only change on this method, since getIdentifier()
method is defined on GrantTypeInterface
only.
/** | ||
* Wrapper around ClientRepository::getClientEntity() that ensures we emit | ||
* an event and throw an exception if the repo doesn't return a client | ||
* entity. | ||
* | ||
* This is a bit of defensive coding because the interface contract | ||
* doesn't actually enforce non-null returns/exception-on-no-client so | ||
* getClientEntity might return null. By contrast, this method will | ||
* always either return a ClientEntityInterface or throw. | ||
* | ||
* @throws OAuthServerException | ||
*/ | ||
protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface | ||
{ | ||
$client = $this->clientRepository->getClientEntity($clientId); | ||
|
||
if ($client instanceof ClientEntityInterface === false) { | ||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); | ||
throw OAuthServerException::invalidClient($request); | ||
} | ||
|
||
return $client; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved from AbstractGrant
class.
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class TokenIntrospectionHandler extends AbstractTokenHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class implements "Token Introspection RFC7662". This class also allows customizing the introspection response by injecting a IntrospectionResponseTypeInterface
instance.
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class TokenRevocationHandler extends AbstractTokenHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class implements "Token Revocation RFC7009".
use DateTimeInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
|
||
class IntrospectionResponse implements IntrospectionResponseTypeInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class implements Introspection Response. RFC7662 section 2.2
|
||
use Psr\Http\Message\ResponseInterface; | ||
|
||
interface IntrospectionResponseTypeInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defines an interface for introspection response type.
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class TokenServer implements EmitterAwareInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class defines 2 methods to respond to token revocation and token introspection requests. Also allows injecting customized token revocation/introspection handlers.
This class is inspired by AuthorizationServer
and ResourceServer
classes.
Fixes #806
Fixes #1350
Closes #1255
Closes #1135
Closes #995
Closes #925
It's much easier to implement Token Revocation RFC7009 and Token Introspection RFC7662 at the same time! These 2 RFCs have most of the logic in common:
"application/x-www-form-urlencoded" data:
token
is required, accepts either an access token or a refresh token.token_type_hint
is optional to help the server optimize the token lookup.access_token
orrefresh_token
.Authorization: Basic {client_credentials}
headerclient_id
andclient_secret
parameters.400
:invalid_request
the requiredtoken
parameter was not sent.401
:invalid_client
the request is not authorized. Client credentials were not sent or invalid.200
: The token is revoked, expired, doesn't exists, or was not issued to the client making the request:active
field set tofalse
.{ "active": false }
200
: The token is valid, active, and was issued to the client making the request:active
field set totrue
.{ "active": true, ... }
Implementation
Summary
Summary of file changes in order of appearance.
New
AbstractHandler
classAbstractGrant
andAbstractTokenHandler
classes.clientRepository
,accessTokenRepository
, andrefreshTokenRepository
properties have been moved to this class from childAbstractGrant
class.setClientRepository
,setAccessTokenRepository
,setRefreshTokenRepository
,getClientCredentials
,parseParam
,getRequestParameter
,getBasicAuthCredentials
,getQueryStringParameter
,getCookieParameter
,getServerParameter
, andvalidateEncryptedRefreshToken
methods have been moved to this class from childAbstractGrant
class without any change.validateClient
method from childAbstractGrant
has been moved to this class with a minor change: The call togetIdentifier()
isGrantTypeInterface
specific.getClientEntityOrFail
method from childAbstractGrant
has been moved to this class. This method is also overridden on childAbstractGrant
: Call tosupportsGrantType
method isAbstracGrant
specific.validateEncryptedRefreshToken
is extracted fromRefreshTokenGrant::validateOldRefreshToken
method.Change
AuthorizationValidators\BearerTokenValidator
classJwtValidatorInterface
interface.validateAuthorization
method into a newvalidateJwt
method.validateJwt
method also accepts optional$clientId
argument. If a$clientId
is provided, it verifies whether the token was issued to the given client.New
AuthorizationValidators\JwtValidatorInterface
interfaceBearerTokenValidator
class.validateJwt
method.AbstractTokenHandler::setJwtValidator()
method.Change
Exception\OAuthServerException
classunsupportedTokenType
method.Change
Grant\AbstractGrant
classAbstractHandler
class.AbstractHandler
class.getClientEntityOrFail
method.Change
Grant\RefreshTokenGrant
classvalidateOldRefreshToken
method has been extracted into a new parent AbstractHandler::validateEncryptedRefreshToken method.New
Handlers\AbstractTokenHandler
classAbstractHandler
class.TokenHandlerInterface
interface.setPublicKey
andsetJwtValidator
methods that to allow injecting a customized JWT validator.validateToken
,validateAccessToken
andvalidateRefreshToken
methods that have been used byTokenIntrospectionHandler
andTokenRevocationHandler
classes.New
Handlers\TokenHandlerInterface
interfaceAbstractTokenHandler
class.setTokenRevocationHandler
andsetTokenIntrospectionHandler
methods of theTokenServer
class.New
Handlers\TokenIntrospectionHandler
classAbstractTokenHandler
class.respondToRequest
method that responds to "Token Introspection RFC7662" request.setResponseType
method that allows injecting a customized response type.New
Handlers\TokenRevocationHandler
classAbstractTokenHandler
class.respondToRequest
method that responds to "Token Revocation RFC7009" request.New
ResponseTypes\IntrospectionResponse
classIntrospectionResponseTypeInterface
interface.generateHttpResponse
method to generate "Token Revocation RFC7009" response.New
ResponseTypes\IntrospectionResponseTypeInterface
classIntrospectionResponse
class.TokenIntrospectionHandler::setResponseType
method that allows injecting a customized response type.New
TokenServer
classrespondToTokenRevocationRequest
andrespondToTokenIntrospectionRequest
methods to respond to the respective request.setTokenRevocationHandler
andsetTokenIntrospectionHandler
methods that allows injecting customized handlers.