diff --git a/src/Exception/TokenDecoderException.php b/src/Exception/TokenDecoderException.php index 99c05be..87d8759 100644 --- a/src/Exception/TokenDecoderException.php +++ b/src/Exception/TokenDecoderException.php @@ -12,4 +12,24 @@ public function __construct(string $string, \Exception $e) { parent::__construct($string, self::CODE, $e); } + + public static function forSignatureValidationFailure(\Exception $e): self + { + return new self('Signature validation failed', $e); + } + + public static function forExpiration(\Exception $e): self + { + return new self('Token has expired', $e); + } + + public static function forIssuerMismatch(\Exception $e): self + { + return new self('Issuer mismatch', $e); + } + + public static function forAudienceMismatch(\Exception $e): self + { + return new self('Audience mismatch', $e); + } } diff --git a/src/Interface/TokenDecoderInterface.php b/src/Interface/TokenDecoderInterface.php index de18ae7..1463f8b 100644 --- a/src/Interface/TokenDecoderInterface.php +++ b/src/Interface/TokenDecoderInterface.php @@ -10,4 +10,10 @@ interface TokenDecoderInterface * @return array */ public function decode(string $token, string $key): array; + + /** + * @param string $realm + * @param array $tokenDecoded + */ + public function validateToken(string $realm, array $tokenDecoded): void; } diff --git a/src/Provider/KeycloakClient.php b/src/Provider/KeycloakClient.php index 5ab5859..4d066ab 100644 --- a/src/Provider/KeycloakClient.php +++ b/src/Provider/KeycloakClient.php @@ -103,6 +103,7 @@ public function verifyToken(AccessTokenInterface $token): ?UserRepresentationDTO $decoder = TokenDecoderFactory::create($this->encryption_algorithm); $tokenDecoded = $decoder->decode($accessToken->getToken(), $this->encryption_key); + $decoder->validateToken($this->realm, $tokenDecoded); $this->keycloakClientLogger->info('KeycloakClient::verifyToken', [ 'tokenDecoded' => $tokenDecoded, ]); diff --git a/src/Token/HS256TokenDecoder.php b/src/Token/HS256TokenDecoder.php index 93357f4..f1521d9 100644 --- a/src/Token/HS256TokenDecoder.php +++ b/src/Token/HS256TokenDecoder.php @@ -4,24 +4,41 @@ namespace Mainick\KeycloakClientBundle\Token; +use Firebase\JWT\JWT; +use Firebase\JWT\Key; use Mainick\KeycloakClientBundle\Exception\TokenDecoderException; use Mainick\KeycloakClientBundle\Interface\TokenDecoderInterface; class HS256TokenDecoder implements TokenDecoderInterface { + public function decode(string $token, string $key): array { - // https://github.com/firebase/php-jwt#example-encodedecode-headers - [$headersB64, $payloadB64, $sig] = explode('.', $token); - $tokenDecoded = json_decode(base64_decode($payloadB64), true, 512, JSON_THROW_ON_ERROR); - try { + $tokenDecoded = JWT::decode($token, new Key($key, 'HS256')); + $json = json_encode($tokenDecoded, JSON_THROW_ON_ERROR); return json_decode($json, true, 512, JSON_THROW_ON_ERROR); - } - catch (\Exception $e) { + } catch (\Exception $e) { throw new TokenDecoderException('Error decoding token', $e); } } + + public function validateToken(string $realm, array $tokenDecoded): void + { + $now = time(); + + if ($tokenDecoded['exp'] < $now) { + throw TokenDecoderException::forExpiration(new \Exception('Token has expired')); + } + + if (str_contains($tokenDecoded['iss'], $realm) === false) { + throw TokenDecoderException::forIssuerMismatch(new \Exception('Invalid token issuer')); + } +// +// if ($tokenDecoded['aud'] !== 'account') { +// throw TokenDecoderException::forAudienceMismatch(new \Exception('Invalid token audience')); +// } + } } diff --git a/src/Token/RS256TokenDecoder.php b/src/Token/RS256TokenDecoder.php index 417b5fa..98dcdb0 100644 --- a/src/Token/RS256TokenDecoder.php +++ b/src/Token/RS256TokenDecoder.php @@ -13,17 +13,17 @@ class RS256TokenDecoder implements TokenDecoderInterface { public function decode(string $token, string $key): array { - $publicKeyPem = << new RS256TokenDecoder(), + self::ALGORITHM_HS256 => new HS256TokenDecoder(), + default => throw new \RuntimeException('Invalid algorithm'), + }; } } diff --git a/tests/Provider/KeycloakClientTest.php b/tests/Provider/KeycloakClientTest.php index 6f4fba2..ba05ef0 100644 --- a/tests/Provider/KeycloakClientTest.php +++ b/tests/Provider/KeycloakClientTest.php @@ -34,7 +34,7 @@ class KeycloakClientTest extends TestCase "exp": "%s", "iat": "%s", "jti": "e11a85c8-aa91-4f75-9088-57db4586f8b9", - "iss": "https://example.org/auth/realms/test-realm", + "iss": "https://example.org/auth/realms/mock_realm", "aud": "account", "nbf": "%s", "sub": "4332085e-b944-4acc-9eb1-27d8f5405f3e", @@ -93,6 +93,8 @@ protected function setUp(): void 'test-app', 'mock_secret', 'none', + self::ENCRYPTION_ALGORITHM, + self::ENCRYPTION_KEY ); $jwt_tmp = sprintf($this->jwtTemplate, time() + 3600, time(), time());