locator = $locator; } public function validateToken(): object { $headers = apache_request_headers(); if (!isset($headers['Authorization'])) { throw new BadRequestException("Missing Authorization header"); } $authorizationHeader = explode(" ", $headers['Authorization']); if ($authorizationHeader[0] === "Bearer") { $token = $authorizationHeader[1]; } else { throw new BadRequestException("Invalid token type"); } return $this->getValidTokenObject($token); } public function getValidTokenObject(string $jwt): object { $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 { $token = JWT::decode($jwt, $keyPair->publicKey, [$keyPair->alg]); if (!isset($token->sub)) { throw new UnauthorizedException("Invalid token: missing subject claim"); } return $token; } catch (\Firebase\JWT\ExpiredException $ex) { throw new UnauthorizedException("Access token is expired"); } } public function checkScope(object $tokenData, string $desiredScope): void { if (!(isset($tokenData->scope))) { throw new UnauthorizedException("Missing 'scope' claim in access token"); } $scopes = explode(' ', $tokenData->scope); foreach ($scopes as $scope) { if ($scope === $desiredScope) { return; } } throw new UnauthorizedException("Scope '$desiredScope' is required for performing this action"); } }