-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Support token revocation and introspection #1473
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
base: master
Are you sure you want to change the base?
Changes from 11 commits
fbc0289
704a3cf
9555b91
3482d6f
68eded0
f52f03d
09d284b
70bf835
3c574f2
bed1164
a3f0080
4a91753
f1d69be
b11ee9b
8fe3382
c10eb55
d0375df
c7702a9
f108e2f
3634409
e45c21b
5d2e571
82a49d6
b2f8661
2915076
dcd57ed
803978c
d84b6df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,282 @@ | ||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| declare(strict_types=1); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| namespace League\OAuth2\Server; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| use Exception; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\Entities\ClientEntityInterface; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\EventEmitting\EmitterAwareInterface; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\Exception\OAuthServerException; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\Grant\GrantTypeInterface; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\Repositories\ClientRepositoryInterface; | ||||||||||||||||||||||||||||||||||||||||||
| use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; | ||||||||||||||||||||||||||||||||||||||||||
| use Psr\Http\Message\ServerRequestInterface; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| use function base64_decode; | ||||||||||||||||||||||||||||||||||||||||||
| use function explode; | ||||||||||||||||||||||||||||||||||||||||||
| use function json_decode; | ||||||||||||||||||||||||||||||||||||||||||
| use function substr; | ||||||||||||||||||||||||||||||||||||||||||
| use function time; | ||||||||||||||||||||||||||||||||||||||||||
| use function trim; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| abstract class AbstractHandler implements EmitterAwareInterface | ||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been racking my brains trying to think of a better name for this handler. I understand the need to have a common base for the TokenHandler and the AbstractGrant, but the name AbstractHandler feels too vague to me and I worry it will cause issues later down the line. Having a look at the methods in this class, nearly all of them are related to the request e.g. validating the client, extracting params in various scenarios etc but the one that doesn't quite fit this is the validation of the encrypted refresh token. Can you think of any name that is more appropriate? Its clear what should go in the Abstract Grant and Abstract Token but I don't think it will be clear at first glance what a Handler is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you noted, this class is extended by both |
||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| use EmitterAwarePolyfill; | ||||||||||||||||||||||||||||||||||||||||||
| use CryptTrait; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| protected ClientRepositoryInterface $clientRepository; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| protected AccessTokenRepositoryInterface $accessTokenRepository; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| protected RefreshTokenRepositoryInterface $refreshTokenRepository; | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * 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 | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
| ) === false | ||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||
| $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidClient($request); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return $client; | ||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old validateClient simply returned a bool. I think this is slightly cleaner as this function is validating and retrieving the client entity. I think it is better when functions have one specific purpose. Is there a reason for this change and is it feasible to change back?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There’s actually no change in the return value of this method, it wasn’t returning a boolean before either. You may check this: oauth2-server/src/Grant/AbstractGrant.php Lines 150 to 169 in 9002f01
|
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * 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; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Gets the client credentials from the request from the request body or | ||||||||||||||||||||||||||||||||||||||||||
| * the Http Basic Authorization header | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return array{0:non-empty-string,1:string} | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function getClientCredentials(ServerRequestInterface $request): array | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($clientId === null) { | ||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidRequest('client_id'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return [$clientId, $clientSecret ?? '']; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Parse request parameter. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @param array<array-key, mixed> $request | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return non-empty-string|null | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| private static function parseParam(string $parameter, array $request, ?string $default = null): ?string | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| $value = $request[$parameter] ?? ''; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (is_scalar($value)) { | ||||||||||||||||||||||||||||||||||||||||||
| $value = trim((string) $value); | ||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidRequest($parameter); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($value === '') { | ||||||||||||||||||||||||||||||||||||||||||
| $value = $default === null ? null : trim($default); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($value === '') { | ||||||||||||||||||||||||||||||||||||||||||
| $value = null; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return $value; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Retrieve request parameter. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return non-empty-string|null | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function getRequestParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| return self::parseParam($parameter, (array) $request->getParsedBody(), $default); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Retrieve HTTP Basic Auth credentials with the Authorization header | ||||||||||||||||||||||||||||||||||||||||||
| * of a request. First index of the returned array is the username, | ||||||||||||||||||||||||||||||||||||||||||
| * second is the password (so list() will work). If the header does | ||||||||||||||||||||||||||||||||||||||||||
| * not exist, or is otherwise an invalid HTTP Basic header, return | ||||||||||||||||||||||||||||||||||||||||||
| * [null, null]. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return array{0:non-empty-string,1:string}|array{0:null,1:null} | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function getBasicAuthCredentials(ServerRequestInterface $request): array | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| if (!$request->hasHeader('Authorization')) { | ||||||||||||||||||||||||||||||||||||||||||
| return [null, null]; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $header = $request->getHeader('Authorization')[0]; | ||||||||||||||||||||||||||||||||||||||||||
| if (stripos($header, 'Basic ') !== 0) { | ||||||||||||||||||||||||||||||||||||||||||
| return [null, null]; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $decoded = base64_decode(substr($header, 6), true); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($decoded === false) { | ||||||||||||||||||||||||||||||||||||||||||
| return [null, null]; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (str_contains($decoded, ':') === false) { | ||||||||||||||||||||||||||||||||||||||||||
| return [null, null]; // HTTP Basic header without colon isn't valid | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| [$username, $password] = explode(':', $decoded, 2); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($username === '') { | ||||||||||||||||||||||||||||||||||||||||||
| return [null, null]; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return [$username, $password]; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Retrieve query string parameter. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return non-empty-string|null | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function getQueryStringParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| return self::parseParam($parameter, $request->getQueryParams(), $default); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Retrieve cookie parameter. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return non-empty-string|null | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function getCookieParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| return self::parseParam($parameter, $request->getCookieParams(), $default); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Retrieve server parameter. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return non-empty-string|null | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function getServerParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string | ||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||
| return self::parseParam($parameter, $request->getServerParams(), $default); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Validate the given encrypted refresh token. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @throws OAuthServerException | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * @return array<non-empty-string, mixed> | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| protected function validateEncryptedRefreshToken( | ||||||||||||||||||||||||||||||||||||||||||
| ServerRequestInterface $request, | ||||||||||||||||||||||||||||||||||||||||||
| string $encryptedRefreshToken, | ||||||||||||||||||||||||||||||||||||||||||
| string $clientId | ||||||||||||||||||||||||||||||||||||||||||
| ): array { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| $refreshToken = $this->decrypt($encryptedRefreshToken); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception $e) { | ||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| $refreshTokenData = json_decode($refreshToken, true); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($refreshTokenData['client_id'] !== $clientId) { | ||||||||||||||||||||||||||||||||||||||||||
| $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request)); | ||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidRefreshToken('Token is not linked to client'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($refreshTokenData['expire_time'] < time()) { | ||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidRefreshToken('Token has expired'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenData['refresh_token_id']) === true) { | ||||||||||||||||||||||||||||||||||||||||||
| throw OAuthServerException::invalidRefreshToken('Token has been revoked'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return $refreshTokenData; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,7 +34,7 @@ | |
| use function preg_replace; | ||
| use function trim; | ||
|
|
||
| class BearerTokenValidator implements AuthorizationValidatorInterface | ||
| class BearerTokenValidator implements AuthorizationValidatorInterface, JwtValidatorInterface | ||
Sephster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| use CryptTrait; | ||
|
|
||
|
|
@@ -100,6 +100,21 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe | |
| throw OAuthServerException::accessDenied('Missing "Bearer" token'); | ||
| } | ||
|
|
||
| $claims = $this->validateJwt($request, $jwt); | ||
|
|
||
| // Return the request with additional attributes | ||
| return $request | ||
| ->withAttribute('oauth_access_token_id', $claims['jti'] ?? null) | ||
hafezdivandari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ->withAttribute('oauth_client_id', $claims['aud'][0] ?? null) | ||
| ->withAttribute('oauth_user_id', $claims['sub'] ?? null) | ||
| ->withAttribute('oauth_scopes', $claims['scopes'] ?? null); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| public function validateJwt(ServerRequestInterface $request, string $jwt, ?string $clientId = null): array | ||
| { | ||
Sephster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try { | ||
| // Attempt to parse the JWT | ||
| $token = $this->jwtConfiguration->parser()->parse($jwt); | ||
|
|
@@ -121,16 +136,20 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe | |
|
|
||
| $claims = $token->claims(); | ||
|
|
||
| // Check if token is linked to the client | ||
| if ( | ||
| $clientId !== null && | ||
| $claims->get('client_id') !== $clientId && | ||
| !$token->isPermittedFor($clientId) | ||
hafezdivandari marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) { | ||
| throw OAuthServerException::accessDenied('Access token is not linked to client'); | ||
| } | ||
|
|
||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Check if token has been revoked | ||
| if ($this->accessTokenRepository->isAccessTokenRevoked($claims->get('jti'))) { | ||
| throw OAuthServerException::accessDenied('Access token has been revoked'); | ||
| } | ||
|
|
||
| // Return the request with additional attributes | ||
| return $request | ||
| ->withAttribute('oauth_access_token_id', $claims->get('jti')) | ||
| ->withAttribute('oauth_client_id', $claims->get('aud')[0]) | ||
| ->withAttribute('oauth_user_id', $claims->get('sub')) | ||
| ->withAttribute('oauth_scopes', $claims->get('scopes')); | ||
| return $claims->all(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we just pass the claims back here instead of claims all? We could then use the get method as we did previously
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace League\OAuth2\Server\AuthorizationValidators; | ||
|
|
||
| use Psr\Http\Message\ServerRequestInterface; | ||
|
|
||
| interface JwtValidatorInterface | ||
| { | ||
| /** | ||
| * Parse and validate the given JWT. | ||
| * | ||
| * @param non-empty-string $jwt | ||
| * @param non-empty-string|null $clientId | ||
| * | ||
| * @return array<non-empty-string, mixed> | ||
| */ | ||
| public function validateJwt(ServerRequestInterface $request, string $jwt, ?string $clientId = null): array; | ||
Sephster marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -265,6 +265,20 @@ public static function unauthorizedClient(?string $hint = null): static | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Unsupported Token Type error. | ||
| */ | ||
| public static function unsupportedTokenType(?string $hint = null): static | ||
hafezdivandari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| return new static( | ||
| 'The authorization server does not support the revocation of the presented token type.', | ||
Sephster marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 15, | ||
| 'unsupported_token_type', | ||
| 400, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way to handle the 503 response where the client may try again later? I don't think we are covering this scenario at the moment
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the moment, tokens are revoked immediately, so we never return a 503 response in this implementation. Given that, this scenario does not currently occur and handling retries for 503 does not seem necessary right now. |
||
| $hint | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Generate a HTTP response. | ||
| */ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.