diff --git a/.github/actions/install/open-source-parsers-jsoncpp/action.yml b/.github/actions/install/open-source-parsers-jsoncpp/action.yml new file mode 100644 index 000000000..3a341f2b0 --- /dev/null +++ b/.github/actions/install/open-source-parsers-jsoncpp/action.yml @@ -0,0 +1,22 @@ +name: Install open-source-parsers/jsoncpp +description: Install open-source-parsers/jsoncpp for building test application +inputs: + version: + description: The desired open-source-parsers/jsoncpp version to install + required: false + default: "1.9.5" +runs: + using: composite + steps: + - run: | + cd /tmp + wget https://github.com/open-source-parsers/jsoncpp/archive/${{ inputs.version }}.tar.gz + tar -zxf /tmp/${{ inputs.version }}.tar.gz + cd jsoncpp-${{ inputs.version }} + # https://github.com/open-source-parsers/jsoncpp/blob/69098a18b9af0c47549d9a271c054d13ca92b006/include/PreventInSourceBuilds.cmake#L8 + mkdir build + cd build + cmake .. -DJSONCPP_WITH_TESTS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_OBJECT_LIBS=OFF + cmake --build . + sudo cmake --install . + shell: bash diff --git a/.github/actions/render/defaults/action.yml b/.github/actions/render/defaults/action.yml index af28f7930..5dc1b2988 100644 --- a/.github/actions/render/defaults/action.yml +++ b/.github/actions/render/defaults/action.yml @@ -47,6 +47,11 @@ runs: library_url: LIBRARY_URL, disable_default_traits: disableDefault, }) - const outputDir = path.join('include', 'jwt-cpp', 'traits', TRAITS_NAME.replace('_', '-')) + // https://dmitripavlutin.com/replace-all-string-occurrences-javascript/ + function replaceAll(string, search, replace) { + return string.split(search).join(replace); + } + + const outputDir = path.join('include', 'jwt-cpp', 'traits', replaceAll(TRAITS_NAME, '_', '-')) fs.mkdirSync(outputDir, { recursive: true }) fs.writeFileSync(path.join(outputDir, 'defaults.h'), content) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bff4a71e1..a166c63ad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -75,6 +75,7 @@ jobs: - { name: "danielaparker_jsoncons", library: "jsoncons", url: "https://github.com/danielaparker/jsoncons", disable_pico: true } - { name: "kazuho_picojson", library: "picojson", url: "https://github.com/kazuho/picojson", disable_pico: false } - { name: "nlohmann_json", library: "JSON for Modern C++", url: "https://github.com/nlohmann/json", disable_pico: true } + - { name: "open_source_parsers_jsoncpp", library: "jsoncpp", url: "https://github.com/open-source-parsers/jsoncpp", disable_pico: true } name: render-defaults (${{ matrix.traits.name }}) steps: - uses: actions/checkout@v3 @@ -84,6 +85,7 @@ jobs: library_name: ${{ matrix.traits.library }} library_url: ${{ matrix.traits.url }} disable_default_traits: ${{ matrix.traits.disable_pico }} + - run: clang-format -i include/jwt-cpp/traits/**/*.h - run: git add include/jwt-cpp/traits/* - uses: ./.github/actions/process-linting-results with: @@ -99,6 +101,7 @@ jobs: - { name: "danielaparker_jsoncons", suite: "JsonconsTest" } # - { name: "kazuho_picojson", suite: "PicoJsonTest" } # Currently the default everything tests against this! - { name: "nlohmann_json", suite: "NlohmannTest" } + - { name: "open_source_parsers_jsoncpp", suite: "OspJsoncppTest" } name: render-tests (${{ matrix.traits.name }}) steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/traits.yml b/.github/workflows/traits.yml index a86568f47..da44ac484 100644 --- a/.github/workflows/traits.yml +++ b/.github/workflows/traits.yml @@ -17,6 +17,7 @@ jobs: - { name: "boost-json", tag: "1.78.0", version: "v1.80.0" } - { name: "nlohmann-json", tag: "3.11.2", version: "v3.11.2" } - { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" } + - { name: "open-source-parsers-jsoncpp", tag: "1.9.5", version: "v1.9.5" } steps: - uses: actions/checkout@v3 - uses: lukka/get-cmake@latest @@ -50,6 +51,11 @@ jobs: with: version: ${{matrix.target.tag}} + - if: matrix.target.name == 'open-source-parsers-jsoncpp' + uses: ./.github/actions/install/open-source-parsers-jsoncpp + with: + version: ${{matrix.target.tag}} + - name: test working-directory: example/traits run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cd876f50..badf330e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ set(JWT_SSL_LIBRARY_OPTIONS OpenSSL LibreSSL wolfSSL) set(JWT_SSL_LIBRARY OpenSSL CACHE STRING "Determines which SSL library to build with") set_property(CACHE JWT_SSL_LIBRARY PROPERTY STRINGS ${JWT_SSL_LIBRARY_OPTIONS}) -set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json) +set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json open-source-parsers-jsoncpp) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") @@ -71,11 +71,9 @@ find_package(nlohmann_json CONFIG) if(NOT nlohmann_json_FOUND) message(STATUS "jwt-cpp: using FetchContent for nlohmann json") include(FetchContent) - FetchContent_Declare(nlohmann_json - URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz - URL_MD5 127794b2c82c0c5693805feaa2a703e2 - ) - FetchContent_MakeAvailable(nlohmann_json) + fetchcontent_declare(nlohmann_json URL https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz + URL_MD5 127794b2c82c0c5693805feaa2a703e2) + fetchcontent_makeavailable(nlohmann_json) endif() set(JWT_INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/example/traits/CMakeLists.txt b/example/traits/CMakeLists.txt index 42f88d54f..4734fabc7 100644 --- a/example/traits/CMakeLists.txt +++ b/example/traits/CMakeLists.txt @@ -28,3 +28,9 @@ if(TARGET kazuho_picojson) add_executable(kazuho-picojson kazuho-picojson.cpp) target_link_libraries(kazuho-picojson jwt-cpp::jwt-cpp kazuho_picojson) endif() + +find_package(jsoncpp CONFIG) +if(TARGET jsoncpp_static) + add_executable(open-source-parsers-jsoncpp open-source-parsers-jsoncpp.cpp) + target_link_libraries(open-source-parsers-jsoncpp jsoncpp_static jwt-cpp::jwt-cpp) +endif() diff --git a/example/traits/open-source-parsers-jsoncpp.cpp b/example/traits/open-source-parsers-jsoncpp.cpp new file mode 100644 index 000000000..893669114 --- /dev/null +++ b/example/traits/open-source-parsers-jsoncpp.cpp @@ -0,0 +1,47 @@ +#include "jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::open_source_parsers_jsoncpp; + using claim = jwt::basic_claim; + + claim from_raw_json; + std::istringstream iss{R"##({"api":{"array":[1,2,3],"null":null}})##"}; + iss >> from_raw_json; + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072ULL, 770979831ULL, 427239169ULL, 525936436ULL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time) + .set_expires_at(time + min{2} + sec{15}) + .set_payload_claim("boolean", true) + .set_payload_claim("integer", 12345) + .set_payload_claim("precision", 12.3456789) + .set_payload_claim("strings", claim(list)) + .set_payload_claim("array", {big_numbers.begin(), big_numbers.end()}) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + const auto decoded = jwt::decode(token); + + const auto array = traits::as_array(decoded.get_payload_claim("object").to_json()["api"]["array"]); + std::cout << "payload /object/api/array = " << array << std::endl; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/include/jwt-cpp/traits/open-source-parsers-jsoncpp/defaults.h b/include/jwt-cpp/traits/open-source-parsers-jsoncpp/defaults.h new file mode 100644 index 000000000..57dce4984 --- /dev/null +++ b/include/jwt-cpp/traits/open-source-parsers-jsoncpp/defaults.h @@ -0,0 +1,92 @@ +#ifndef JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H +#define JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [jsoncpp](https://github.com/open-source-parsers/jsoncpp) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { + return builder(); + } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk + parse_jwk(const traits::open_source_parsers_jsoncpp::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks + parse_jwks(const traits::open_source_parsers_jsoncpp::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_OPEN_SOURCE_PARSERS_JSONCPP_DEFAULTS_H diff --git a/include/jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h b/include/jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h new file mode 100644 index 000000000..1d4c0c7d6 --- /dev/null +++ b/include/jwt-cpp/traits/open-source-parsers-jsoncpp/traits.h @@ -0,0 +1,130 @@ +#ifndef JWT_CPP_JSONCPP_TRAITS_H +#define JWT_CPP_JSONCPP_TRAITS_H + +#include "jwt-cpp/jwt.h" +#include "json/json.h" + +namespace jwt { + namespace traits { + struct open_source_parsers_jsoncpp { + using value_type = Json::Value; + using string_type = std::string; + class array_type : public Json::Value { + public: + using value_type = Json::Value; + + array_type() = default; + array_type(const array_type&) = default; + explicit array_type(const Json::Value& o) : Json::Value(o) {} + array_type(array_type&&) = default; + explicit array_type(Json::Value&& o) : Json::Value(o) {} + template + array_type(Iterator begin, Iterator end) { + for (Iterator it = begin; it != end; ++it) { + Json::Value value; + value = *it; + this->append(value); + } + } + ~array_type() = default; + array_type& operator=(const array_type& o) = default; + array_type& operator=(array_type&& o) noexcept = default; + }; + using number_type = double; + using integer_type = Json::Value::Int; + using boolean_type = bool; + class object_type : public Json::Value { + public: + using key_type = std::string; + using mapped_type = Json::Value; + using size_type = size_t; + + object_type() = default; + object_type(const object_type&) = default; + explicit object_type(const Json::Value& o) : Json::Value(o) {} + object_type(object_type&&) = default; + explicit object_type(Json::Value&& o) : Json::Value(o) {} + ~object_type() = default; + object_type& operator=(const object_type& o) = default; + object_type& operator=(object_type&& o) noexcept = default; + + // Add missing C++11 element access + const mapped_type& at(const key_type& key) const { + Json::Value const* found = find(key.data(), key.data() + key.length()); + if (!found) throw std::out_of_range("invalid key"); + return *found; + } + + size_type count(const key_type& key) const { return this->isMember(key) ? 1 : 0; } + }; + + // Translation between the implementation notion of type, to the jwt::json::type equivilant + static jwt::json::type get_type(const value_type& val) { + using jwt::json::type; + + if (val.isArray()) + return type::array; + else if (val.isString()) + return type::string; + else if (val.isNumeric()) + return type::number; + else if (val.isInt()) + return type::integer; + else if (val.isBool()) + return type::boolean; + else if (val.isObject()) + return type::object; + + throw std::logic_error("invalid type"); + } + + static integer_type as_integer(const value_type& val) { + switch (val.type()) { + case Json::intValue: return val.asInt64(); + case Json::uintValue: return static_cast(val.asUInt64()); + default: throw std::bad_cast(); + } + } + + static boolean_type as_boolean(const value_type& val) { + if (!val.isBool()) throw std::bad_cast(); + return val.asBool(); + } + + static number_type as_number(const value_type& val) { + if (!val.isNumeric()) throw std::bad_cast(); + return val.asDouble(); + } + + static string_type as_string(const value_type& val) { + if (!val.isString()) throw std::bad_cast(); + return val.asString(); + } + + static object_type as_object(const value_type& val) { + if (!val.isObject()) throw std::bad_cast(); + return object_type(val); + } + + static array_type as_array(const value_type& val) { + if (!val.isArray()) throw std::bad_cast(); + return array_type(val); + } + + static bool parse(value_type& val, string_type str) { + Json::Reader reader; + return reader.parse(str, val); + } + + static string_type serialize(const value_type& val) { + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + std::unique_ptr writer(builder.newStreamWriter()); + return Json::writeString(builder, val); + } + }; + } // namespace traits +} // namespace jwt + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cb7eaddc8..0b88b5e74 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,17 @@ include(GoogleTest) if(HUNTER_ENABLED) hunter_add_package(GTest) endif() -find_package(GTest REQUIRED) + +find_package(GTest) +if(NOT TARGET GTest::gtest_main) + message(STATUS "jwt-cpp: using FetchContent for GTest") + include(FetchContent) + fetchcontent_declare(googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip) + # https://google.github.io/googletest/quickstart-cmake.html + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + fetchcontent_makeavailable(googletest) +endif() set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/BaseTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ClaimTest.cpp @@ -30,6 +40,11 @@ if(TARGET boost_json) list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/BoostJsonTest.cpp) endif() +find_package(jsoncpp CONFIG) +if(TARGET jsoncpp_static) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/OspJsoncppTest.cpp) +endif() + add_executable(jwt-cpp-test ${TEST_SOURCES}) # NOTE: Don't use space inside a generator expression here, because the function prematurely breaks the expression into @@ -44,7 +59,8 @@ if(HUNTER_ENABLED) # Define a compile define to bypass openssl error tests target_compile_definitions(jwt-cpp-test PRIVATE HUNTER_ENABLED=1) else() - target_link_libraries(jwt-cpp-test PRIVATE GTest::GTest GTest::Main) + # https://github.com/google/googletest/blob/eb80f759d595874a5e905a3342bd8e2af4c0a12d/googletest/README.md?plain=1#L62-L64 + target_link_libraries(jwt-cpp-test PRIVATE GTest::gtest_main) if(TARGET jsoncons) target_link_libraries(jwt-cpp-test PRIVATE jsoncons) endif() diff --git a/tests/traits/OspJsoncppTest.cpp b/tests/traits/OspJsoncppTest.cpp new file mode 100644 index 000000000..1b65fa5ab --- /dev/null +++ b/tests/traits/OspJsoncppTest.cpp @@ -0,0 +1,133 @@ +#include "jwt-cpp/traits/open-source_parsers_jsoncpp/traits.h" + +#include + +TEST(OspJsoncppTest, BasicClaims) { + const auto string = jwt::basic_claim( + jwt::traits::open_source_parsers_jsoncpp::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(OspJsoncppTest, AudienceAsString) { + jwt::traits::open_source_parsers_jsoncpp::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(OspJsoncppTest, SetArray) { + std::vector vect = {100, 20, 10}; + auto token = jwt::create() + .set_payload_claim( + "test", jwt::basic_claim(vect.begin(), vect.end())) + .sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(OspJsoncppTest, SetObject) { + std::istringstream iss{"{\"api-x\": [1]}"}; + jwt::basic_claim object; + iss >> object; + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(OspJsoncppTest, VerifyTokenHS256) { + jwt::traits::open_source_parsers_jsoncpp::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(OspJsoncppTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(OspJsoncppTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(OspJsoncppTest, VerifyArray) { + jwt::traits::open_source_parsers_jsoncpp::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + std::vector vect = {100, 20, 10}; + jwt::basic_claim array_claim(vect.begin(), vect.end()); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(OspJsoncppTest, VerifyObject) { + jwt::traits::open_source_parsers_jsoncpp::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::basic_claim object_claim; + std::istringstream iss{"{\"api-x\": [1]}"}; + iss >> object_claim; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +}