From dd6c80b4f1bb5cb14aed146ea63b5a4793338e05 Mon Sep 17 00:00:00 2001 From: Sonia Zorba <sonia.zorba@inaf.it> Date: Tue, 23 Jul 2019 17:26:24 +0200 Subject: [PATCH] Handled JWT verification for WS endpoints --- classes/IdTokenBuilder.php | 2 +- classes/OAuth2RequestHandler.php | 30 +++++++++++++++++++++++- classes/datalayer/JWKSDAO.php | 2 ++ classes/datalayer/mysql/MySQLJWKSDAO.php | 17 ++++++++++++++ index.php | 2 ++ 5 files changed, 51 insertions(+), 2 deletions(-) diff --git a/classes/IdTokenBuilder.php b/classes/IdTokenBuilder.php index cef3321..1ea499d 100644 --- a/classes/IdTokenBuilder.php +++ b/classes/IdTokenBuilder.php @@ -29,7 +29,7 @@ class IdTokenBuilder { 'iss' => $this->locator->config->jwtIssuer, 'sub' => $user->id, 'iat' => time(), - 'exp' => time() + 120, + 'exp' => time() + 3600, 'name' => $user->getCompleteName() ); diff --git a/classes/OAuth2RequestHandler.php b/classes/OAuth2RequestHandler.php index c481f98..bdee406 100644 --- a/classes/OAuth2RequestHandler.php +++ b/classes/OAuth2RequestHandler.php @@ -2,6 +2,8 @@ namespace RAP; +use \Firebase\JWT\JWT; + class OAuth2RequestHandler { private $locator; @@ -166,7 +168,33 @@ class OAuth2RequestHandler { } $accessToken = $this->locator->getAccessTokenDAO()->getAccessToken($bearer_token); - if ($accessToken->expired) { + if ($accessToken === null) { + $this->attemptJWTTokenValidation($bearer_token); + } else if ($accessToken->expired) { + throw new UnauthorizedException("Access token is expired"); + } + } + + private function attemptJWTTokenValidation($jwt): void { + + $jwtParts = explode('.', $jwt); + if (count($jwtParts) === 0) { + throw new UnauthorizedException("Invalid token"); + } + + $header = JWT::jsonDecode(JWT::urlsafeB64Decode($jwtParts[0])); + if (!isset($header->kid)) { + throw new UnauthorizedException("Invalid token: missing kid in header"); + } + + $keyPair = $this->locator->getJWKSDAO()->getRSAKeyPairById($header->kid); + if ($keyPair === null) { + throw new UnauthorizedException("Invalid kid: no key found"); + } + + try { + JWT::decode($jwt, $keyPair->publicKey, [$keyPair->alg]); + } catch (\Firebase\JWT\ExpiredException $ex) { throw new UnauthorizedException("Access token is expired"); } } diff --git a/classes/datalayer/JWKSDAO.php b/classes/datalayer/JWKSDAO.php index c2821ea..54b0ce0 100644 --- a/classes/datalayer/JWKSDAO.php +++ b/classes/datalayer/JWKSDAO.php @@ -6,6 +6,8 @@ interface JWKSDAO { public function getRSAKeyPairs(): array; + public function getRSAKeyPairById(string $id): ?RSAKeyPair; + public function insertRSAKeyPair(RSAKeyPair $keyPair): RSAKeyPair; public function getNewestKeyPair(): RSAKeyPair; diff --git a/classes/datalayer/mysql/MySQLJWKSDAO.php b/classes/datalayer/mysql/MySQLJWKSDAO.php index cb4cf4b..4d6c713 100644 --- a/classes/datalayer/mysql/MySQLJWKSDAO.php +++ b/classes/datalayer/mysql/MySQLJWKSDAO.php @@ -43,6 +43,23 @@ class MySQLJWKSDAO extends BaseMySQLDAO implements JWKSDAO { return $keyPairs; } + public function getRSAKeyPairById(string $id): ?RSAKeyPair { + + $dbh = $this->getDBHandler(); + + $query = "SELECT id, private_key, public_key, alg, creation_time FROM rsa_keypairs WHERE id = :id"; + + $stmt = $dbh->prepare($query); + $stmt->bindParam(':id', $id); + $stmt->execute(); + + foreach ($stmt->fetchAll() as $row) { + return $this->getRSAKeyPairFromResultRow($row); + } + + return null; + } + public function getNewestKeyPair(): RSAKeyPair { $dbh = $this->getDBHandler(); diff --git a/index.php b/index.php index 76b64a8..5fd9f6d 100644 --- a/index.php +++ b/index.php @@ -37,12 +37,14 @@ Flight::map('error', function($ex) { http_response_code(401); echo "Unauthorized: " . $ex->message; } else if ($ex instanceof \Exception) { + http_response_code(500); if ($ex->getMessage() !== null) { echo $ex->getMessage(); } else { echo $ex->getTraceAsString(); } } else { + http_response_code(500); throw $ex; } }); -- GitLab