diff --git a/classes/IdTokenBuilder.php b/classes/IdTokenBuilder.php index cef3321aa36d6ca8752660bf89e1570ddc276ca6..1ea499de588ce879559ed0653029fbc4d0702f88 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 c481f9816b2fbc87aafda7e32702184a75ad62b6..bdee406f5070014dba03239a4c99702753012758 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 c2821ea90b72ff65153f4fd525a63cea481065ea..54b0ce011e95dec26c1a2598addda7b0256af1fd 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 cb4cf4b50e0054261103893ae8240e3631903d58..4d6c713bbda6430531c63cd2bc7362bedf009a13 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 76b64a8f0b93095e552aede99c5664a41cd8c680..5fd9f6db83a294a0fec9534fe1ad28f1439779be 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; } });